diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1c8c66..ce779794 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,8 @@ file(GLOB dvmhost_SRC "dmr/lc/*.cpp" "dmr/lc/csbk/*.h" "dmr/lc/csbk/*.cpp" + "dmr/lookups/*.h" + "dmr/lookups/*.cpp" "dmr/packet*.h" "dmr/packet/*.cpp" diff --git a/config.example.yml b/config.example.yml index 53352833..98c3132f 100644 --- a/config.example.yml +++ b/config.example.yml @@ -307,6 +307,10 @@ system: # (By default this should be true, unless you are operating in a mode that will dynamically permit # TGIDs via the permit-tg RCON command.) authoritative: true + # Flag indicating whether or not this host sends permit-tg commands to voice channels for TGID operations. + # (By default this should be false, unless you are operating in a mode that will dynamically permit + # TGIDs via the permit-tg RCON command.) + controlPermitTG: false # Channel Identity (corresponds to the appropriate entry in the iden_table file). channelId: 2 # Channel Number (used to calculate actual host frequency based on the identity table). diff --git a/dmr/Control.cpp b/dmr/Control.cpp index 59093131..b268f2a8 100644 --- a/dmr/Control.cpp +++ b/dmr/Control.cpp @@ -66,7 +66,7 @@ using namespace dmr; /// Flag indicating whether DMR verbose logging is enabled. Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, - lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, + ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) : m_authoritative(authoritative), m_colorCode(colorCode), @@ -110,6 +110,7 @@ Control::~Control() /// Helper to set DMR configuration options. /// /// Instance of the ConfigINI class. +/// /// Voice Channel Number list. /// Voice Channel data map. /// DMR Network ID. @@ -117,12 +118,14 @@ Control::~Control() /// Channel ID. /// Channel Number. /// -void Control::setOptions(yaml::Node& conf, const std::vector voiceChNo, const std::unordered_map voiceChData, +void Control::setOptions(yaml::Node& conf, bool controlPermitTG, const std::vector voiceChNo, const std::unordered_map voiceChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node dmrProtocol = conf["protocols"]["dmr"]; + m_controlPermitTG = controlPermitTG; + Slot::m_verifyReg = dmrProtocol["verifyReg"].as(false); uint8_t nRandWait = (uint8_t)dmrProtocol["nRandWait"].as(dmr::DEFAULT_NRAND_WAIT); @@ -149,9 +152,11 @@ void Control::setOptions(yaml::Node& conf, const std::vector voiceChNo switch (m_tsccSlotNo) { case 1U: m_slot1->setTSCC(enableTSCC, dedicatedTSCC); + m_slot1->setControlPermitTG(m_controlPermitTG); break; case 2U: m_slot2->setTSCC(enableTSCC, dedicatedTSCC); + m_slot2->setControlPermitTG(m_controlPermitTG); break; default: LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); diff --git a/dmr/Control.h b/dmr/Control.h index 4a662c18..d73c11eb 100644 --- a/dmr/Control.h +++ b/dmr/Control.h @@ -63,13 +63,13 @@ namespace dmr /// Initializes a new instance of the Control class. Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, - lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssi, + ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssi, uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose); /// Finalizes a instance of the Control class. ~Control(); /// Helper to set DMR configuration options. - void setOptions(yaml::Node& conf, const std::vector voiceChNo, const std::unordered_map voiceChData, + void setOptions(yaml::Node& conf, bool controlPermitTG, const std::vector voiceChNo, const std::unordered_map voiceChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); /// Gets a flag indicating whether the DMR control channel is running. @@ -121,6 +121,7 @@ namespace dmr friend class Slot; bool m_authoritative; + bool m_controlPermitTG; uint32_t m_colorCode; @@ -130,9 +131,9 @@ namespace dmr Slot* m_slot1; Slot* m_slot2; - lookups::IdenTableLookup* m_idenTable; - lookups::RadioIdLookup* m_ridLookup; - lookups::TalkgroupIdLookup* m_tidLookup; + ::lookups::IdenTableLookup* m_idenTable; + ::lookups::RadioIdLookup* m_ridLookup; + ::lookups::TalkgroupIdLookup* m_tidLookup; uint8_t m_tsccSlotNo; bool m_ccRunning; diff --git a/dmr/Slot.cpp b/dmr/Slot.cpp index ddcbce84..65d8a809 100644 --- a/dmr/Slot.cpp +++ b/dmr/Slot.cpp @@ -71,16 +71,16 @@ network::BaseNetwork* Slot::m_network = nullptr; bool Slot::m_duplex = true; -lookups::IdenTableLookup* Slot::m_idenTable = nullptr; -lookups::RadioIdLookup* Slot::m_ridLookup = nullptr; -lookups::TalkgroupIdLookup* Slot::m_tidLookup = nullptr; -lookups::AffiliationLookup *Slot::m_affiliations = nullptr; +::lookups::IdenTableLookup* Slot::m_idenTable = nullptr; +::lookups::RadioIdLookup* Slot::m_ridLookup = nullptr; +::lookups::TalkgroupIdLookup* Slot::m_tidLookup = nullptr; +dmr::lookups::DMRAffiliationLookup *Slot::m_affiliations = nullptr; -lookups::IdenTable Slot::m_idenEntry = lookups::IdenTable(); +::lookups::IdenTable Slot::m_idenEntry = ::lookups::IdenTable(); uint32_t Slot::m_hangCount = 3U * 17U; -lookups::RSSIInterpolator* Slot::m_rssiMapper = nullptr; +::lookups::RSSIInterpolator* Slot::m_rssiMapper = nullptr; uint32_t Slot::m_jitterTime = 360U; uint32_t Slot::m_jitterSlots = 6U; @@ -574,6 +574,7 @@ void Slot::setTSCC(bool enable, bool dedicated) m_dedicatedTSCC = dedicated; if (m_enableTSCC) { m_modem->setDMRIgnoreCACH_AT(m_slotNo); + m_affiliations->setSlotForChannelTSCC(m_channelNo, m_slotNo); } } @@ -606,8 +607,8 @@ void Slot::setSilenceThreshold(uint32_t threshold) /// /// void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, - network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, - lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose) + network::BaseNetwork* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, + ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose) { assert(dmr != nullptr); assert(modem != nullptr); @@ -635,7 +636,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s m_idenTable = idenTable; m_ridLookup = ridLookup; m_tidLookup = tidLookup; - m_affiliations = new lookups::AffiliationLookup("DMR Affiliations", verbose); + m_affiliations = new dmr::lookups::DMRAffiliationLookup(verbose); m_hangCount = callHang * 17U; @@ -666,15 +667,15 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s /// Channel ID. /// Channel Number. /// -void Slot::setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, +void Slot::setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg) { m_siteData = SiteData(SITE_MODEL_SMALL, netId, siteId, 3U, requireReg); m_channelNo = channelNo; - std::vector entries = m_idenTable->list(); + std::vector<::lookups::IdenTable> entries = m_idenTable->list(); for (auto it = entries.begin(); it != entries.end(); ++it) { - lookups::IdenTable entry = *it; + ::lookups::IdenTable entry = *it; if (entry.channelId() == channelId) { m_idenEntry = entry; break; diff --git a/dmr/Slot.h b/dmr/Slot.h index 8354a1c6..2aff50a5 100644 --- a/dmr/Slot.h +++ b/dmr/Slot.h @@ -34,6 +34,7 @@ #include "Defines.h" #include "dmr/Control.h" #include "dmr/SiteData.h" +#include "dmr/lookups/DMRAffiliationLookup.h" #include "dmr/packet/ControlSignaling.h" #include "dmr/packet/Data.h" #include "dmr/packet/Voice.h" @@ -43,7 +44,6 @@ #include "lookups/IdenTableLookup.h" #include "lookups/RadioIdLookup.h" #include "lookups/TalkgroupIdLookup.h" -#include "lookups/AffiliationLookup.h" #include "RingBuffer.h" #include "StopWatch.h" #include "Timer.h" @@ -105,15 +105,17 @@ namespace dmr /// Helper to enable and configure TSCC support for this slot. void setTSCC(bool enable, bool dedicated); + /// Sets a flag indicating whether the DMR control channel can send permit-tg to voice channels. + void setControlPermitTG(bool controlPermitTG) { m_controlPermitTG = controlPermitTG; } /// Helper to set the voice error silence threshold. void setSilenceThreshold(uint32_t threshold); /// Helper to initialize the slot processor. static void init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, - network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, - lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose); + network::BaseNetwork* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, + ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose); /// Sets local configured site data. - static void setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, + static void setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq); /// Sets TSCC Aloha configuration. static void setAlohaConfig(uint8_t nRandWait, uint8_t backOff); @@ -188,6 +190,8 @@ namespace dmr bool m_enableTSCC; bool m_dedicatedTSCC; + bool m_controlPermitTG; + bool m_verbose; bool m_debug; @@ -208,16 +212,16 @@ namespace dmr static bool m_duplex; - static lookups::IdenTableLookup* m_idenTable; - static lookups::RadioIdLookup* m_ridLookup; - static lookups::TalkgroupIdLookup* m_tidLookup; - static lookups::AffiliationLookup* m_affiliations; + static ::lookups::IdenTableLookup* m_idenTable; + static ::lookups::RadioIdLookup* m_ridLookup; + static ::lookups::TalkgroupIdLookup* m_tidLookup; + static lookups::DMRAffiliationLookup* m_affiliations; - static lookups::IdenTable m_idenEntry; + static ::lookups::IdenTable m_idenEntry; static uint32_t m_hangCount; - static lookups::RSSIInterpolator* m_rssiMapper; + static ::lookups::RSSIInterpolator* m_rssiMapper; static uint32_t m_jitterTime; static uint32_t m_jitterSlots; diff --git a/dmr/lc/CSBK.cpp b/dmr/lc/CSBK.cpp index db53593c..b7ff2299 100644 --- a/dmr/lc/CSBK.cpp +++ b/dmr/lc/CSBK.cpp @@ -88,7 +88,7 @@ CSBK::CSBK() : m_logicalCh1(DMR_CHNULL), m_logicalCh2(DMR_CHNULL), m_slotNo(0U), - m_siteIdenEntry(lookups::IdenTable()) + m_siteIdenEntry(::lookups::IdenTable()) { /* stub */ } diff --git a/dmr/lc/CSBK.h b/dmr/lc/CSBK.h index a8bbb28b..b0d2d1b1 100644 --- a/dmr/lc/CSBK.h +++ b/dmr/lc/CSBK.h @@ -133,7 +133,7 @@ namespace dmr /** Local Site data */ /// Local Site Identity Entry. - __PROTECTED_PROPERTY_PLAIN(lookups::IdenTable, siteIdenEntry, siteIdenEntry); + __PROTECTED_PROPERTY_PLAIN(::lookups::IdenTable, siteIdenEntry, siteIdenEntry); protected: static bool m_verbose; diff --git a/dmr/lookups/DMRAffiliationLookup.cpp b/dmr/lookups/DMRAffiliationLookup.cpp new file mode 100644 index 00000000..2e062fa6 --- /dev/null +++ b/dmr/lookups/DMRAffiliationLookup.cpp @@ -0,0 +1,316 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program 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 2 of the License, or +* (at your option) any later version. +* +* This program 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 this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "dmr/lookups/DMRAffiliationLookup.h" +#include "Log.h" + +using namespace dmr::lookups; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the DMRAffiliationLookup class. +/// +/// Flag indicating whether verbose logging is enabled. +DMRAffiliationLookup::DMRAffiliationLookup(bool verbose) : ::lookups::AffiliationLookup("DMR Affiliation", verbose), + m_grantChSlotTable(), + m_tsccChNo(0U), + m_tsccSlot(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the DMRAffiliationLookup class. +/// +DMRAffiliationLookup::~DMRAffiliationLookup() +{ + /* stub */ +} + +/// +/// Helper to grant a channel. +/// +/// +/// +/// +bool DMRAffiliationLookup::grantCh(uint32_t dstId, uint32_t grantTimeout) +{ + uint32_t chNo = m_rfChTable.at(0); + uint8_t slot = getAvailableSlotForChannel(chNo); + + if (slot == 0U) { + return false; + } + + return grantChSlot(dstId, slot, grantTimeout); +} + +/// +/// Helper to grant a channel and slot. +/// +/// +/// +/// +/// +bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint8_t slot, uint32_t grantTimeout) +{ + if (dstId == 0U) { + return false; + } + + if (!isRFChAvailable()) { + return false; + } + + uint32_t chNo = m_rfChTable.at(0); + if (chNo == m_tsccChNo && slot == m_tsccSlot) { + return false; + } + + if (getAvailableSlotForChannel(chNo) == 0U || chNo == m_tsccChNo) { + auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); + m_rfChTable.erase(it); + } + + m_grantChTable[dstId] = chNo; + m_grantChSlotTable[dstId] = std::make_tuple(chNo, slot); + m_rfGrantChCnt++; + + m_grantTimers[dstId] = Timer(1000U, grantTimeout); + m_grantTimers[dstId].start(); + + if (m_verbose) { + LogMessage(LOG_HOST, "%s, granting channel, chNo = %u, slot = %u, dstId = %u", + m_name, chNo, slot, dstId); + } + + return true; +} + +/// +/// Helper to release the channel grant for the destination ID. +/// +/// +/// +bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) +{ + if (dstId == 0U && !releaseAll) { + return false; + } + + // are we trying to release all grants? + if (dstId == 0U && releaseAll) { + LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name); + + std::vector gntsToRel = std::vector(); + for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) { + uint32_t dstId = it->first; + gntsToRel.push_back(dstId); + } + + // release grants + for (auto it = gntsToRel.begin(); it != gntsToRel.end(); ++it) { + releaseGrant(*it, false); + } + + return true; + } + + if (isGranted(dstId)) { + uint32_t chNo = m_grantChTable.at(dstId); + std::tuple slotData = m_grantChSlotTable.at(dstId); + uint8_t slot = std::get<1>(slotData); + + if (m_verbose) { + LogMessage(LOG_HOST, "%s, releasing channel grant, chNo = %u, slot = %u, dstId = %u", + m_name, chNo, slot, dstId); + } + + m_grantChTable[dstId] = 0U; + m_grantChSlotTable.erase(dstId); + + auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); + if (it == m_rfChTable.end()) { + m_rfChTable.push_back(chNo); + } + + if (m_rfGrantChCnt > 0U) { + m_rfGrantChCnt--; + } + else { + m_rfGrantChCnt = 0U; + } + + m_grantTimers[dstId].stop(); + return true; + } + + return false; +} + +/// +/// Helper to determine if the channel number is busy. +/// +/// +/// +bool DMRAffiliationLookup::isChBusy(uint32_t chNo) const +{ + if (chNo == 0U) { + return false; + } + + // lookup dynamic channel grant table entry + for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) { + if (it->second == chNo) { + uint8_t slotCount = 0U; + if (chNo == m_tsccChNo) { + slotCount++; // one slot is *always* used for TSCC + } + + for (auto it = m_grantChSlotTable.begin(); it != m_grantChSlotTable.end(); ++it) { + uint32_t foundChNo = std::get<0>(it->second); + if (foundChNo == chNo) + slotCount++; + } + + if (slotCount == 2U) { + return true; + } else { + return false; + } + } + } + + return false; +} + +/// +/// Helper to get the slot granted for the given destination ID. +/// +/// +/// +uint8_t DMRAffiliationLookup::getGrantedSlot(uint32_t dstId) const +{ + if (dstId == 0U) { + return 0U; + } + + // lookup dynamic channel grant table entry + for (auto it = m_grantChSlotTable.begin(); it != m_grantChSlotTable.end(); ++it) { + if (it->first == dstId) { + uint8_t slot = std::get<1>(it->second); + return slot; + } + } + + return 0U; +} + +/// +/// Helper to set a slot for the given channel as being the TSCC. +/// +/// +/// +/// +void DMRAffiliationLookup::setSlotForChannelTSCC(uint32_t chNo, uint8_t slot) +{ + assert(chNo != 0U); + if ((slot == 0U) || (slot > 2U)) { + return; + } + + m_tsccChNo = chNo; + m_tsccSlot = slot; +} + +/// +/// Helper to determine the first available slot for given the channel number. +/// +/// +/// +uint8_t DMRAffiliationLookup::getAvailableSlotForChannel(uint32_t chNo) const +{ + if (chNo == 0U) { + return 0U; + } + + uint8_t slot = 1U; + + // lookup dynamic channel slot grant table entry + bool grantedSlot = false; + int slotCount = 0U; + for (auto it = m_grantChSlotTable.begin(); it != m_grantChSlotTable.end(); ++it) { + uint32_t foundChNo = std::get<0>(it->second); + if (foundChNo == chNo) + { + uint8_t foundSlot = std::get<1>(it->second); + if (slot == foundSlot) { + switch (foundSlot) { + case 1U: + slot = 2U; + break; + case 2U: + slot = 1U; + break; + } + + grantedSlot = true; + slotCount++; + } + } + } + + if (slotCount == 2U) { + slot = 0U; + return slot; + } + + // are we trying to assign the TSCC slot? + if (chNo == m_tsccChNo && slot == m_tsccSlot) { + if (!grantedSlot) { + // since we didn't find a slot being granted out -- utilize the slot opposing the TSCC + switch (m_tsccSlot) { + case 1U: + slot = 2U; + break; + case 2U: + slot = 1U; + break; + } + } else { + slot = 0U; // TSCC is not assignable + } + } + + return slot; +} diff --git a/dmr/lookups/DMRAffiliationLookup.h b/dmr/lookups/DMRAffiliationLookup.h new file mode 100644 index 00000000..149ea0cc --- /dev/null +++ b/dmr/lookups/DMRAffiliationLookup.h @@ -0,0 +1,77 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program 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 2 of the License, or +* (at your option) any later version. +* +* This program 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 this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_AFFILIATION_LOOKUP_H__) +#define __DMR_AFFILIATION_LOOKUP_H__ + +#include "Defines.h" +#include "lookups/AffiliationLookup.h" + +#include + +namespace dmr +{ + namespace lookups + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a lookup table class that contains DMR slot grant + // information. + // --------------------------------------------------------------------------- + + class HOST_SW_API DMRAffiliationLookup : public ::lookups::AffiliationLookup { + public: + /// Initializes a new instance of the DMRAffiliationLookup class. + DMRAffiliationLookup(bool verbose); + /// Finalizes a instance of the DMRAffiliationLookup class. + virtual ~DMRAffiliationLookup(); + + /// Helper to grant a channel. + virtual bool grantCh(uint32_t dstId, uint32_t grantTimeout); + /// Helper to grant a channel and slot. + bool grantChSlot(uint32_t dstId, uint8_t slot, uint32_t grantTimeout); + /// Helper to release the channel grant for the destination ID. + virtual bool releaseGrant(uint32_t dstId, bool releaseAll); + /// Helper to determine if the channel number is busy. + virtual bool isChBusy(uint32_t chNo) const; + /// Helper to get the slot granted for the given destination ID. + uint8_t getGrantedSlot(uint32_t dstId) const; + + /// Helper to set a slot for the given channel as being the TSCC. + void setSlotForChannelTSCC(uint32_t chNo, uint8_t slot); + + /// Helper to determine the first available slot for given the channel number. + uint8_t getAvailableSlotForChannel(uint32_t chNo) const; + + protected: + std::unordered_map> m_grantChSlotTable; + + uint32_t m_tsccChNo; + uint8_t m_tsccSlot; + }; + } // namespace lookups +} // namespace dmr + +#endif // __DMR_AFFILIATION_LOOKUP_H__ diff --git a/dmr/packet/ControlSignaling.cpp b/dmr/packet/ControlSignaling.cpp index eba4bae8..c17ae6dd 100644 --- a/dmr/packet/ControlSignaling.cpp +++ b/dmr/packet/ControlSignaling.cpp @@ -38,6 +38,7 @@ #include "dmr/Sync.h" #include "edac/BPTC19696.h" #include "edac/CRC.h" +#include "remote/RemoteCommand.h" #include "Log.h" #include "Utils.h" @@ -737,6 +738,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ { Slot *m_tscc = m_slot->m_dmr->getTSCCSlot(); + uint8_t slot = 0U; + bool emergency = ((serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag bool privacy = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Privacy Flag bool broadcast = ((serviceOptions & 0xFFU) & 0x10U) == 0x10U; // Broadcast Flag @@ -812,13 +815,14 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ else { if (m_tscc->m_affiliations->grantCh(dstId, GRANT_TIMER_TIMEOUT)) { chNo = m_tscc->m_affiliations->getGrantedCh(dstId); - + slot = m_tscc->m_affiliations->getGrantedSlot(dstId); //m_tscc->m_siteData.setChCnt(m_tscc->m_affiliations->getRFChCnt() + m_tscc->m_affiliations->getGrantedRFChCnt()); } } } else { chNo = m_tscc->m_affiliations->getGrantedCh(dstId); + slot = m_tscc->m_affiliations->getGrantedSlot(dstId); m_tscc->m_affiliations->touchGrant(dstId); } @@ -829,15 +833,23 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ ::ActivityLog("DMR", true, "Slot %u group grant request from %u to TG %u", m_tscc->m_slotNo, srcId, dstId); } - // - // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg - // + // callback RCON to permit-tg on the specified voice channel + if (m_tscc->m_authoritative && m_tscc->m_controlPermitTG) { + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { + std::stringstream ss; + ss << "permit-tg " << modem::DVM_STATE::STATE_DMR << " " << dstId << " " << slot; + + RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), + ss.str(), m_tscc->m_debug); + } + } std::unique_ptr csbk = new_unique(CSBK_TV_GRANT); if (broadcast) csbk->setCSBKO(CSBKO_BTV_GRANT); csbk->setLogicalCh1(chNo); - csbk->setSlotNo(1U); // eah? this can't be okay... + csbk->setSlotNo(slot); if (m_verbose) { LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", @@ -856,13 +868,21 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ ::ActivityLog("DMR", true, "Slot %u individual grant request from %u to TG %u", m_tscc->m_slotNo, srcId, dstId); } - // - // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg - // + // callback RCON to permit-tg on the specified voice channel + if (m_tscc->m_authoritative && m_tscc->m_controlPermitTG) { + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { + std::stringstream ss; + ss << "permit-tg " << modem::DVM_STATE::STATE_DMR << " " << dstId << " " << slot; + + RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), + ss.str(), m_tscc->m_debug); + } + } std::unique_ptr csbk = new_unique(CSBK_PV_GRANT); csbk->setLogicalCh1(chNo); - csbk->setSlotNo(1U); // eah? this can't be okay... + csbk->setSlotNo(slot); if (m_verbose) { LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", @@ -895,6 +915,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u { Slot *m_tscc = m_slot->m_dmr->getTSCCSlot(); + uint8_t slot = 0U; + bool emergency = ((serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag bool privacy = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Privacy Flag bool broadcast = ((serviceOptions & 0xFFU) & 0x10U) == 0x10U; // Broadcast Flag @@ -970,6 +992,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u else { if (m_tscc->m_affiliations->grantCh(dstId, GRANT_TIMER_TIMEOUT)) { chNo = m_tscc->m_affiliations->getGrantedCh(dstId); + slot = m_tscc->m_affiliations->getGrantedSlot(dstId); //m_tscc->m_siteData.setChCnt(m_tscc->m_affiliations->getRFChCnt() + m_tscc->m_affiliations->getGrantedRFChCnt()); } @@ -977,6 +1000,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u } else { chNo = m_tscc->m_affiliations->getGrantedCh(dstId); + slot = m_tscc->m_affiliations->getGrantedSlot(dstId); m_tscc->m_affiliations->touchGrant(dstId); } @@ -990,7 +1014,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u std::unique_ptr csbk = new_unique(CSBK_TD_GRANT); csbk->setLogicalCh1(chNo); - csbk->setSlotNo(1U); // eah? this can't be okay... + csbk->setSlotNo(slot); if (m_verbose) { LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_DATA_CALL (Group Data Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", @@ -1011,7 +1035,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u std::unique_ptr csbk = new_unique(CSBK_PD_GRANT); csbk->setLogicalCh1(chNo); - csbk->setSlotNo(1U); // eah? this can't be okay... + csbk->setSlotNo(slot); if (m_verbose) { LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_DATA_CALL (Individual Data Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", diff --git a/host/Host.cpp b/host/Host.cpp index 697136b6..0a3f0e98 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -140,6 +140,7 @@ Host::Host(const std::string& confFile) : m_p25QueueSizeBytes(2592U), // 12 frames m_nxdnQueueSizeBytes(1488U), // 31 frames m_authoritative(true), + m_controlPermitTG(false), m_activeTickDelay(5U), m_idleTickDelay(5U), m_remoteControl(nullptr) @@ -433,7 +434,7 @@ int Host::run() dmr = std::unique_ptr(new dmr::Control(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes, embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, dmrDumpCsbkData, dmrDebug, dmrVerbose)); - dmr->setOptions(m_conf, m_voiceChNo, m_voiceChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true); + dmr->setOptions(m_conf, m_controlPermitTG, m_voiceChNo, m_voiceChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true); if (dmrCtrlChannel) { dmr->setCCRunning(true); @@ -506,7 +507,7 @@ int Host::run() p25 = std::unique_ptr(new p25::Control(m_authoritative, m_p25NAC, callHang, m_p25QueueSizeBytes, m_modem, m_network, m_timeout, m_rfTalkgroupHang, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, p25RepeatDataPacket, p25DumpTsbkData, p25Debug, p25Verbose)); - p25->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_voiceChData, m_p25PatchSuperGroup, m_p25NetId, m_p25SysId, m_p25RfssId, + p25->setOptions(m_conf, m_controlPermitTG, m_cwCallsign, m_voiceChNo, m_voiceChData, m_p25PatchSuperGroup, m_p25NetId, m_p25SysId, m_p25RfssId, m_siteId, m_channelId, m_channelNo, true); if (p25CtrlChannel) { @@ -571,7 +572,7 @@ int Host::run() nxdn = std::unique_ptr(new nxdn::Control(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, nxdnDumpRcchData, nxdnDebug, nxdnVerbose)); - nxdn->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_voiceChData, m_siteId, m_channelId, m_channelNo, true); + nxdn->setOptions(m_conf, m_controlPermitTG, m_cwCallsign, m_voiceChNo, m_voiceChData, m_siteId, m_channelId, m_channelNo, true); if (nxdnCtrlChannel) { nxdn->setCCRunning(true); @@ -1925,7 +1926,11 @@ bool Host::readParams() LogInfo(" P25 RFSS Id: $%02X", m_p25RfssId); if (!m_authoritative) { + m_controlPermitTG = false; LogWarning(LOG_HOST, "Host is non-authoritative, this requires RCON to \"permit-tg\" for VCs and \"grant-tg\" for CCs!"); + } else { + m_controlPermitTG = rfssConfig["controlPermitTG"].as(false); + LogInfo(" Control Permit TG: %s", m_controlPermitTG ? "yes" : "no"); } } else { diff --git a/host/Host.h b/host/Host.h index 6d925b09..ce05b1cc 100644 --- a/host/Host.h +++ b/host/Host.h @@ -147,6 +147,7 @@ private: uint32_t m_nxdnQueueSizeBytes; bool m_authoritative; + bool m_controlPermitTG; uint8_t m_activeTickDelay; uint8_t m_idleTickDelay; diff --git a/nxdn/Control.cpp b/nxdn/Control.cpp index 5ae6f256..8d759b33 100644 --- a/nxdn/Control.cpp +++ b/nxdn/Control.cpp @@ -94,6 +94,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_voice(nullptr), m_data(nullptr), m_authoritative(authoritative), + m_controlPermitTG(false), m_ran(ran), m_timeout(timeout), m_modem(modem), @@ -203,6 +204,7 @@ void Control::reset() /// Helper to set NXDN configuration options. /// /// Instance of the yaml::Node class. +/// /// /// Voice Channel Number list. /// Voice Channel data map. @@ -210,13 +212,15 @@ void Control::reset() /// Channel ID. /// Channel Number. /// -void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, +void Control::setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo, const std::unordered_map voiceChData, uint16_t locId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node nxdnProtocol = conf["protocols"]["nxdn"]; + m_controlPermitTG = controlPermitTG; + m_trunk->m_verifyAff = nxdnProtocol["verifyAff"].as(false); m_trunk->m_verifyReg = nxdnProtocol["verifyReg"].as(false); diff --git a/nxdn/Control.h b/nxdn/Control.h index b3ded7c3..8cbf425a 100644 --- a/nxdn/Control.h +++ b/nxdn/Control.h @@ -83,7 +83,7 @@ namespace nxdn void reset(); /// Helper to set NXDN configuration options. - void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, + void setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo, const std::unordered_map voiceChData, uint16_t locId, uint8_t channelId, uint32_t channelNo, bool printOptions); @@ -130,6 +130,7 @@ namespace nxdn packet::Trunk* m_trunk; bool m_authoritative; + bool m_controlPermitTG; uint32_t m_ran; uint32_t m_timeout; diff --git a/nxdn/packet/Trunk.cpp b/nxdn/packet/Trunk.cpp index f5290e6c..dc12b020 100644 --- a/nxdn/packet/Trunk.cpp +++ b/nxdn/packet/Trunk.cpp @@ -36,6 +36,7 @@ #include "nxdn/Sync.h" #include "nxdn/NXDNUtils.h" #include "edac/CRC.h" +#include "remote/RemoteCommand.h" #include "HostMain.h" #include "Log.h" #include "Utils.h" @@ -508,9 +509,18 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic } } - // - // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg - // + // callback RCON to permit-tg on the specified voice channel + if (m_nxdn->m_authoritative && m_nxdn->m_controlPermitTG) { + ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.getRFChData(chNo); + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && + chNo != m_nxdn->m_siteData.channelNo()) { + std::stringstream ss; + ss << "permit-tg " << modem::DVM_STATE::STATE_NXDN << " " << dstId; + + RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), + ss.str(), m_nxdn->m_debug); + } + } std::unique_ptr rcch = new_unique(rcch::MESSAGE_TYPE_VCALL_CONN); rcch->setMessageType(RTCH_MESSAGE_TYPE_VCALL); diff --git a/p25/Control.cpp b/p25/Control.cpp index 709668b5..ea731cef 100644 --- a/p25/Control.cpp +++ b/p25/Control.cpp @@ -90,6 +90,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_data(nullptr), m_trunk(nullptr), m_authoritative(authoritative), + m_controlPermitTG(false), m_nac(nac), m_txNAC(nac), m_timeout(timeout), @@ -207,6 +208,7 @@ void Control::reset() /// Helper to set P25 configuration options. /// /// Instance of the yaml::Node class. +/// /// /// Voice Channel Number list. /// Voice Channel data map. @@ -218,13 +220,15 @@ void Control::reset() /// Channel ID. /// Channel Number. /// -void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, +void Control::setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo, const std::unordered_map voiceChData, uint32_t pSuperGroup, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node p25Protocol = conf["protocols"]["p25"]; + m_controlPermitTG = controlPermitTG; + m_tduPreambleCount = p25Protocol["tduPreambleCount"].as(8U); m_trunk->m_patchSuperGroup = pSuperGroup; diff --git a/p25/Control.h b/p25/Control.h index 59cc1261..5d7c1fda 100644 --- a/p25/Control.h +++ b/p25/Control.h @@ -85,7 +85,7 @@ namespace p25 void reset(); /// Helper to set P25 configuration options. - void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, + void setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo, const std::unordered_map voiceChData, uint32_t pSuperGroup, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); @@ -146,6 +146,7 @@ namespace p25 friend class lookups::P25AffiliationLookup; bool m_authoritative; + bool m_controlPermitTG; uint32_t m_nac; uint32_t m_txNAC; diff --git a/p25/packet/Trunk.cpp b/p25/packet/Trunk.cpp index 48b41e4f..539b3b58 100644 --- a/p25/packet/Trunk.cpp +++ b/p25/packet/Trunk.cpp @@ -35,6 +35,7 @@ #include "p25/P25Utils.h" #include "p25/Sync.h" #include "edac/CRC.h" +#include "remote/RemoteCommand.h" #include "Log.h" #include "Utils.h" @@ -2221,9 +2222,18 @@ bool Trunk::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOp ::ActivityLog("P25", true, "group grant request from %u to TG %u", srcId, dstId); } - // - // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg - // + // callback RCON to permit-tg on the specified voice channel + if (m_p25->m_authoritative && m_p25->m_controlPermitTG) { + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && + chNo != m_p25->m_siteData.channelNo()) { + std::stringstream ss; + ss << "permit-tg " << modem::DVM_STATE::STATE_P25 << " " << dstId; + + RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), + ss.str(), m_p25->m_debug); + } + } std::unique_ptr iosp = new_unique(IOSP_GRP_VCH); iosp->setMFId(m_lastMFID); @@ -2247,9 +2257,18 @@ bool Trunk::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOp ::ActivityLog("P25", true, "unit-to-unit grant request from %u to %u", srcId, dstId); } - // - // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg - // + // callback RCON to permit-tg on the specified voice channel + if (m_p25->m_authoritative && m_p25->m_controlPermitTG) { + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && + chNo != m_p25->m_siteData.channelNo()) { + std::stringstream ss; + ss << "permit-tg " << modem::DVM_STATE::STATE_P25 << " " << dstId; + + RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), + ss.str(), m_p25->m_debug); + } + } std::unique_ptr iosp = new_unique(IOSP_UU_VCH); iosp->setMFId(m_lastMFID); diff --git a/remote/RemoteCommand.cpp b/remote/RemoteCommand.cpp index 4e486c7a..539e4766 100644 --- a/remote/RemoteCommand.cpp +++ b/remote/RemoteCommand.cpp @@ -96,6 +96,25 @@ RemoteCommand::~RemoteCommand() /// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE. int RemoteCommand::send(const std::string& command) { + assert(!m_address.empty()); + assert(m_port > 0U); + + return send(m_address, m_port, m_password, command, m_debug); +} + +/// +/// Sends remote control command to the specified modem. +/// +/// Network Hostname/IP address to connect to. +/// Network port number. +/// Authentication password. +/// Command string to send to remote modem. +/// Flag indicating whether debug is enabled. +/// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE. +int RemoteCommand::send(const std::string& address, uint32_t port, const std::string& password, const std::string& command, bool debug) +{ + assert(!address.empty()); + assert(port > 0U); UDPSocket socket(0U); bool ret = socket.open(); @@ -108,22 +127,22 @@ int RemoteCommand::send(const std::string& command) sockaddr_storage addr; uint32_t addrLen; - if (UDPSocket::lookup(m_address, m_port, addr, addrLen) != 0) { + if (UDPSocket::lookup(address, port, addr, addrLen) != 0) { ::LogError(LOG_HOST, "Could not lookup the address of remote"); return ERRNO_ADDR_LOOKUP; } - ::LogInfoEx(LOG_HOST, "sending RCON command \"%s\" to %s:%u", command.c_str(), m_address.c_str(), m_port); + ::LogInfoEx(LOG_HOST, "sending RCON command \"%s\" to %s:%u", command.c_str(), address.c_str(), port); buffer[0U] = RCON_FRAME_START; buffer[1U] = START_OF_TEXT; - if (!m_password.empty()) { - size_t size = m_password.size(); + if (!password.empty()) { + size_t size = password.size(); uint8_t* in = new uint8_t[size]; for (size_t i = 0U; i < size; i++) - in[i] = m_password.at(i); + in[i] = password.at(i); uint8_t out[32U]; ::memset(out, 0x00U, 32U); @@ -139,7 +158,7 @@ int RemoteCommand::send(const std::string& command) buffer[35U + command.size()] = END_OF_TEXT; - if (m_debug) + if (debug) Utils::dump(1U, "RCON Sent", (uint8_t*)buffer, 36U + command.size()); ret = socket.write((uint8_t *)buffer, 36U + command.size(), addr, addrLen); @@ -166,23 +185,23 @@ int RemoteCommand::send(const std::string& command) if (offs + len > RESPONSE_BUFFER_LEN) break; - if (m_debug) + if (debug) ::LogDebug(LOG_RCON, "RemoteCommand::send() block len = %u, offs = %u", len - 3, offs); buffer[len] = '\0'; - if (m_debug) + if (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); + ::LogError(LOG_HOST, "Invalid response from host %s:%u", address.c_str(), 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); + ::LogError(LOG_HOST, "Invalid response from host %s:%u", address.c_str(), port); socket.close(); return EXIT_FAILURE; } diff --git a/remote/RemoteCommand.h b/remote/RemoteCommand.h index 0cb81073..effe8afa 100644 --- a/remote/RemoteCommand.h +++ b/remote/RemoteCommand.h @@ -51,6 +51,9 @@ public: /// Sends remote control command to the specified modem. int send(const std::string& command); + /// Sends remote control command to the specified modem. + static int send(const std::string& address, uint32_t port, const std::string& password, const std::string& command, bool debug = false); + private: std::string m_address; uint32_t m_port;