// // cwiresxcmdhandler.cpp // xlxd // // Created by Jean-Luc Deltombe (LX3JL) on 09/10/2019. // Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. // // ---------------------------------------------------------------------------- // This file is part of xlxd. // // xlxd is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // xlxd is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Foobar. If not, see . // ---------------------------------------------------------------------------- #include #include "main.h" #include "ccrc.h" #include "cysffich.h" #include "cysfpayload.h" #include "cysfclient.h" #include "cysfnodedirfile.h" #include "cysfnodedirhttp.h" #include "cysfutils.h" #include "creflector.h" #include "cwiresxcmdhandler.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor CWiresxCmdHandler::CWiresxCmdHandler() { m_seqNo = 0; m_bStopThread = false; m_pThread = NULL; } //////////////////////////////////////////////////////////////////////////////////////// // destructor CWiresxCmdHandler::~CWiresxCmdHandler() { // kill threads m_bStopThread = true; if ( m_pThread != NULL ) { m_pThread->join(); delete m_pThread; } // empty queue m_CmdQueue.Lock(); while ( !m_CmdQueue.empty() ) { m_CmdQueue.pop(); } m_CmdQueue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// // initialization bool CWiresxCmdHandler::Init(void) { // fill our wiresx info m_ReflectorWiresxInfo.SetCallsign(g_Reflector.GetCallsign()); m_ReflectorWiresxInfo.SetNode(g_Reflector.GetCallsign()); m_ReflectorWiresxInfo.SetName("Reflector"); // reset stop flag m_bStopThread = false; // start thread; m_pThread = new std::thread(CWiresxCmdHandler::Thread, this); // done return true; } void CWiresxCmdHandler::Close(void) { m_bStopThread = true; if ( m_pThread != NULL ) { m_pThread->join(); delete m_pThread; m_pThread = NULL; } } //////////////////////////////////////////////////////////////////////////////////////// // thread void CWiresxCmdHandler::Thread(CWiresxCmdHandler *This) { while ( !This->m_bStopThread ) { This->Task(); } } //////////////////////////////////////////////////////////////////////////////////////// // task void CWiresxCmdHandler::Task(void) { CWiresxInfo Info; CWiresxCmd Cmd; uint32 uiNodeTxFreq; uint32 uiNodeRxFreq; char cModule; bool bCmd; // anything to do ? bCmd = false; m_CmdQueue.Lock(); { // any cmd in our queue ? if ( !m_CmdQueue.empty() ) { // yes, get the command Cmd = m_CmdQueue.front(); // check that the command is at least one second old // so that the so delayed processing of the command // introduce the delay the radio needs to switch // from tx to rx if ( Cmd.GetTime().DurationSinceNow() >= WIRESX_REPLY_DELAY ) { m_CmdQueue.pop(); bCmd = true; } } } m_CmdQueue.Unlock(); // handle it if ( bCmd ) { // fill our info object Info = m_ReflectorWiresxInfo; g_YsfNodeDir.FindFrequencies(Cmd.GetCallsign(), &uiNodeTxFreq, &uiNodeRxFreq); Info.SetFrequencies(uiNodeTxFreq, uiNodeRxFreq); // find our client and the module it's currentlink linked to cModule = ' '; CClients *clients = g_Reflector.GetClients(); CClient *client = clients->FindClient(Cmd.GetCallsign(), Cmd.GetIp(), PROTOCOL_YSF); if ( client != NULL ) { cModule = client->GetReflectorModule(); } g_Reflector.ReleaseClients(); // and crack the cmd switch ( Cmd.GetCmd() ) { case WIRESX_CMD_DX_REQ: std::cout << "Wires-X DX_REQ command from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; // reply ReplyToWiresxDxReqPacket(Cmd.GetIp(), Info, cModule); break; case WIRESX_CMD_ALL_REQ: case WIRESX_CMD_SEARCH_REQ: std::cout << "Wires-X ALL_REQ command from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; // reply ReplyToWiresxAllReqPacket(Cmd.GetIp(), Info, Cmd.GetArg()); break; case WIRESX_CMD_CONN_REQ: if ( (Cmd.GetArg() >= 1) && (Cmd.GetArg() <= NB_OF_MODULES) ) { cModule = 'A' + (char)(Cmd.GetArg() - 1); std::cout << "Wires-X CONN_REQ command to link on module " << cModule << " from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; // acknowledge ReplyToWiresxConnReqPacket(Cmd.GetIp(), Info, cModule); // change client's module CClients *clients = g_Reflector.GetClients(); CClient *client = clients->FindClient(Cmd.GetCallsign(), Cmd.GetIp(), PROTOCOL_YSF); if ( client != NULL ) { client->SetReflectorModule(cModule); } g_Reflector.ReleaseClients(); } else { std::cout << "Wires-X CONN_REQ command with illegal argument from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; } break; case WIRESX_CMD_DISC_REQ: std::cout << "Wires-X DISC_REQ command from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; // and reply ReplyToWiresxDiscReqPacket(Cmd.GetIp(), Info); // change client's module { CClients *clients = g_Reflector.GetClients(); CClient *client = clients->FindClient(Cmd.GetCallsign(), Cmd.GetIp(), PROTOCOL_YSF); if ( client != NULL ) { client->SetReflectorModule(' '); } g_Reflector.ReleaseClients(); } break; case WIRESX_CMD_UNKNOWN: default: std::cout << "Wires-X non implemented command from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; break; } } else { // if nothing to do, sleep a bit CTimePoint::TaskSleepFor(10); } } //////////////////////////////////////////////////////////////////////////////////////// // packet encoding bool CWiresxCmdHandler::ReplyToWiresxDxReqPacket(const CIp &Ip, const CWiresxInfo &WiresxInfo, char Module) { uint8 DX_RESP[] = {0x5DU, 0x51U, 0x5FU, 0x26U}; bool ok; uint8 data[150U]; uint8 RoomId; bool IsLinked; // linked module // module A == 0 IsLinked = (Module != ' '); RoomId = (uint8)(Module - 'A'); // fill data buffer ::memset(data, 0x00U, 150U); ::memset(data, ' ', 128U); // seq no data[0U] = m_seqNo; // command ::memcpy(data + 1U, DX_RESP, 4U); // node info ::memcpy(data + 5U, WiresxInfo.GetId(), 5U); ::memcpy(data + 10U, WiresxInfo.GetNode(), 10U); ::memcpy(data + 20U, WiresxInfo.GetName(), 14U); // linked room if (!IsLinked) { data[34U] = '1'; data[35U] = '2'; // no room linked data[57U] = '0'; data[58U] = '0'; data[59U] = '0'; } else { data[34U] = '1'; data[35U] = '5'; // linked room char item[16U]; // refl->m_id ::sprintf(item, "%05d", 4001U + RoomId); ::memcpy(data + 36U, item, 5U); // refl->name ::memset(item, ' ', 16U); ::memcpy(item, "MODULE", 6U); // K2IE fix for U/C only radios item[7] = 'A' + RoomId; ::memcpy(data + 41U, item, 16U); // refl->count ::sprintf(item, "%03d", RoomId + 1); ::memcpy(data + 57U, item, 3U); // other ::memset(data + 60U, ' ', 10U); // refl->m_desc ::memcpy(data + 70U, "Description ", 14U); } // frequencies { unsigned int offset; char sign; if (WiresxInfo.GetTxFrequency() >= WiresxInfo.GetRxFrequency()) { offset = WiresxInfo.GetTxFrequency() - WiresxInfo.GetRxFrequency(); sign = '-'; } else { offset = WiresxInfo.GetRxFrequency() - WiresxInfo.GetTxFrequency(); sign = '+'; } unsigned int freqHz = WiresxInfo.GetTxFrequency() % 1000000U; unsigned int freqkHz = (freqHz + 500U) / 1000U; char freq[30U]; ::sprintf(freq, "%05u.%03u000%c%03u.%06u", WiresxInfo.GetTxFrequency() / 1000000U, freqkHz, sign, offset / 1000000U, offset % 1000000U); ::memcpy(data + 84U, freq, 23U); } // EOD & CRC data[127U] = 0x03U; data[128U] = CCRC::addCRC(data, 128U); // and encode the reply CBuffer Data; Data.Set(data, 129U); ok = EncodeAndSendWiresxPacket(Ip, Data, WiresxInfo); // done m_seqNo++; return ok; } bool CWiresxCmdHandler::ReplyToWiresxAllReqPacket(const CIp &Ip, const CWiresxInfo &WiresxInfo, int Start) { bool ok = false; uint8 ALL_RESP[] = {0x5DU, 0x46U, 0x5FU, 0x29U}; uint8 data[1100U]; // fill data buffer ::memset(data, 0x00U, 1100U); // seq no data[0U] = m_seqNo; // command ::memcpy(data + 1U, ALL_RESP, 4U); data[5U] = '2'; data[6U] = '1'; // node info ::memcpy(data + 7U, WiresxInfo.GetId(), 5U); ::memcpy(data + 12U, WiresxInfo.GetNode(), 10U); // number of entries uint total = NB_OF_MODULES; uint n = NB_OF_MODULES - Start; if (n > 20U) n = 20U; ::sprintf((char*)(data + 22U), "%03u%03u", n, total); data[28U] = 0x0DU; // entries uint offset = 29U; for ( uint i = 0; i < n; i++ ) { char item[16U]; // module A == 0 int RoomId = i + Start; // prepare ::memset(data + offset, ' ', 50U); data[offset + 0U] = '5'; // refl->m_id ::sprintf(item, "%05d", 4001U + RoomId); ::memcpy(data + offset + 1U, item, 5U); // refl->name ::memset(item, ' ', 16U); ::memcpy(item, "MODULE", 6U); // K2IE fix for U/C only radios item[7] = 'A' + RoomId; ::memcpy(data + offset + 6U, item, 16U); // refl->count ::sprintf(item, "%03d", RoomId + 1); ::memcpy(data + offset + 22U, item, 3U); // other ::memset(data + offset + 25U, ' ', 10U); // refl->m_desc ::memcpy(data + offset + 35U, "Description ", 14U); data[offset + 49U] = 0x0DU; // next offset += 50U; } // the following is a workaround for FT2D which // do not accept less than 20 items frames. // first send a 'patched' frame if ( n < 20 ) { // FT2D workaround uint offset2 = offset; // patch the remaining uint k = 1029U - offset2; ::memset(data+offset2, ' ', k); offset2 += k; // EOD + CRC data[offset2 + 0U] = 0x03U; data[offset2 + 1U] = CCRC::addCRC(data, offset2 + 1U); offset2 += 2U; // and encode the reply CBuffer Data; Data.Set(data, offset2 + 2U); ok = EncodeAndSendWiresxPacket(Ip, Data, WiresxInfo); } // and next repeat with normal frame { // EOD + CRC data[offset + 0U] = 0x03U; data[offset + 1U] = CCRC::addCRC(data, offset + 1U); offset += 2U; // patch the remaining //uint k = 1031U - offset; //::memset(data+offset, ' ', k); //offset += k; // and encode the reply CBuffer Data; Data.Set(data, offset + 2U); ok = EncodeAndSendWiresxPacket(Ip, Data, WiresxInfo); } // done m_seqNo++; return ok; } bool CWiresxCmdHandler::ReplyToWiresxConnReqPacket(const CIp &Ip, const CWiresxInfo &WiresxInfo, char Module) { uint8 CONN_RESP[] = {0x5DU, 0x41U, 0x5FU, 0x26U}; bool ok = false; uint8 data[110U]; uint RoomId; // linked room // Module A == 0 RoomId = (uint8)(Module - 'A'); // prepare buffer ::memset(data, 0x00U, 110U); ::memset(data, ' ', 90U); // seq no data[0U] = m_seqNo; // command ::memcpy(data + 1U, CONN_RESP, 4U); // node info ::memcpy(data + 5U, WiresxInfo.GetId(), 5U); ::memcpy(data + 10U, WiresxInfo.GetNode(), 10U); ::memcpy(data + 20U, WiresxInfo.GetName(), 14U); data[34U] = '1'; data[35U] = '5'; // entry info { char item[16U]; // refl->m_id ::sprintf(item, "%05d", 4001U + RoomId); ::memcpy(data + 36U, item, 5U); // refl->name ::memset(item, ' ', 16U); ::memcpy(item, "MODULE", 6U); // K2IE fix for U/C only radios item[7] = 'A' + RoomId; ::memcpy(data + 41U, item, 16U); // refl->count ::sprintf(item, "%03d", RoomId + 1); ::memcpy(data + 57U, item, 3U); // refl->m_desc ::memcpy(data + 70U, "Description ", 14U); } data[84U] = '0'; data[85U] = '0'; data[86U] = '0'; data[87U] = '0'; data[88U] = '0'; // EOD + CRC data[89U] = 0x03U; data[90U] = CCRC::addCRC(data, 90U); // and encode the reply CBuffer Data; Data.Set(data, 91U); ok = EncodeAndSendWiresxPacket(Ip, Data, WiresxInfo); // done m_seqNo++; return ok; } bool CWiresxCmdHandler::ReplyToWiresxDiscReqPacket(const CIp &Ip, const CWiresxInfo &WiresxInfo) { uint8 DISC_RESP[] = {0x5DU, 0x41U, 0x5FU, 0x26U}; bool ok = false; uint8 data[110U]; // prepare buffer ::memset(data, 0x00U, 110U); ::memset(data, ' ', 90U); // seq no data[0U] = m_seqNo; // command ::memcpy(data + 1U, DISC_RESP, 4U); // node info ::memcpy(data + 5U, WiresxInfo.GetId(), 5U); ::memcpy(data + 10U, WiresxInfo.GetNode(), 10U); ::memcpy(data + 20U, WiresxInfo.GetName(), 14U); // data data[34U] = '1'; data[35U] = '2'; data[57U] = '0'; data[58U] = '0'; data[59U] = '0'; // EOD + CRC data[89U] = 0x03U; data[90U] = CCRC::addCRC(data, 90U); // and encode the reply CBuffer Data; Data.Set(data, 91U); ok = EncodeAndSendWiresxPacket(Ip, Data, WiresxInfo); // done m_seqNo++; return ok; } //////////////////////////////////////////////////////////////////////////////////////// ///// packet encoding helpers bool CWiresxCmdHandler::EncodeAndSendWiresxPacket(const CIp &Ip, const CBuffer &DataOrg, const CWiresxInfo &WiresxInfo) { uint8 DEFAULT_FICH[] = {0x20U, 0x00U, 0x01U, 0x00U}; uint8 NET_HEADER[] = "YSFD ALL "; CYSFFICH fich; CYSFPayload payload; uint8 buffer[200U]; CBuffer Data(DataOrg); // seq no uint8 seqNo = 0U; // calculate bt and adjust length uint length = (uint)Data.size(); uint8 bt = 0; if (length > 260U) { bt = 1U; bt += (length - 260U) / 259U; length += bt; } if (length > 20U) { uint blocks = (length - 20U) / 40U; if ((length % 40U) > 0U) blocks++; length = blocks * 40U + 20U; } else { length = 20U; } if ( length > (uint)Data.size() ) { Data.Append((uint8)0x20U, (int)(length - (uint)Data.size())); } // ft uint8 ft = WiresxCalcFt(length, 0U); // Write the header { //header ::memcpy(buffer, NET_HEADER, 34U); ::memcpy(buffer + 4U, WiresxInfo.GetCallsign(), 10U); ::memcpy(buffer + 14U, WiresxInfo.GetNode(), 10U); // sync ::memcpy(buffer + 35U, YSF_SYNC_BYTES, YSF_SYNC_LENGTH_BYTES); // Fich fich.load(DEFAULT_FICH); fich.setFI(YSF_FI_HEADER); fich.setBT(bt); fich.setFT(ft); fich.encode(buffer + 35U + 5U); // payload payload.writeDataFRModeData1(WiresxInfo.GetCsd1(), buffer + 35U); payload.writeDataFRModeData2(WiresxInfo.GetCsd2(), buffer + 35U); // seqno buffer[34U] = seqNo; seqNo += 2U; // and post it SendPacket(Ip, buffer); } // write the payload fich.setFI(YSF_FI_COMMUNICATIONS); uint offset = 0; for ( uint8 bn = 0; bn <= bt; bn++ ) { for ( uint8 fn = 0; fn <= ft; fn++ ) { // fich fich.setFT(ft); fich.setFN(fn); fich.setBT(bt); fich.setBN(bn); fich.encode(buffer + 35U + 5U); // seq no buffer[34U] = seqNo; seqNo += 2U; // data //if ( (bn == 0) && (fn == 0) ) if ( fn == 0 ) { payload.writeDataFRModeData1(WiresxInfo.GetCsd1(), buffer + 35); payload.writeDataFRModeData2(WiresxInfo.GetCsd2(), buffer + 35); } //else if ( (bn == 0) && (fn == 1) ) else if ( fn == 1 ) { if ( bn == 0 ) { payload.writeDataFRModeData1(WiresxInfo.GetCsd3(), buffer + 35); payload.writeDataFRModeData2(Data.data() + offset, buffer + 35); offset += 20; } else { uint8 temp[20U]; temp[0U] = 0x00U; ::memcpy(temp + 1U, Data.data() + offset, 19U); payload.writeDataFRModeData2(temp, buffer + 35U); offset += 19U; } } else { payload.writeDataFRModeData1(Data.data() + offset, buffer + 35); offset += 20; payload.writeDataFRModeData2(Data.data() + offset, buffer + 35); offset += 20; } // and post it SendPacket(Ip, buffer); // and some delay before next packet CTimePoint::TaskSleepFor(100); } } // Write the trailer { // fich fich.setFI(YSF_FI_TERMINATOR); fich.setFN(ft); fich.setBN(bt); fich.encode(buffer + 35U + 5U); // payload payload.writeDataFRModeData1(WiresxInfo.GetCsd1(), buffer + 35U); payload.writeDataFRModeData2(WiresxInfo.GetCsd2(), buffer + 35U); // seq no buffer[34U] = seqNo | 0x01U; // and post it SendPacket(Ip, buffer); } // done return true; } uint8 CWiresxCmdHandler::WiresxCalcFt(uint length, uint offset) const { length -= offset; if (length > 220U) return 7U; if (length > 180U) return 6U; if (length > 140U) return 5U; if (length > 100U) return 4U; if (length > 60U) return 3U; if (length > 20U) return 2U; return 1U; } void CWiresxCmdHandler::SendPacket(const CIp &Ip, uint8 *Buffer) { //CBuffer packet(Buffer, 155); //DebugTestDecodePacket(packet); // and push in queue m_PacketQueue.Lock(); { m_PacketQueue.push(CWiresxPacket(CBuffer(Buffer, 155), Ip)); } m_PacketQueue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// // debug #ifdef DEBUG_DUMPFILE bool CWiresxCmdHandler::DebugTestDecodePacket(const CBuffer &Buffer) { uint8 tag[] = { 'Y','S','F','D' }; static uint8 command[4098]; static int len; CYSFFICH Fich; CYSFPayload payload; CBuffer dump; bool valid = false; if ( (Buffer.size() == 155) && (Buffer.Compare(tag, sizeof(tag)) == 0) ) { // decode YSH fich if ( Fich.decode(&(Buffer.data()[40])) ) { std::cout << (int)Fich.getDT() << "," << (int)Fich.getFI() << "," << (int)Fich.getBN() << "," << (int)Fich.getBT() << "," << (int)Fich.getFN() << "," << (int)Fich.getFT() << " : "; switch ( Fich.getFI() ) { case YSF_FI_HEADER: len = 0; ::memset(command, 0x00, sizeof(command)); std::cout << "Header" << std::endl; break; case YSF_FI_TERMINATOR: std::cout << "Trailer" << std::endl; std::cout << "length of payload : " << len << std::endl; dump.Set(command, len); dump.DebugDump(g_Reflector.m_DebugFile); dump.DebugDumpAscii(g_Reflector.m_DebugFile); break; case YSF_FI_COMMUNICATIONS: if ( Fich.getDT() == YSF_DT_DATA_FR_MODE ) { valid = payload.readDataFRModeData1(&(Buffer.data()[35]), command + len); len += 20; valid &= payload.readDataFRModeData2(&(Buffer.data()[35]), command + len); len += 20; std::cout << "decoded ok" << std::endl; } break; } } else { std::cout << "invalid fich in packet" << std::endl; } } else { std::cout << "invalid size packet" << std::endl; } return valid; } #endif