From 7159c8297a627c1598547b9657ecc65589c18319 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 8 Oct 2022 21:35:57 -0400 Subject: [PATCH] fix issue with logger not allowing strings >500 characters; refactor RCON to support returning data to a RCON client; enhance network support to allow enabling RCON while networking itself is disabled; add version and help commands to RCON; --- Log.cpp | 11 +- Makefile | 4 +- Utils.cpp | 16 - Utils.h | 20 + config.example.yml | 10 +- host/Host.cpp | 94 +++-- network/RemoteControl.cpp | 833 +++++++++++++++++++++++++------------- network/RemoteControl.h | 6 + network/UDPSocket.cpp | 4 +- remote/RemoteCommand.cpp | 98 ++++- remote/RemoteCommand.h | 10 +- 11 files changed, 736 insertions(+), 370 deletions(-) diff --git a/Log.cpp b/Log.cpp index 30a82f62..5874ef3e 100644 --- a/Log.cpp +++ b/Log.cpp @@ -55,6 +55,9 @@ #define EOL "\r\n" #endif +const uint32_t ACT_LOG_BUFFER_LEN = 501U; +const uint32_t LOG_BUFFER_LEN = 4096U; + // --------------------------------------------------------------------------- // Global Variables // --------------------------------------------------------------------------- @@ -194,7 +197,7 @@ void ActivityLog(const char *mode, const bool sourceRf, const char* msg, ...) assert(mode != NULL); assert(msg != NULL); - char buffer[501U]; + char buffer[ACT_LOG_BUFFER_LEN]; #if defined(_WIN32) || defined(_WIN64) SYSTEMTIME st; ::GetSystemTime(&st); @@ -212,7 +215,7 @@ void ActivityLog(const char *mode, const bool sourceRf, const char* msg, ...) va_list vl; va_start(vl, msg); - ::vsnprintf(buffer + ::strlen(buffer), 500, msg, vl); + ::vsnprintf(buffer + ::strlen(buffer), ACT_LOG_BUFFER_LEN - 1U, msg, vl); va_end(vl); @@ -279,7 +282,7 @@ void Log(uint32_t level, const char *module, const char* fmt, ...) { assert(fmt != NULL); - char buffer[501U]; + char buffer[LOG_BUFFER_LEN]; #if defined(_WIN32) || defined(_WIN64) if (!m_disableTimeDisplay) { SYSTEMTIME st; @@ -327,7 +330,7 @@ void Log(uint32_t level, const char *module, const char* fmt, ...) va_list vl; va_start(vl, fmt); - ::vsnprintf(buffer + ::strlen(buffer), 500, fmt, vl); + ::vsnprintf(buffer + ::strlen(buffer), LOG_BUFFER_LEN - 1U, fmt, vl); va_end(vl); diff --git a/Makefile b/Makefile index 76b8cc92..54c1c849 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,9 @@ CMD_OBJECTS = \ remote/RemoteCommand.cmd.o \ edac/SHA256.cmd.o \ network/UDPSocket.cmd.o \ - Log.cmd.o + Thread.cmd.o \ + Log.cmd.o \ + Utils.cmd.o .PHONY: dvmhost dvmcmd all strip clean install dpkg tarball diff --git a/Utils.cpp b/Utils.cpp index 249bd564..8ff753ac 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -40,22 +40,6 @@ const uint8_t BITS_TABLE[] = { B6(0), B6(1), B6(1), B6(2) }; -#if defined(ENABLE_DMR) -#define DESCR_DMR "DMR, " -#else -#define DESCR_DMR "" -#endif -#if defined(ENABLE_P25) -#define DESCR_P25 "P25, " -#else -#define DESCR_P25 "" -#endif -#if defined(ENABLE_NXDN) -#define DESCR_NXDN "NXDN, " -#else -#define DESCR_NXDN "" -#endif - // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- diff --git a/Utils.h b/Utils.h index 2535eca9..0e732090 100644 --- a/Utils.h +++ b/Utils.h @@ -30,6 +30,26 @@ #include +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#if defined(ENABLE_DMR) +#define DESCR_DMR "DMR, " +#else +#define DESCR_DMR "" +#endif +#if defined(ENABLE_P25) +#define DESCR_P25 "P25, " +#else +#define DESCR_P25 "" +#endif +#if defined(ENABLE_NXDN) +#define DESCR_NXDN "NXDN, " +#else +#define DESCR_NXDN "" +#endif + // --------------------------------------------------------------------------- // Externs // --------------------------------------------------------------------------- diff --git a/config.example.yml b/config.example.yml index e81b2a9a..55f31563 100644 --- a/config.example.yml +++ b/config.example.yml @@ -10,11 +10,6 @@ network: id: 100000 address: 127.0.0.1 port: 62031 - rconEnable: false - rconAddress: 127.0.0.1 - rconPort: 9990 - rconPassword: "PASSWORD" - rconDebug: false jitter: 360 password: "PASSWORD" slot1: true @@ -24,6 +19,11 @@ network: allowDiagnosticTransfer: true handleChGrants: false debug: false + rconEnable: false + rconAddress: 127.0.0.1 + rconPort: 9990 + rconPassword: "PASSWORD" + rconDebug: false protocols: dmr: enable: true diff --git a/host/Host.cpp b/host/Host.cpp index 1b87a6c7..4a68548f 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -317,11 +317,9 @@ int Host::run() m_tidLookup->read(); // initialize networking - if (m_conf["network"]["enable"].as(false)) { - ret = createNetwork(); - if (!ret) - return EXIT_FAILURE; - } + ret = createNetwork(); + if (!ret) + return EXIT_FAILURE; // set CW parameters if (systemConf["cwId"]["enable"].as(false)) { @@ -2236,7 +2234,13 @@ bool Host::createModem() /// bool Host::createNetwork() { + // dump out if both networking and RCON are disabled + if (m_conf["network"]["enable"].as(false) && m_conf["network"]["rconEnable"].as(false)) { + return true; + } + yaml::Node networkConf = m_conf["network"]; + bool netEnable = networkConf["enable"].as(false); std::string address = networkConf["address"].as(); uint16_t port = (uint16_t)networkConf["port"].as(TRAFFIC_DEFAULT_PORT); uint16_t local = (uint16_t)networkConf["local"].as(0U); @@ -2266,59 +2270,65 @@ bool Host::createNetwork() IdenTable entry = m_idenTable->find(m_channelId); LogInfo("Network Parameters"); - LogInfo(" Peer Id: %u", id); - LogInfo(" Address: %s", address.c_str()); - LogInfo(" Port: %u", port); - if (local > 0U) - LogInfo(" Local: %u", local); - else - LogInfo(" Local: random"); + LogInfo(" Enabled: %s", netEnable ? "yes" : "no"); + if (netEnable) { + LogInfo(" Peer Id: %u", id); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + if (local > 0U) + LogInfo(" Local: %u", local); + else + LogInfo(" Local: random"); + LogInfo(" DMR Jitter: %ums", jitter); + LogInfo(" Slot 1: %s", slot1 ? "enabled" : "disabled"); + LogInfo(" Slot 2: %s", slot2 ? "enabled" : "disabled"); + LogInfo(" Allow Activity Log Transfer: %s", allowActivityTransfer ? "yes" : "no"); + LogInfo(" Allow Diagnostic Log Transfer: %s", allowDiagnosticTransfer ? "yes" : "no"); + LogInfo(" Update Lookups: %s", updateLookup ? "yes" : "no"); + LogInfo(" Handle Channel Grants: %s", handleChGrants ? "yes" : "no"); + + if (debug) { + LogInfo(" Debug: yes"); + } + } LogInfo(" RCON Enabled: %s", rconEnable ? "yes" : "no"); if (rconEnable) { LogInfo(" RCON Address: %s", rconAddress.c_str()); LogInfo(" RCON Port: %u", rconPort); - } - if (rconDebug) { - LogInfo(" RCON Debug: yes"); + if (rconDebug) { + LogInfo(" RCON Debug: yes"); + } } - LogInfo(" DMR Jitter: %ums", jitter); - LogInfo(" Slot 1: %s", slot1 ? "enabled" : "disabled"); - LogInfo(" Slot 2: %s", slot2 ? "enabled" : "disabled"); - LogInfo(" Allow Activity Log Transfer: %s", allowActivityTransfer ? "yes" : "no"); - LogInfo(" Allow Diagnostic Log Transfer: %s", allowDiagnosticTransfer ? "yes" : "no"); - LogInfo(" Update Lookups: %s", updateLookup ? "yes" : "no"); - LogInfo(" Handle Channel Grants: %s", handleChGrants ? "yes" : "no"); - if (debug) { - LogInfo(" Debug: yes"); - } + // initialize networking + if (netEnable) { + m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, handleChGrants); - m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, handleChGrants); + m_network->setLookups(m_ridLookup, m_tidLookup); + m_network->setMetadata(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_channelId, m_channelNo, + m_power, m_latitude, m_longitude, m_height, m_location); + if (rconEnable) { + m_network->setRconData(rconPassword, rconPort); + } - m_network->setLookups(m_ridLookup, m_tidLookup); - m_network->setMetadata(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_channelId, m_channelNo, - m_power, m_latitude, m_longitude, m_height, m_location); - if (rconEnable) { - m_network->setRconData(rconPassword, rconPort); - } + bool ret = m_network->open(); + if (!ret) { + delete m_network; + m_network = NULL; + LogError(LOG_HOST, "failed to initialize traffic networking!"); + return false; + } - bool ret = m_network->open(); - if (!ret) { - delete m_network; - m_network = NULL; - LogError(LOG_HOST, "failed to initialize traffic networking!"); - return false; + m_network->enable(true); + ::LogSetNetwork(m_network); } - m_network->enable(true); - ::LogSetNetwork(m_network); - // initialize network remote command if (rconEnable) { m_remoteControl = new RemoteControl(rconAddress, rconPort, rconPassword, rconDebug); m_remoteControl->setLookups(m_ridLookup, m_tidLookup); - ret = m_remoteControl->open(); + bool ret = m_remoteControl->open(); if (!ret) { delete m_remoteControl; m_remoteControl = NULL; diff --git a/network/RemoteControl.cpp b/network/RemoteControl.cpp index 30583f5b..1344b65a 100644 --- a/network/RemoteControl.cpp +++ b/network/RemoteControl.cpp @@ -34,6 +34,7 @@ #include "p25/Control.h" #include "nxdn/Control.h" #include "host/Host.h" +#include "network/UDPSocket.h" #include "RemoteControl.h" #include "HostMain.h" #include "Log.h" @@ -47,14 +48,21 @@ using namespace modem; #include #include +#include +#include + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- #define BAD_CMD_STR "Bad or invalid remote command." +#define INVALID_AUTH_STR "Invalid authentication" #define INVALID_OPT_STR "Invalid command arguments: " #define CMD_FAILED_STR "Remote command failed: " +#define RCD_GET_VERSION "version" +#define RCD_GET_HELP "help" + #define RCD_MODE_CMD "mdm-mode" #define RCD_MODE_OPT_IDLE "idle" #define RCD_MODE_OPT_LCKOUT "lockout" @@ -71,10 +79,6 @@ using namespace modem; #define RCD_P25_CC_CMD "p25-cc" #define RCD_P25_CC_FALLBACK "p25-cc-fallback" -#define RCD_DMRD_MDM_INJ_CMD "dmrd-mdm-inj" -#define RCD_P25D_MDM_INJ_CMD "p25d-mdm-inj" -#define RCD_NXDD_MDM_INJ_CMD "nxdd-mdm-inj" - #define RCD_DMR_RID_PAGE_CMD "dmr-rid-page" #define RCD_DMR_RID_CHECK_CMD "dmr-rid-check" #define RCD_DMR_RID_INHIBIT_CMD "dmr-rid-inhibit" @@ -104,10 +108,35 @@ using namespace modem; #define RCD_P25_DUMP_TSBK "p25-dump-tsbk" #define RCD_NXDN_DEBUG "nxdn-debug" -const uint32_t START_OF_TEXT = 0x02; -const uint32_t REC_SEPARATOR = 0x1E; +#define RCD_DMRD_MDM_INJ_CMD "debug-dmrd-mdm-inj" +#define RCD_P25D_MDM_INJ_CMD "debug-p25d-mdm-inj" +#define RCD_NXDD_MDM_INJ_CMD "debug-nxdd-mdm-inj" + +const uint8_t RCON_FRAME_START = 0xFEU; +const uint8_t START_OF_TEXT = 0x02U; +const uint8_t END_OF_TEXT = 0x03U; +const uint8_t END_OF_BLOCK = 0x17U; +const uint8_t REC_SEPARATOR = 0x1EU; -const uint32_t RC_BUFFER_LENGTH = 140U; +const uint32_t RC_BUFFER_LENGTH = 250U; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +template +std::string string_format(const std::string& format, FormatArgs ... args) +{ + int size_s = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; // extra space for '\0' + if (size_s <= 0) + throw std::runtime_error("Error during string formatting."); + + auto size = static_cast(size_s); + std::unique_ptr buf(new char[ size ]); + std::snprintf(buf.get(), size, format.c_str(), args ...); + + return std::string(buf.get(), buf.get() + size - 1); +} // --------------------------------------------------------------------------- // Public Class Members @@ -177,47 +206,59 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx args.clear(); uint8_t buffer[RC_BUFFER_LENGTH]; + std::string reply = "OK"; sockaddr_storage address; uint32_t addrLen; - uint32_t ret = m_socket.read((uint8_t*)buffer, RC_BUFFER_LENGTH, address, addrLen); - if (ret > 0U) { - buffer[ret] = '\0'; + uint32_t len = m_socket.read((uint8_t*)buffer, RC_BUFFER_LENGTH, address, addrLen); + if (len > 0U) { + buffer[len] = '\0'; if (m_debug) - Utils::dump(1U, "RCON Received", (uint8_t*)buffer, ret); + Utils::dump(1U, "RCON Received", (uint8_t*)buffer, len); // make sure this is an RCON command - if (buffer[0U] != START_OF_TEXT) { + if (buffer[0U] != RCON_FRAME_START) { + LogWarning(LOG_RCON, BAD_CMD_STR); + return; + } + + if (buffer[1U] != START_OF_TEXT) { LogWarning(LOG_RCON, BAD_CMD_STR); return; } - // ensure we have at least 33 bytes - if (ret < 33U) { + // ensure we have at least 34 bytes + if (len < 34U) { LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; + writeResponse(reply, address, addrLen); return; } if (m_passwordHash != NULL) { uint8_t hash[32U]; ::memset(hash, 0x00U, 32U); - if (::memcmp(m_passwordHash, buffer + 1U, 32U) != 0) { - LogError(LOG_RCON, CMD_FAILED_STR "Invalid authentication!"); + if (::memcmp(m_passwordHash, buffer + 2U, 32U) != 0) { + LogError(LOG_RCON, CMD_FAILED_STR INVALID_AUTH_STR " from %s", UDPSocket::address(address).c_str()); + reply = INVALID_AUTH_STR; + writeResponse(reply, address, addrLen); return; } } // make sure we have arguments after the hash - if ((buffer[33U] != REC_SEPARATOR) || (ret - 34U) <= 0U) { + if ((buffer[34U] != REC_SEPARATOR) || (len - 35U) <= 0U) { LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; + writeResponse(reply, address, addrLen); return; } - uint32_t size = ret - 34U; + uint32_t size = len - 36U; char argBuffer[RC_BUFFER_LENGTH]; ::memset(argBuffer, 0x00U, RC_BUFFER_LENGTH); - ::memcpy(argBuffer, buffer + 34U, size); + ::memcpy(argBuffer, buffer + 35U, size); // parse the original command into a vector of strings. char* b = argBuffer; @@ -230,36 +271,48 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx if (args.size() < 1 || args.at(0U) == "") { args.clear(); LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; } else { std::string rcom = args.at(0); uint32_t argCnt = args.size() - 1; - LogInfoEx(LOG_RCON, "RCON %s, argCnt = %u", rcom.c_str(), argCnt); + LogInfoEx(LOG_RCON, "RCON %s, argCnt = %u from %s", rcom.c_str(), argCnt, UDPSocket::address(address).c_str()); // process command - if (rcom == RCD_MODE_CMD && argCnt >= 1U) { + if (rcom == RCD_GET_VERSION) { + reply = (__PROG_NAME__ " " __VER__ " (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built " __BUILD__ ")"); + } + else if (rcom == RCD_GET_HELP) { + reply = displayHelp(); + } + else if (rcom == RCD_MODE_CMD && argCnt >= 1U) { std::string mode = getArgString(args, 0U); // Command is in the form of: "mode " if (mode == RCD_MODE_OPT_IDLE) { host->m_fixedMode = false; host->setState(STATE_IDLE); - LogInfoEx(LOG_RCON, "Dynamic mode, mode %u", host->m_state); + + reply = string_format("Dynamic mode, mode %u", host->m_state); + LogInfoEx(LOG_RCON, reply.c_str()); } else if (mode == RCD_MODE_OPT_LCKOUT) { host->m_fixedMode = false; host->setState(HOST_STATE_LOCKOUT); - LogInfoEx(LOG_RCON, "Lockout mode, mode %u", host->m_state); + reply = string_format("Lockout mode, mode %u", host->m_state); + LogInfoEx(LOG_RCON, reply.c_str()); } #if defined(ENABLE_DMR) else if (mode == RCD_MODE_OPT_FDMR) { if (dmr != NULL) { host->m_fixedMode = true; host->setState(STATE_DMR); - LogInfoEx(LOG_RCON, "Fixed mode, mode %u", host->m_state); + reply = string_format("Fixed mode, mode %u", host->m_state); + LogInfoEx(LOG_RCON, reply.c_str()); } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_DMR) @@ -268,10 +321,12 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx if (p25 != NULL) { host->m_fixedMode = true; host->setState(STATE_P25); - LogInfoEx(LOG_RCON, "Fixed mode, mode %u", host->m_state); + reply = string_format("Fixed mode, mode %u", host->m_state); + LogInfoEx(LOG_RCON, reply.c_str()); } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_P25) @@ -280,13 +335,19 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx if (p25 != NULL) { host->m_fixedMode = true; host->setState(STATE_NXDN); - LogInfoEx(LOG_RCON, "Fixed mode, mode %u", host->m_state); + reply = string_format("Fixed mode, mode %u", host->m_state); + LogInfoEx(LOG_RCON, reply.c_str()); } else { - LogError(LOG_RCON, CMD_FAILED_STR "NXDN mode is not enabled!"); + reply = CMD_FAILED_STR "NXDN mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_NXDN) + else { + reply = INVALID_OPT_STR "invalid mode!"; + LogError(LOG_RCON, reply.c_str()); + } } else if (rcom == RCD_KILL_CMD) { // Command is in the form of: "kill" @@ -300,7 +361,8 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx m_ridLookup->toggleEntry(srcId, true); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to whitelist RID 0!"); + reply = INVALID_OPT_STR "tried to whitelist RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_RID_BLIST_CMD && argCnt >= 1U) { @@ -310,7 +372,8 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx m_ridLookup->toggleEntry(srcId, false); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to blacklist RID 0!"); + reply = INVALID_OPT_STR "tried to blacklist RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } #if defined(ENABLE_DMR) @@ -321,11 +384,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx g_fireDMRBeacon = true; } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR beacons is not enabled!"); + reply = CMD_FAILED_STR "DMR beacons is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_DMR) @@ -337,11 +402,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx g_fireP25Control = true; } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 control data is not enabled!"); + reply = CMD_FAILED_STR "P25 control data is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_CC_FALLBACK) { @@ -352,191 +419,16 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->setConvFallback((fallback == 1U) ? true : false); } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 control data is not enabled!"); + reply = CMD_FAILED_STR "P25 control data is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_P25) -#if defined(ENABLE_DMR) - else if (rcom == RCD_DMRD_MDM_INJ_CMD && argCnt >= 1U) { - // Command is in the form of: "dmrd-mdm-inj - if (dmr != NULL) { - uint8_t slot = getArgUInt32(args, 0U); - std::string argString = getArgString(args, 1U); - const char* fileName = argString.c_str(); - if (fileName != NULL) { - FILE* file = ::fopen(fileName, "r"); - if (file != NULL) { - uint8_t* buffer = NULL; - int32_t fileSize = 0; - - // obtain file size - ::fseek(file, 0, SEEK_END); - fileSize = ::ftell(file); - ::rewind(file); - - // allocate a buffer and read file - buffer = new uint8_t[fileSize]; - if (buffer != NULL) { - int32_t bytes = ::fread(buffer, 1U, fileSize, file); - if (bytes == fileSize) { - uint8_t sync[dmr::DMR_SYNC_LENGTH_BYTES]; - ::memcpy(sync, buffer, dmr::DMR_SYNC_LENGTH_BYTES); - - // count data sync errors - uint8_t dataErrs = 0U; - for (uint8_t i = 0U; i < dmr::DMR_SYNC_LENGTH_BYTES; i++) - dataErrs += Utils::countBits8(sync[i] ^ dmr::DMR_MS_DATA_SYNC_BYTES[i]); - - // count voice sync errors - uint8_t voiceErrs = 0U; - for (uint8_t i = 0U; i < dmr::DMR_SYNC_LENGTH_BYTES; i++) - voiceErrs += Utils::countBits8(sync[i] ^ dmr::DMR_MS_VOICE_SYNC_BYTES[i]); - - if ((dataErrs <= 4U) || (voiceErrs <= 4U)) { - if (slot == 0U) { - host->m_modem->injectDMRData1(buffer, fileSize); - } - else if (slot == 1U) { - host->m_modem->injectDMRData2(buffer, fileSize); - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "invalid DMR slot!"); - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR data has too many errors!"); - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR failed to open DMR data!"); - } - - delete[] buffer; - } - - ::fclose(file); - } - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); - } - } -#endif // defined(ENABLE_DMR) -#if defined(ENABLE_P25) - else if (rcom == RCD_P25D_MDM_INJ_CMD && argCnt >= 1U) { - // Command is in the form of: "p25d-mdm-inj - if (p25 != NULL) { - std::string argString = getArgString(args, 0U); - const char* fileName = argString.c_str(); - if (fileName != NULL) { - FILE* file = ::fopen(fileName, "r"); - if (file != NULL) { - uint8_t* buffer = NULL; - int32_t fileSize = 0; - - // obtain file size - ::fseek(file, 0, SEEK_END); - fileSize = ::ftell(file); - ::rewind(file); - - // allocate a buffer and read file - buffer = new uint8_t[fileSize]; - if (buffer != NULL) { - int32_t bytes = ::fread(buffer, 1U, fileSize, file); - if (bytes == fileSize) { - uint8_t sync[p25::P25_SYNC_LENGTH_BYTES]; - ::memcpy(sync, buffer, p25::P25_SYNC_LENGTH_BYTES); - - uint8_t errs = 0U; - for (uint8_t i = 0U; i < p25::P25_SYNC_LENGTH_BYTES; i++) - errs += Utils::countBits8(sync[i] ^ p25::P25_SYNC_BYTES[i]); - - if (errs <= 4U) { - bool valid = p25->nid().decode(buffer); - if (valid) { - host->m_modem->injectP25Data(buffer, fileSize); - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 data did not contain a valid NID!"); - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 data has too many errors!"); - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 failed to open P25 data!"); - } - - delete[] buffer; - } - - ::fclose(file); - } - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); - } - } -#endif // defined(ENABLE_P25) -#if defined(ENABLE_NXDN) - else if (rcom == RCD_NXDD_MDM_INJ_CMD && argCnt >= 1U) { - // Command is in the form of: "nxdd-mdm-inj - if (p25 != NULL) { - std::string argString = getArgString(args, 0U); - const char* fileName = argString.c_str(); - if (fileName != NULL) { - FILE* file = ::fopen(fileName, "r"); - if (file != NULL) { - uint8_t* buffer = NULL; - int32_t fileSize = 0; - - // obtain file size - ::fseek(file, 0, SEEK_END); - fileSize = ::ftell(file); - ::rewind(file); - - // allocate a buffer and read file - buffer = new uint8_t[fileSize]; - if (buffer != NULL) { - int32_t bytes = ::fread(buffer, 1U, fileSize, file); - if (bytes == fileSize) { - uint8_t sync[nxdn::NXDN_FSW_BYTES_LENGTH]; - ::memcpy(sync, buffer, nxdn::NXDN_FSW_BYTES_LENGTH); - - uint8_t errs = 0U; - for (uint8_t i = 0U; i < nxdn::NXDN_FSW_BYTES_LENGTH; i++) - errs += Utils::countBits8(sync[i] ^ nxdn::NXDN_FSW_BYTES[i]); - - if (errs <= 4U) { - host->m_modem->injectNXDNData(buffer, fileSize); - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "NXDN data has too many errors!"); - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "NXDN failed to open NXDN data!"); - } - - delete[] buffer; - } - - ::fclose(file); - } - } - } - else { - LogError(LOG_RCON, CMD_FAILED_STR "NXDN mode is not enabled!"); - } - } -#endif // defined(ENABLE_NXDN) #if defined(ENABLE_DMR) else if (rcom == RCD_DMR_RID_PAGE_CMD && argCnt >= 2U) { // Command is in the form of: "dmr-rid-page " @@ -548,15 +440,18 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx dmr->writeRF_Call_Alrt(slotNo, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR call alert RID 0!"); + reply = INVALID_OPT_STR "tried to DMR call alert RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for call alert!"); + reply = INVALID_OPT_STR "invalid DMR slot number for call alert!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_DMR_RID_CHECK_CMD && argCnt >= 2U) { @@ -569,15 +464,18 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx dmr->writeRF_Ext_Func(slotNo, dmr::DMR_EXT_FNCT_CHECK, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR radio check RID 0!"); + reply = INVALID_OPT_STR "tried to DMR radio check RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for radio check!"); + reply = INVALID_OPT_STR "invalid DMR slot number for radio check!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_DMR_RID_INHIBIT_CMD && argCnt >= 2U) { @@ -590,15 +488,18 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx dmr->writeRF_Ext_Func(slotNo, dmr::DMR_EXT_FNCT_INHIBIT, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR radio inhibit RID 0!"); + reply = INVALID_OPT_STR "tried to DMR radio inhibit RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for radio inhibit!"); + reply = INVALID_OPT_STR "invalid DMR slot number for radio inhibit!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_DMR_RID_UNINHIBIT_CMD && argCnt >= 2U) { @@ -611,15 +512,18 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx dmr->writeRF_Ext_Func(slotNo, dmr::DMR_EXT_FNCT_UNINHIBIT, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR radio uninhibit RID 0!"); + reply = INVALID_OPT_STR "tried to DMR radio uninhibit RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for radio uninhibit!"); + reply = INVALID_OPT_STR "invalid DMR slot number for radio uninhibit!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_DMR) @@ -638,7 +542,8 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RID_PAGE_CMD && argCnt >= 1U) { @@ -650,11 +555,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_Call_Alrt(p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 call alert RID 0!"); + reply = INVALID_OPT_STR "tried to P25 call alert RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RID_CHECK_CMD && argCnt >= 1U) { @@ -666,11 +573,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_Ext_Func(p25::P25_EXT_FNCT_CHECK, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 radio check RID 0!"); + reply = INVALID_OPT_STR "tried to P25 radio check RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RID_INHIBIT_CMD && argCnt >= 1U) { @@ -682,11 +591,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_Ext_Func(p25::P25_EXT_FNCT_INHIBIT, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 inhibit RID 0!"); + reply = INVALID_OPT_STR "tried to P25 inhibit RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RID_UNINHIBIT_CMD && argCnt >= 1U) { @@ -698,11 +609,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_Ext_Func(p25::P25_EXT_FNCT_UNINHIBIT, p25::P25_WUID_FNE, dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 uninhibit RID 0!"); + reply = INVALID_OPT_STR "tried to P25 uninhibit RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RID_GAQ_CMD && argCnt >= 1U) { @@ -714,11 +627,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_Grp_Aff_Q(dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 grp aff. query RID 0!"); + reply = INVALID_OPT_STR "tried to P25 grp aff. query RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RID_UREG_CMD && argCnt >= 1U) { @@ -730,11 +645,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_U_Reg_Cmd(dstId); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 unit reg. command RID 0!"); + reply = INVALID_OPT_STR "tried to P25 unit reg. command RID 0!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_PATCH_CMD && argCnt >= 1U) { @@ -749,11 +666,13 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->trunk()->writeRF_TSDU_Mot_Patch(group1, group2, group3); } else { - LogError(LOG_RCON, INVALID_OPT_STR "tried to add P25 group patch with no TGID?"); + reply = INVALID_OPT_STR "tried to add P25 group patch with no TGID?"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RELEASE_GRANTS_CMD) { @@ -762,7 +681,8 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->affiliations().releaseGrant(0, true); } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_RELEASE_AFFS_CMD) { @@ -778,7 +698,8 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_P25) @@ -788,29 +709,35 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx if (dmr != NULL) { if (host->m_dmrTSCCData) { if (p25 != NULL) { - LogError(LOG_RCON, CMD_FAILED_STR "Can't enable DMR control channel while P25 is enabled!"); + reply = CMD_FAILED_STR "Can't enable DMR control channel while P25 is enabled!"; + LogError(LOG_RCON, reply.c_str()); } else { host->m_dmrCtrlChannel = !host->m_dmrCtrlChannel; - LogInfoEx(LOG_RCON, "DMR CC is %s", host->m_p25CtrlChannel ? "enabled" : "disabled"); + reply = string_format("DMR CC is %s", host->m_p25CtrlChannel ? "enabled" : "disabled"); + LogInfoEx(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR control data is not enabled!"); + reply = CMD_FAILED_STR "DMR control data is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_DMR_CC_BCAST_CMD) { // Command is in the form of: "dmr-cc-bcast" if (dmr != NULL) { host->m_dmrTSCCData = !host->m_dmrTSCCData; - LogInfoEx(LOG_RCON, "DMR CC broadcast is %s", host->m_dmrTSCCData ? "enabled" : "disabled"); + reply = string_format("DMR CC broadcast is %s", host->m_dmrTSCCData ? "enabled" : "disabled"); + LogInfoEx(LOG_RCON, reply.c_str()); } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_DMR) @@ -820,7 +747,8 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx if (p25 != NULL) { if (host->m_p25CCData) { if (dmr != NULL) { - LogError(LOG_RCON, CMD_FAILED_STR "Can't enable P25 control channel while DMR is enabled!"); + reply = CMD_FAILED_STR "Can't enable P25 control channel while DMR is enabled!"; + LogError(LOG_RCON, reply.c_str()); } else { host->m_p25CtrlChannel = !host->m_p25CtrlChannel; @@ -828,15 +756,18 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx g_fireP25Control = true; p25->setCCHalted(false); - LogInfoEx(LOG_RCON, "P25 CC is %s", host->m_p25CtrlChannel ? "enabled" : "disabled"); + reply = string_format("P25 CC is %s", host->m_p25CtrlChannel ? "enabled" : "disabled"); + LogInfoEx(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 control data is not enabled!"); + reply = CMD_FAILED_STR "P25 control data is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else if (rcom == RCD_P25_CC_BCAST_CMD) { @@ -854,71 +785,294 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx p25->setCCHalted(false); } - LogInfoEx(LOG_RCON, "P25 CC broadcast is %s", host->m_p25CtrlBroadcast ? "enabled" : "disabled"); + reply = string_format("P25 CC broadcast is %s", host->m_p25CtrlBroadcast ? "enabled" : "disabled"); + LogInfoEx(LOG_RCON, reply.c_str()); } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 control data is not enabled!"); + reply = CMD_FAILED_STR "P25 control data is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_P25) #if defined(ENABLE_DMR) else if (rcom == RCD_DMR_DEBUG) { // Command is in the form of: "dmr-debug " - uint8_t debug = getArgUInt8(args, 0U); - uint8_t verbose = getArgUInt8(args, 1U); - if (dmr != NULL) { - dmr->setDebugVerbose((debug == 1U) ? true : false, (verbose == 1U) ? true : false); - } + if (argCnt < 2U) { + LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; + } else { - LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + uint8_t debug = getArgUInt8(args, 0U); + uint8_t verbose = getArgUInt8(args, 1U); + if (dmr != NULL) { + dmr->setDebugVerbose((debug == 1U) ? true : false, (verbose == 1U) ? true : false); + } + else { + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); + } } } #endif // defined(ENABLE_DMR) #if defined(ENABLE_P25) else if (rcom == RCD_P25_DEBUG) { // Command is in the form of: "p25-debug " - uint8_t debug = getArgUInt8(args, 0U); - uint8_t verbose = getArgUInt8(args, 1U); - if (p25 != NULL) { - p25->setDebugVerbose((debug == 1U) ? true : false, (verbose == 1U) ? true : false); - } + if (argCnt < 2U) { + LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; + } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + uint8_t debug = getArgUInt8(args, 0U); + uint8_t verbose = getArgUInt8(args, 1U); + if (p25 != NULL) { + p25->setDebugVerbose((debug == 1U) ? true : false, (verbose == 1U) ? true : false); + } + else { + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); + } } } else if (rcom == RCD_P25_DUMP_TSBK) { // Command is in the form of: "p25-dump-tsbk 0/1" - uint8_t verbose = getArgUInt8(args, 0U); - if (p25 != NULL) { - p25->trunk()->setTSBKVerbose((verbose == 1U) ? true : false); - } + if (argCnt < 1U) { + LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; + } else { - LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + uint8_t verbose = getArgUInt8(args, 0U); + if (p25 != NULL) { + p25->trunk()->setTSBKVerbose((verbose == 1U) ? true : false); + } + else { + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); + } } } #endif // defined(ENABLE_P25) #if defined(ENABLE_NXDN) else if (rcom == RCD_NXDN_DEBUG) { // Command is in the form of: "nxdn-debug " - uint8_t debug = getArgUInt8(args, 0U); - uint8_t verbose = getArgUInt8(args, 1U); - if (nxdn != NULL) { - nxdn->setDebugVerbose((debug == 1U) ? true : false, (verbose == 1U) ? true : false); + if (argCnt < 2U) { + LogWarning(LOG_RCON, BAD_CMD_STR); + reply = BAD_CMD_STR; + } + else { + uint8_t debug = getArgUInt8(args, 0U); + uint8_t verbose = getArgUInt8(args, 1U); + if (nxdn != NULL) { + nxdn->setDebugVerbose((debug == 1U) ? true : false, (verbose == 1U) ? true : false); + } + else { + reply = CMD_FAILED_STR "NXDN mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); + } + } + } +#endif // defined(ENABLE_NXDN) +#if defined(ENABLE_DMR) + else if (rcom == RCD_DMRD_MDM_INJ_CMD && argCnt >= 1U) { + // Command is in the form of: "debug-dmrd-mdm-inj + if (dmr != NULL) { + uint8_t slot = getArgUInt32(args, 0U); + std::string argString = getArgString(args, 1U); + const char* fileName = argString.c_str(); + if (fileName != NULL) { + FILE* file = ::fopen(fileName, "r"); + if (file != NULL) { + uint8_t* buffer = NULL; + int32_t fileSize = 0; + + // obtain file size + ::fseek(file, 0, SEEK_END); + fileSize = ::ftell(file); + ::rewind(file); + + // allocate a buffer and read file + buffer = new uint8_t[fileSize]; + if (buffer != NULL) { + int32_t bytes = ::fread(buffer, 1U, fileSize, file); + if (bytes == fileSize) { + uint8_t sync[dmr::DMR_SYNC_LENGTH_BYTES]; + ::memcpy(sync, buffer, dmr::DMR_SYNC_LENGTH_BYTES); + + // count data sync errors + uint8_t dataErrs = 0U; + for (uint8_t i = 0U; i < dmr::DMR_SYNC_LENGTH_BYTES; i++) + dataErrs += Utils::countBits8(sync[i] ^ dmr::DMR_MS_DATA_SYNC_BYTES[i]); + + // count voice sync errors + uint8_t voiceErrs = 0U; + for (uint8_t i = 0U; i < dmr::DMR_SYNC_LENGTH_BYTES; i++) + voiceErrs += Utils::countBits8(sync[i] ^ dmr::DMR_MS_VOICE_SYNC_BYTES[i]); + + if ((dataErrs <= 4U) || (voiceErrs <= 4U)) { + if (slot == 0U) { + host->m_modem->injectDMRData1(buffer, fileSize); + } + else if (slot == 1U) { + host->m_modem->injectDMRData2(buffer, fileSize); + } + else { + reply = CMD_FAILED_STR "invalid DMR slot!"; + LogError(LOG_RCON, reply.c_str()); + } + } + else { + reply = CMD_FAILED_STR "DMR data has too many errors!"; + LogError(LOG_RCON, reply.c_str()); + } + } + else { + reply = CMD_FAILED_STR "DMR failed to open DMR data!"; + LogError(LOG_RCON, reply.c_str()); + } + + delete[] buffer; + } + + ::fclose(file); + } + } } else { - LogError(LOG_RCON, CMD_FAILED_STR "NXDN mode is not enabled!"); + reply = CMD_FAILED_STR "DMR mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); + } + } +#endif // defined(ENABLE_DMR) +#if defined(ENABLE_P25) + else if (rcom == RCD_P25D_MDM_INJ_CMD && argCnt >= 1U) { + // Command is in the form of: "debug-p25d-mdm-inj + if (p25 != NULL) { + std::string argString = getArgString(args, 0U); + const char* fileName = argString.c_str(); + if (fileName != NULL) { + FILE* file = ::fopen(fileName, "r"); + if (file != NULL) { + uint8_t* buffer = NULL; + int32_t fileSize = 0; + + // obtain file size + ::fseek(file, 0, SEEK_END); + fileSize = ::ftell(file); + ::rewind(file); + + // allocate a buffer and read file + buffer = new uint8_t[fileSize]; + if (buffer != NULL) { + int32_t bytes = ::fread(buffer, 1U, fileSize, file); + if (bytes == fileSize) { + uint8_t sync[p25::P25_SYNC_LENGTH_BYTES]; + ::memcpy(sync, buffer, p25::P25_SYNC_LENGTH_BYTES); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < p25::P25_SYNC_LENGTH_BYTES; i++) + errs += Utils::countBits8(sync[i] ^ p25::P25_SYNC_BYTES[i]); + + if (errs <= 4U) { + bool valid = p25->nid().decode(buffer); + if (valid) { + host->m_modem->injectP25Data(buffer, fileSize); + } + else { + reply = CMD_FAILED_STR "P25 data did not contain a valid NID!"; + LogError(LOG_RCON, reply.c_str()); + } + } + else { + reply = CMD_FAILED_STR "P25 data has too many errors!"; + LogError(LOG_RCON, reply.c_str()); + } + } + else { + reply = CMD_FAILED_STR "P25 failed to open P25 data!"; + LogError(LOG_RCON, reply.c_str()); + } + + delete[] buffer; + } + + ::fclose(file); + } + } + } + else { + reply = CMD_FAILED_STR "P25 mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); + } + } +#endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + else if (rcom == RCD_NXDD_MDM_INJ_CMD && argCnt >= 1U) { + // Command is in the form of: "debug-nxdd-mdm-inj + if (p25 != NULL) { + std::string argString = getArgString(args, 0U); + const char* fileName = argString.c_str(); + if (fileName != NULL) { + FILE* file = ::fopen(fileName, "r"); + if (file != NULL) { + uint8_t* buffer = NULL; + int32_t fileSize = 0; + + // obtain file size + ::fseek(file, 0, SEEK_END); + fileSize = ::ftell(file); + ::rewind(file); + + // allocate a buffer and read file + buffer = new uint8_t[fileSize]; + if (buffer != NULL) { + int32_t bytes = ::fread(buffer, 1U, fileSize, file); + if (bytes == fileSize) { + uint8_t sync[nxdn::NXDN_FSW_BYTES_LENGTH]; + ::memcpy(sync, buffer, nxdn::NXDN_FSW_BYTES_LENGTH); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < nxdn::NXDN_FSW_BYTES_LENGTH; i++) + errs += Utils::countBits8(sync[i] ^ nxdn::NXDN_FSW_BYTES[i]); + + if (errs <= 4U) { + host->m_modem->injectNXDNData(buffer, fileSize); + } + else { + reply = CMD_FAILED_STR "NXDN data has too many errors!"; + LogError(LOG_RCON, reply.c_str()); + } + } + else { + reply = CMD_FAILED_STR "NXDN failed to open NXDN data!"; + LogError(LOG_RCON, reply.c_str()); + } + + delete[] buffer; + } + + ::fclose(file); + } + } + } + else { + reply = CMD_FAILED_STR "NXDN mode is not enabled!"; + LogError(LOG_RCON, reply.c_str()); } } #endif // defined(ENABLE_NXDN) else { args.clear(); + reply = BAD_CMD_STR; LogError(LOG_RCON, BAD_CMD_STR " (\"%s\")", rcom.c_str()); } } + + // write response + writeResponse(reply, address, addrLen); } } @@ -943,6 +1097,129 @@ void RemoteControl::close() // Private Class Members // --------------------------------------------------------------------------- +/// +/// Helper to write response to client. +/// +/// +/// +/// +/// +void RemoteControl::writeResponse(std::string reply, sockaddr_storage address, uint32_t addrLen) +{ + uint8_t buffer[RC_BUFFER_LENGTH]; + ::memset(buffer, 0x00U, RC_BUFFER_LENGTH); + + buffer[0U] = RCON_FRAME_START; + buffer[1U] = START_OF_TEXT; + + LogInfoEx(LOG_RCON, "RCON reply len = %u, blocks = %u to %s", reply.length(), (reply.length() / (RC_BUFFER_LENGTH - 3U)) + 1U, UDPSocket::address(address).c_str()); + + if (reply.length() > (RC_BUFFER_LENGTH - 3U)) { + uint32_t len = reply.length(), offs = 0U; + for (uint32_t i = 0U; i < reply.length() / (RC_BUFFER_LENGTH - 3U); i++) { + if (m_debug) + LogDebug(LOG_RCON, "RemoteControl::writeResponse() block = %u, block len = %u, offs = %u", i, len, offs); + + std::string str = reply.substr(offs, RC_BUFFER_LENGTH - 2U); + + ::memset(buffer + 2U, 0x00U, str.length()); + ::memcpy(buffer + 2U, str.c_str(), str.length()); + + if (len - RC_BUFFER_LENGTH == 0U) { + buffer[str.length() + 1U] = END_OF_TEXT; + } + else { + buffer[str.length() + 1U] = END_OF_BLOCK; + } + + if (m_debug) + Utils::dump(1U, "RCON (Multiblock) Sent", (uint8_t*)buffer, RC_BUFFER_LENGTH); + + m_socket.write(buffer, RC_BUFFER_LENGTH, address, addrLen); + + Thread::sleep(50); + + offs += RC_BUFFER_LENGTH - 3U; + len -= RC_BUFFER_LENGTH - 3U; + } + + if (m_debug) + LogDebug(LOG_RCON, "RemoteControl::writeResponse() remaining block len = %u, offs = %u", len, offs); + + if (len > 0U) { + std::string str = reply.substr(offs, std::string::npos); + + ::memset(buffer + 2U, 0x00U, RC_BUFFER_LENGTH - 2U); + ::memcpy(buffer + 2U, str.c_str(), str.length()); + + buffer[str.length() + 1U] = END_OF_TEXT; + + if (m_debug) + Utils::dump(1U, "RCON (Multiblock) Sent", (uint8_t*)buffer, str.length() + 3U); + + m_socket.write(buffer, str.length() + 3U, address, addrLen); + } + } + else { + ::memcpy(buffer + 2U, reply.c_str(), reply.length()); + buffer[reply.length() + 1U] = END_OF_TEXT; + + if (m_debug) { + LogDebug(LOG_RCON, "RemoteControl::writeResponse() single block len = %u", reply.length() + 3U); + Utils::dump(1U, "RCON Sent", (uint8_t*)buffer, reply.length() + 3U); + } + + m_socket.write(buffer, reply.length() + 3U, address, addrLen); + } +} + +/// +/// Helper to print the remote control help. +/// +std::string RemoteControl::displayHelp() +{ + std::string reply = ""; + + reply += "RCON Help\r\nGeneral Commands:\r\n"; + reply += " version Display current version of host\r\n"; + reply += " mdm-mode Set current mode of host (idle, lockout, dmr, p25, nxdn)\r\n"; + reply += " mdm-kill Causes the host to quit\r\n"; + reply += "\r\n"; + reply += " rid-whitelist Whitelists the specified RID in the host ACL tables\r\n"; + reply += " rid-blacklist Blacklists the specified RID in the host ACL tables\r\n"; + reply += "\r\n"; + reply += " dmr-beacon Transmits a DMR beacon burst\r\n"; + reply += " p25-cc Transmits a non-continous P25 CC burst\r\n"; + reply += " p25-cc-fallback <0/1> Sets the P25 CC into conventional fallback mode\r\n"; + reply += "\r\n"; + reply += " dmr-debug \r\n"; + reply += " p25-debug \r\n"; + reply += " nxdn-debug \r\n"; + reply += "\r\nDMR Commands:\r\n"; + reply += " dmr-rid-page Pages/Calls the specified RID\r\n"; + reply += " dmr-rid-check Radio Checks the specified RID\r\n"; + reply += " dmr-rid-inhibit Inhibits the specified RID\r\n"; + reply += " dmr-rid-uninhibit Uninhibits the specified RID\r\n"; + reply += "\r\n"; + reply += " dmr-cc-dedicated <0/1> Enables or disables dedicated control channel\r\n"; + reply += " dmr-cc-bcast <0/1> Enables or disables broadcast of the control channel\r\n"; + reply += "\r\nP25 Commands:\r\n"; + reply += " p25-set-mfid Sets the P25 MFId for the next sent P25 command\r\n"; + reply += " p25-rid-page Pages/Calls the specified RID\r\n"; + reply += " p25-rid-check Radio Checks the specified RID\r\n"; + reply += " p25-rid-inhibit Inhibits the specified RID\r\n"; + reply += " p25-rid-uninhibit Uninhibits the specified RID\r\n"; + reply += " p25-rid-gaq Group affiliation queries the specified RID\r\n"; + reply += " p25-rid-ureg Demand unit registration for the specified RID\r\n"; + reply += "\r\n"; + reply += " p25-rel-grnts Forcibly releases all channel grants for P25\r\n"; + reply += " p25-rel-affs Forcibly releases all group affiliations for P25\r\n"; + reply += "\r\n"; + reply += " p25-cc-dedicated <0/1> Enables or disables dedicated control channel\r\n"; + reply += " p25-cc-bcast <0/1> Enables or disables broadcast of the control channel\r\n"; + return reply; +} + /// /// /// diff --git a/network/RemoteControl.h b/network/RemoteControl.h index 74fae630..fd8500c3 100644 --- a/network/RemoteControl.h +++ b/network/RemoteControl.h @@ -83,6 +83,12 @@ private: ::lookups::RadioIdLookup* m_ridLookup; ::lookups::TalkgroupIdLookup* m_tidLookup; + /// Helper to send response to client. + void writeResponse(std::string reply, sockaddr_storage address, uint32_t addrLen); + + /// Helper to print the remote control help. + std::string displayHelp(); + /// std::string getArgString(std::vector args, uint32_t n) const; diff --git a/network/UDPSocket.cpp b/network/UDPSocket.cpp index 00586a77..7e126e31 100644 --- a/network/UDPSocket.cpp +++ b/network/UDPSocket.cpp @@ -169,7 +169,7 @@ bool UDPSocket::open(const uint32_t index, const uint32_t af, const std::string& return false; } - LogInfo("Opening UDP port on %u", port); + LogInfoEx(LOG_NET, "Opening UDP port on %u", port); } return true; @@ -394,7 +394,7 @@ int UDPSocket::lookup(const std::string& hostname, uint16_t port, sockaddr_stora paddr->sin_family = AF_INET; paddr->sin_port = htons(port); paddr->sin_addr.s_addr = htonl(INADDR_NONE); - LogError("Cannot find address for host %s", hostname.c_str()); + LogError(LOG_NET, "Cannot find address for host %s", hostname.c_str()); return err; } diff --git a/remote/RemoteCommand.cpp b/remote/RemoteCommand.cpp index 2bdd295d..81a92bbc 100644 --- a/remote/RemoteCommand.cpp +++ b/remote/RemoteCommand.cpp @@ -32,7 +32,9 @@ #include "edac/SHA256.h" #include "network/UDPSocket.h" #include "RemoteCommand.h" +#include "Thread.h" #include "Log.h" +#include "Utils.h" using namespace network; @@ -55,10 +57,14 @@ using namespace network; #define ERRNO_ADDR_LOOKUP 97 #define ERRNO_FAILED_TO_SEND 96 -const uint32_t START_OF_TEXT = 0x02; -const uint32_t REC_SEPARATOR = 0x1E; +const uint8_t RCON_FRAME_START = 0xFEU; +const uint8_t START_OF_TEXT = 0x02U; +const uint8_t END_OF_TEXT = 0x03U; +const uint8_t END_OF_BLOCK = 0x17U; +const uint8_t REC_SEPARATOR = 0x1EU; -const uint32_t RC_BUFFER_LENGTH = 140U; +const uint32_t RC_BUFFER_LENGTH = 250U; +const uint32_t RESPONSE_BUFFER_LEN = 4095U; // --------------------------------------------------------------------------- // Macros @@ -74,6 +80,7 @@ static std::string g_progExe = std::string(__EXE_NAME__); static std::string g_remoteAddress = std::string("127.0.0.1"); static uint32_t g_remotePort = RCON_DEFAULT_PORT; static std::string g_remotePassword = std::string(); +static bool g_debug = false; // --------------------------------------------------------------------------- // Global Functions @@ -101,6 +108,7 @@ void usage(const char* message, const char* arg) " -p remote modem command port\n" " -P remote modem authentication password\n" "\n" + " -d enable debug\n" " -v show version information\n" " -h show this screen\n" " -- stop handling options\n", @@ -156,6 +164,10 @@ int checkArgs(int argc, char* argv[]) p += 2; } + else if (IS("-d")) { + ++p; + g_debug = true; + } else if (IS("-v")) { ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); ::fprintf(stdout, "Copyright (c) 2017-2022 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n"); @@ -215,13 +227,13 @@ int main(int argc, char** argv) } // initialize system logging - bool ret = ::LogInitialise("", "", 0U, 1U); + bool ret = ::LogInitialise("", "", 0U, 1U, true); if (!ret) { ::fprintf(stderr, "unable to open the log file\n"); return 1; } - CRemoteCommand* command = new CRemoteCommand(g_remoteAddress, g_remotePort, g_remotePassword); + RemoteCommand* command = new RemoteCommand(g_remoteAddress, g_remotePort, g_remotePassword); int retCode = command->send(cmd); ::LogFinalise(); @@ -233,12 +245,12 @@ int main(int argc, char** argv) // --------------------------------------------------------------------------- /// -/// Initializes a new instance of the CRemoteCommand class. +/// Initializes a new instance of the RemoteCommand class. /// /// Network Hostname/IP address to connect to. /// Network port number. /// Authentication password. -CRemoteCommand::CRemoteCommand(const std::string& address, uint32_t port, const std::string& password) : +RemoteCommand::RemoteCommand(const std::string& address, uint32_t port, const std::string& password) : m_address(address), m_port(port), m_password(password) @@ -248,9 +260,9 @@ CRemoteCommand::CRemoteCommand(const std::string& address, uint32_t port, const } /// -/// Finalizes a instance of the CRemoteCommand class. +/// Finalizes a instance of the RemoteCommand class. /// -CRemoteCommand::~CRemoteCommand() +RemoteCommand::~RemoteCommand() { /* stub */ } @@ -260,7 +272,7 @@ CRemoteCommand::~CRemoteCommand() /// /// Command string to send to remote modem. /// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE. -int CRemoteCommand::send(const std::string& command) +int RemoteCommand::send(const std::string& command) { UDPSocket socket(0U); @@ -279,10 +291,11 @@ int CRemoteCommand::send(const std::string& command) return ERRNO_ADDR_LOOKUP; } - ::LogInfoEx(LOG_HOST, "%s: sending command \"%s\" to %s:%u\r\n", g_progExe.c_str(), command.c_str(), + ::LogInfoEx(LOG_HOST, "%s: sending command \"%s\" to %s:%u", g_progExe.c_str(), command.c_str(), m_address.c_str(), m_port); - buffer[0U] = START_OF_TEXT; + buffer[0U] = RCON_FRAME_START; + buffer[1U] = START_OF_TEXT; if (!m_password.empty()) { size_t size = m_password.size(); @@ -297,19 +310,70 @@ int CRemoteCommand::send(const std::string& command) edac::SHA256 sha256; sha256.buffer(in, (uint32_t)(size), out); - ::memcpy(buffer + 1U, out, 32U); + ::memcpy(buffer + 2U, out, 32U); } - buffer[33U] = REC_SEPARATOR; - ::memcpy(buffer + 34U, command.c_str(), command.size()); + buffer[34U] = REC_SEPARATOR; + ::memcpy(buffer + 35U, command.c_str(), command.size()); + + buffer[35U + command.size()] = END_OF_TEXT; - ret = socket.write((uint8_t *)buffer, 34U + command.size(), addr, addrLen); + if (g_debug) + Utils::dump(1U, "RCON Sent", (uint8_t*)buffer, 36U + command.size()); + + ret = socket.write((uint8_t *)buffer, 36U + command.size(), addr, addrLen); if (!ret) { socket.close(); - ::LogError(LOG_HOST, "Failed to send command: \"%s\"\r\n", command.c_str()); + ::LogError(LOG_HOST, "Failed to send command: \"%s\"", command.c_str()); return ERRNO_FAILED_TO_SEND; } + Thread::sleep(100); + + uint8_t response[RESPONSE_BUFFER_LEN]; + ::memset(response, 0x00U, RESPONSE_BUFFER_LEN); + + int len = 1; + uint32_t offs = 0U; + do + { + ::memset(buffer, 0x00U, RC_BUFFER_LENGTH); + int len = socket.read((uint8_t *)buffer, RC_BUFFER_LENGTH, addr, addrLen); + if (len <= 1) + break; + + if (offs + len > RESPONSE_BUFFER_LEN) + break; + + if (g_debug) + ::LogDebug(LOG_RCON, "RemoteCommand::send() block len = %u, offs = %u", len - 3, offs); + + buffer[len] = '\0'; + + if (g_debug) + Utils::dump(1U, "RCON Received", (uint8_t*)buffer, len); + + // make sure this is an RCON response + if (buffer[0U] != RCON_FRAME_START) { + ::LogError(LOG_HOST, "Invalid response from host %s:%u", m_address.c_str(), m_port); + socket.close(); + return EXIT_FAILURE; + } + + if (buffer[1U] != START_OF_TEXT) { + ::LogError(LOG_HOST, "Invalid response from host %s:%u", m_address.c_str(), m_port); + socket.close(); + return EXIT_FAILURE; + } + + ::memcpy(response + offs, buffer + 2U, len - 3U); + offs += len - 3U; + + Thread::sleep(100); + } while (buffer[len - 1U] != END_OF_TEXT); + + ::LogInfoEx(LOG_HOST, ">> %s", response); + socket.close(); return EXIT_SUCCESS; } diff --git a/remote/RemoteCommand.h b/remote/RemoteCommand.h index 6a38631e..01bafe52 100644 --- a/remote/RemoteCommand.h +++ b/remote/RemoteCommand.h @@ -40,13 +40,13 @@ // This class implements the core remote command logic. // --------------------------------------------------------------------------- -class HOST_SW_API CRemoteCommand +class HOST_SW_API RemoteCommand { public: - /// Initializes a new instance of the CRemoteCommand class. - CRemoteCommand(const std::string& address, uint32_t port, const std::string& password); - /// Finalizes a instance of the CRemoteCommand class. - ~CRemoteCommand(); + /// Initializes a new instance of the RemoteCommand class. + RemoteCommand(const std::string& address, uint32_t port, const std::string& password); + /// Finalizes a instance of the RemoteCommand class. + ~RemoteCommand(); /// Sends remote control command to the specified modem. int send(const std::string& command);