diff --git a/configs/config.example.yml b/configs/config.example.yml index 09af266c..6484d1ef 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -372,6 +372,9 @@ system: restSsl: false # Flag indicating voice channels will notify the control channel of traffic status. notifyEnable: true + # Amount of time between network presence announcements. (seconds) + # NOTE: This value applies to VC -> CC and CC -> FNE presence notification messages. + presence: 120 # # Voice Channels diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 050ba945..65ec4fa3 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -7,7 +7,7 @@ * @package DVM / Common Library * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2022 Bryan Biedenkapp, N2PLL +* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ #include "lookups/AffiliationLookup.h" @@ -15,6 +15,8 @@ using namespace lookups; +#include + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -23,10 +25,9 @@ using namespace lookups; /// Initializes a new instance of the AffiliationLookup class. /// /// Name of lookup table. +/// Instance of the channel lookup class. /// Flag indicating whether verbose logging is enabled. -AffiliationLookup::AffiliationLookup(const std::string name, bool verbose) : - m_rfChTable(), - m_rfChDataTable(), +AffiliationLookup::AffiliationLookup(const std::string name, ChannelLookup* channelLookup, bool verbose) : m_rfGrantChCnt(0U), m_unitRegTable(), m_grpAffTable(), @@ -37,11 +38,12 @@ AffiliationLookup::AffiliationLookup(const std::string name, bool verbose) : m_grantTimers(), m_releaseGrant(nullptr), m_name(), + m_chLookup(channelLookup), m_verbose(verbose) { - m_name = name; + assert(channelLookup != nullptr); - m_rfChTable.clear(); + m_name = name; m_unitRegTable.clear(); m_grpAffTable.clear(); @@ -268,13 +270,12 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi return false; } - if (!isRFChAvailable()) { + if (!m_chLookup->isRFChAvailable()) { return false; } - uint32_t chNo = m_rfChTable.at(0); - auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); - m_rfChTable.erase(it); + uint32_t chNo = m_chLookup->getFirstRFChannel(); + m_chLookup->removeRFCh(chNo); m_grantChTable[dstId] = chNo; m_grantSrcIdTable[dstId] = srcId; @@ -355,7 +356,7 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) m_grantSrcIdTable.erase(dstId); m_uuGrantedTable.erase(dstId); m_netGrantedTable.erase(dstId); - m_rfChTable.push_back(chNo); + m_chLookup->addRFCh(chNo, true); if (m_rfGrantChCnt > 0U) { m_rfGrantChCnt--; @@ -514,27 +515,6 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId) return 0U; } -/// -/// Helper to get RF channel data. -/// -/// -/// -VoiceChData AffiliationLookup::getRFChData(uint32_t chNo) const -{ - if (chNo == 0U) { - return VoiceChData(); - } - - VoiceChData data; - try { - data = m_rfChDataTable.at(chNo); - } catch (...) { - data = VoiceChData(); - } - - return data; -} - /// /// Updates the processor by the passed number of milliseconds. /// diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index aae3bc83..9e214ec2 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -14,6 +14,7 @@ #define __AFFILIATION_LOOKUP_H__ #include "common/Defines.h" +#include "common/lookups/ChannelLookup.h" #include "common/Timer.h" #include @@ -24,79 +25,6 @@ namespace lookups { - // --------------------------------------------------------------------------- - // Class Declaration - // Represents voice channel data. - // --------------------------------------------------------------------------- - - class HOST_SW_API VoiceChData { - public: - /// Initializes a new instance of the VoiceChData class. - VoiceChData() : - m_chId(0U), - m_chNo(0U), - m_address(), - m_port(), - m_password(), - m_ssl() - { - /* stub */ - } - /// Initializes a new instance of the VoiceChData class. - /// Voice Channel Identity. - /// Voice Channel Number. - /// REST API Address. - /// REST API Port. - /// REST API Password. - /// Flag indicating REST is using SSL. - VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) : - m_chId(chId), - m_chNo(chNo), - m_address(address), - m_port(port), - m_password(password), - m_ssl(ssl) - { - /* stub */ - } - - /// Equals operator. - /// - /// - VoiceChData & operator=(const VoiceChData & data) - { - if (this != &data) { - m_chId = data.m_chId; - m_chNo = data.m_chNo; - m_address = data.m_address; - m_port = data.m_port; - m_password = data.m_password; - m_ssl = data.m_ssl; - } - - return *this; - } - - /// Helper to determine if the channel identity is valid. - bool isValidChId() const { return m_chId != 0U; } - /// Helper to determine if the channel is valid. - bool isValidCh() const { return m_chNo != 0U; } - - public: - /// Voice Channel Identity. - __READONLY_PROPERTY_PLAIN(uint8_t, chId); - /// Voice Channel Number. - __READONLY_PROPERTY_PLAIN(uint32_t, chNo); - /// REST API Address. - __READONLY_PROPERTY_PLAIN(std::string, address); - /// REST API Port. - __READONLY_PROPERTY_PLAIN(uint16_t, port); - /// REST API Password. - __READONLY_PROPERTY_PLAIN(std::string, password); - /// Flag indicating REST is using SSL. - __READONLY_PROPERTY_PLAIN(bool, ssl); - }; - // --------------------------------------------------------------------------- // Class Declaration // Implements a lookup table class that contains subscriber registration @@ -106,10 +34,11 @@ namespace lookups class HOST_SW_API AffiliationLookup { public: /// Initializes a new instance of the AffiliationLookup class. - AffiliationLookup(const std::string name, bool verbose); + AffiliationLookup(const std::string name, ChannelLookup* chLookup, bool verbose); /// Finalizes a instance of the AffiliationLookup class. virtual ~AffiliationLookup(); + /** Unit Registrations */ /// Gets the count of unit registrations. uint8_t unitRegSize() const { return m_unitRegTable.size(); } /// Gets the unit registration table. @@ -123,6 +52,7 @@ namespace lookups /// Helper to release unit registrations. virtual void clearUnitReg(); + /** Group Affiliations */ /// Gets the count of affiliations. uint8_t grpAffSize() const { return m_grpAffTable.size(); } /// Gets the group affiliation table. @@ -138,6 +68,7 @@ namespace lookups /// Helper to release group affiliations. virtual std::vector clearGroupAff(uint32_t dstId, bool releaseAll); + /** Channel Grants */ /// Gets the count of grants. uint8_t grantSize() const { return m_grantChTable.size(); } /// Gets the grant table. @@ -162,23 +93,12 @@ namespace lookups virtual uint32_t getGrantedBySrcId(uint32_t srcId); /// Helper to get the source ID granted for the given destination ID. virtual uint32_t getGrantedSrcId(uint32_t dstId); - - /// Helper to set RF channel data. - void setRFChData(const std::unordered_map& chData) { m_rfChDataTable = chData; } - /// Helper to get RF channel data. - VoiceChData getRFChData(uint32_t chNo) const; - - /// Helper to add a RF channel. - void addRFCh(uint32_t chNo) { m_rfChTable.push_back(chNo); } - /// Helper to remove a RF channel. - void removeRFCh(uint32_t chNo) { m_rfChTable.push_back(chNo); } - /// Gets the count of RF channels. - uint8_t getRFChCnt() const { return m_rfChTable.size(); } - /// Helper to determine if there are any RF channels available.. - bool isRFChAvailable() const { return !m_rfChTable.empty(); } /// Gets the count of granted RF channels. uint8_t getGrantedRFChCnt() const { return m_rfGrantChCnt; } + /// Gets the RF channel lookup class. + ChannelLookup* rfCh() const { return m_chLookup; } + /// Updates the processor by the passed number of milliseconds. void clock(uint32_t ms); @@ -186,8 +106,6 @@ namespace lookups void setReleaseGrantCallback(std::function&& callback) { m_releaseGrant = callback; } protected: - std::vector m_rfChTable; - std::unordered_map m_rfChDataTable; uint8_t m_rfGrantChCnt; std::vector m_unitRegTable; @@ -203,6 +121,7 @@ namespace lookups std::function m_releaseGrant; std::string m_name; + ChannelLookup* m_chLookup; bool m_verbose; }; diff --git a/src/common/lookups/ChannelLookup.cpp b/src/common/lookups/ChannelLookup.cpp new file mode 100644 index 00000000..e6f219af --- /dev/null +++ b/src/common/lookups/ChannelLookup.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** +* Digital Voice Modem - Common Library +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Common Library +* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) +* +* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* +*/ +#include "lookups/ChannelLookup.h" +#include "Log.h" + +using namespace lookups; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the ChannelLookup class. +/// +ChannelLookup::ChannelLookup() : + m_rfChTable(), + m_rfChDataTable() +{ + m_rfChTable.clear(); +} + +/// +/// Finalizes a instance of the ChannelLookup class. +/// +ChannelLookup::~ChannelLookup() = default; + +/// +/// Helper to get RF channel data. +/// +/// +/// +VoiceChData ChannelLookup::getRFChData(uint32_t chNo) const +{ + if (chNo == 0U) { + return VoiceChData(); + } + + VoiceChData data; + try { + data = m_rfChDataTable.at(chNo); + } catch (...) { + data = VoiceChData(); + } + + return data; +} + +/// +/// Helper to add a RF channel. +/// +/// +/// +/// +bool ChannelLookup::addRFCh(uint32_t chNo, bool force) +{ + if (chNo == 0U) { + return false; + } + + if (force) { + m_rfChTable.push_back(chNo); + return true; + } + + auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); + if (it == m_rfChTable.end()) { + m_rfChTable.push_back(chNo); + return true; + } + + return false; +} + +/// +/// Helper to remove a RF channel. +/// +/// +/// +void ChannelLookup::removeRFCh(uint32_t chNo) +{ + if (chNo == 0U) { + return; + } + + auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); + m_rfChTable.erase(it); +} diff --git a/src/common/lookups/ChannelLookup.h b/src/common/lookups/ChannelLookup.h new file mode 100644 index 00000000..148c98be --- /dev/null +++ b/src/common/lookups/ChannelLookup.h @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** +* Digital Voice Modem - Common Library +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Common Library +* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) +* +* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* +*/ +#if !defined(__CHANNEL_LOOKUP_H__) +#define __CHANNEL_LOOKUP_H__ + +#include "common/Defines.h" +#include "common/Timer.h" + +#include +#include +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents voice channel data. + // --------------------------------------------------------------------------- + + class HOST_SW_API VoiceChData { + public: + /// Initializes a new instance of the VoiceChData class. + VoiceChData() : + m_chId(0U), + m_chNo(0U), + m_address(), + m_port(), + m_password(), + m_ssl() + { + /* stub */ + } + /// Initializes a new instance of the VoiceChData class. + /// Voice Channel Identity. + /// Voice Channel Number. + /// REST API Address. + /// REST API Port. + /// REST API Password. + /// Flag indicating REST is using SSL. + VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) : + m_chId(chId), + m_chNo(chNo), + m_address(address), + m_port(port), + m_password(password), + m_ssl(ssl) + { + /* stub */ + } + + /// Equals operator. + /// + /// + VoiceChData & operator=(const VoiceChData & data) + { + if (this != &data) { + m_chId = data.m_chId; + m_chNo = data.m_chNo; + m_address = data.m_address; + m_port = data.m_port; + m_password = data.m_password; + m_ssl = data.m_ssl; + } + + return *this; + } + + /// Helper to determine if the channel identity is valid. + bool isValidChId() const { return m_chId != 0U; } + /// Helper to determine if the channel is valid. + bool isValidCh() const { return m_chNo != 0U; } + + public: + /// Voice Channel Identity. + __READONLY_PROPERTY_PLAIN(uint8_t, chId); + /// Voice Channel Number. + __READONLY_PROPERTY_PLAIN(uint32_t, chNo); + /// REST API Address. + __PROPERTY_PLAIN(std::string, address); + /// REST API Port. + __PROPERTY_PLAIN(uint16_t, port); + /// REST API Password. + __READONLY_PROPERTY_PLAIN(std::string, password); + /// Flag indicating REST is using SSL. + __PROPERTY_PLAIN(bool, ssl); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a lookup table class that contains RF channel information. + // --------------------------------------------------------------------------- + + class HOST_SW_API ChannelLookup { + public: + /// Initializes a new instance of the ChannelLookup class. + ChannelLookup(); + /// Finalizes a instance of the ChannelLookup class. + virtual ~ChannelLookup(); + + /** RF Channel Data */ + /// Gets the count of RF channel data. + uint8_t rfChDataSize() const { return m_rfChDataTable.size(); } + /// Gets the RF channel data table. + std::unordered_map rfChDataTable() const { return m_rfChDataTable; } + /// Helper to set RF channel data. + void setRFChData(const std::unordered_map& chData) { m_rfChDataTable = chData; } + /// Helper to set RF channel data. + void setRFChData(uint32_t chNo, VoiceChData chData) { m_rfChDataTable[chNo] = chData; } + /// Helper to get RF channel data. + VoiceChData getRFChData(uint32_t chNo) const; + /// Helper to get first available channel number. + uint32_t getFirstRFChannel() const { return m_rfChTable.at(0); } + + /// Gets the count of RF channels. + uint8_t rfChSize() const { return m_rfChTable.size(); } + /// Gets the RF channels table. + std::vector rfChTable() const { return m_rfChTable; } + /// Helper to add a RF channel. + bool addRFCh(uint32_t chNo, bool force = false); + /// Helper to remove a RF channel. + void removeRFCh(uint32_t chNo); + /// Helper to determine if there are any RF channels available.. + bool isRFChAvailable() const { return !m_rfChTable.empty(); } + + private: + std::vector m_rfChTable; + std::unordered_map m_rfChDataTable; + }; +} // namespace lookups + +#endif // __CHANNEL_LOOKUP_H__ diff --git a/src/common/network/udp/Socket.cpp b/src/common/network/udp/Socket.cpp index 846e7e22..2224a185 100644 --- a/src/common/network/udp/Socket.cpp +++ b/src/common/network/udp/Socket.cpp @@ -24,6 +24,8 @@ using namespace network::udp; #include #include +#include + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- @@ -578,15 +580,15 @@ void Socket::setPresharedKey(const uint8_t* presharedKey) /// /// String containing hostname to resolve. /// Numeric port number of service to resolve. -/// Socket address structure. +/// Socket address structure. /// /// Zero if no error during lookup, otherwise error. -int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& addr, uint32_t& addrLen) +int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& address, uint32_t& addrLen) { struct addrinfo hints; ::memset(&hints, 0, sizeof(hints)); - return lookup(hostname, port, addr, addrLen, hints); + return lookup(hostname, port, address, addrLen, hints); } /// @@ -594,11 +596,11 @@ int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& /// /// String containing hostname to resolve. /// Numeric port number of service to resolve. -/// Socket address structure. +/// Socket address structure. /// /// /// Zero if no error during lookup, otherwise error. -int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& addr, uint32_t& addrLen, struct addrinfo& hints) +int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& address, uint32_t& addrLen, struct addrinfo& hints) { std::string portstr = std::to_string(port); struct addrinfo* res; @@ -608,7 +610,7 @@ int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& int err = getaddrinfo(hostname.empty() ? NULL : hostname.c_str(), portstr.c_str(), &hints, &res); if (err != 0) { - sockaddr_in* paddr = (sockaddr_in*)& addr; + sockaddr_in* paddr = (sockaddr_in*)& address; ::memset(paddr, 0x00U, addrLen = sizeof(sockaddr_in)); paddr->sin_family = AF_INET; paddr->sin_port = htons(port); @@ -617,13 +619,56 @@ int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& return err; } - ::memcpy(&addr, res->ai_addr, addrLen = res->ai_addrlen); + ::memcpy(&address, res->ai_addr, addrLen = res->ai_addrlen); freeaddrinfo(res); return 0; } +/// +/// +/// +/// Zero if no error during lookup, otherwise error. +std::string Socket::getLocalAddress() +{ + struct ifaddrs *ifaddr, *ifa; + int n; + char host[NI_MAXHOST]; + + std::string address = std::string(); + + int err = -1; + if ((err = getifaddrs(&ifaddr)) == -1) { + LogError(LOG_NET, "Cannot retreive system network interfaces"); + return "0.0.0.0"; + } + + for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { + if (ifa->ifa_addr == NULL) + continue; + + int family = ifa->ifa_addr->sa_family; + if (family == AF_INET || family == AF_INET6) { + err = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (err != 0) { + LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d", errno); + break; + } + + address = std::string(host); + if (address == "127.0.0.1" || address == "::1") + continue; + else + break; + } + } + + freeifaddrs(ifaddr); + return address; +} + /// /// /// diff --git a/src/common/network/udp/Socket.h b/src/common/network/udp/Socket.h index 51286732..1c86a456 100644 --- a/src/common/network/udp/Socket.h +++ b/src/common/network/udp/Socket.h @@ -136,6 +136,9 @@ namespace network /// Helper to lookup a hostname and resolve it to an IP address. static int lookup(const std::string& hostName, uint16_t port, sockaddr_storage& address, uint32_t& addrLen, struct addrinfo& hints); + /// + static std::string getLocalAddress(); + /// static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type = IMT_ADDRESS_AND_PORT); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 2e4b5ea9..a9a11860 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1092,7 +1092,8 @@ void FNENetwork::createPeerAffiliations(uint32_t peerId, std::string peerName) erasePeerAffiliations(peerId); std::lock_guard lock(m_peerMutex); - m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, m_verbose); + lookups::ChannelLookup* chLookup = new lookups::ChannelLookup(); + m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, chLookup, m_verbose); } /// @@ -1106,8 +1107,12 @@ bool FNENetwork::erasePeerAffiliations(uint32_t peerId) auto it = std::find_if(m_peerAffiliations.begin(), m_peerAffiliations.end(), [&](PeerAffiliationMapPair x) { return x.first == peerId; }); if (it != m_peerAffiliations.end()) { lookups::AffiliationLookup* aff = m_peerAffiliations[peerId]; - if (aff != nullptr) + if (aff != nullptr) { + lookups::ChannelLookup* rfCh = aff->rfCh(); + if (rfCh != nullptr) + delete rfCh; delete aff; + } m_peerAffiliations.erase(peerId); return true; diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 454a0fdb..20fca540 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -139,6 +139,7 @@ bool Host::readParams() ** Channel Configuration */ yaml::Node rfssConfig = systemConf["config"]; + m_channelLookup = new ChannelLookup(); m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); if (m_channelId > 15U) { // clamp to 15 m_channelId = 15U; @@ -188,6 +189,7 @@ bool Host::readParams() uint16_t restApiPort = (uint16_t)controlCh["restPort"].as(REST_API_DEFAULT_PORT); std::string restApiPassword = controlCh["restPassword"].as(); bool restSsl = controlCh["restSsl"].as(false); + m_presenceTime = controlCh["presence"].as(120U); VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword, restSsl); m_controlChData = data; @@ -232,7 +234,7 @@ bool Host::readParams() chNo = 4095U; } - std::string restApiAddress = channel["restAddress"].as("127.0.0.1"); + std::string restApiAddress = channel["restAddress"].as("0.0.0.0"); uint16_t restApiPort = (uint16_t)channel["restPort"].as(REST_API_DEFAULT_PORT); std::string restApiPassword = channel["restPassword"].as(); bool restSsl = channel["restSsl"].as(false); @@ -240,14 +242,15 @@ bool Host::readParams() ::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", chId, chNo, restApiAddress.c_str(), restApiPort, restSsl); VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword, restSsl); - m_voiceChData[chNo] = data; - m_voiceChNo.push_back(chNo); + m_channelLookup->setRFChData(chNo, data); + m_channelLookup->addRFCh(chNo); } std::string strVoiceChNo = ""; - for (auto it = m_voiceChNo.begin(); it != m_voiceChNo.end(); ++it) { + std::vector voiceChNo = m_channelLookup->rfChTable(); + for (auto it = voiceChNo.begin(); it != voiceChNo.end(); ++it) { uint32_t chNo = ::atoi(std::to_string(*it).c_str()); - ::lookups::VoiceChData voiceChData = m_voiceChData[chNo]; + ::lookups::VoiceChData voiceChData = m_channelLookup->getRFChData(chNo); char hexStr[29]; @@ -816,6 +819,8 @@ bool Host::createNetwork() // initialize network remote command if (restApiEnable) { + m_restAddress = restApiAddress; + m_restPort = restApiPort; m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, restApiSSLKey, restApiSSLCert, restApiEnableSSL, this, restApiDebug); m_RESTAPI->setLookups(m_ridLookup, m_tidLookup); bool ret = m_RESTAPI->open(); @@ -827,6 +832,8 @@ bool Host::createNetwork() } } else { + m_restAddress = "0.0.0.0"; + m_restPort = REST_API_DEFAULT_PORT; m_RESTAPI = nullptr; } diff --git a/src/host/Host.cpp b/src/host/Host.cpp index a1925604..cb90d266 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -15,6 +15,7 @@ */ #include "Defines.h" #include "common/lookups/RSSIInterpolator.h" +#include "common/network/udp/Socket.h" #include "common/Log.h" #include "common/StopWatch.h" #include "common/Thread.h" @@ -86,8 +87,7 @@ Host::Host(const std::string& confFile) : m_txFrequency(0U), m_channelId(0U), m_channelNo(0U), - m_voiceChNo(), - m_voiceChData(), + m_channelLookup(), m_voiceChPeerId(), m_controlChData(), m_idenTable(nullptr), @@ -102,6 +102,7 @@ Host::Host(const std::string& confFile) : m_nxdnCCData(false), m_nxdnCtrlChannel(false), m_nxdnCtrlBroadcast(false), + m_presenceTime(120U), m_siteId(1U), m_sysId(1U), m_dmrNetId(1U), @@ -120,6 +121,8 @@ Host::Host(const std::string& confFile) : m_nxdnBcastDurationTimer(1000U), m_activeTickDelay(5U), m_idleTickDelay(5U), + m_restAddress("0.0.0.0"), + m_restPort(REST_API_DEFAULT_PORT), m_RESTAPI(nullptr) { /* stub */ @@ -404,9 +407,9 @@ int Host::run() } dmr = std::make_unique(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes, - embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, + embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, dmrDumpCsbkData, dmrDebug, dmrVerbose); - dmr->setOptions(m_conf, m_supervisor, m_voiceChNo, m_voiceChData, m_controlChData, m_dmrNetId, m_siteId, m_channelId, + dmr->setOptions(m_conf, m_supervisor, m_controlChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true); if (dmrCtrlChannel) { @@ -475,9 +478,9 @@ int Host::run() } p25 = std::make_unique(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, + m_network, m_timeout, m_rfTalkgroupHang, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, p25RepeatDataPacket, p25DumpTsbkData, p25Debug, p25Verbose); - p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_controlChData, + p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_controlChData, m_p25NetId, m_sysId, m_p25RfssId, m_siteId, m_channelId, m_channelNo, true); if (p25CtrlChannel) { @@ -537,9 +540,9 @@ int Host::run() } nxdn = std::make_unique(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes, - m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, + m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, nxdnDumpRcchData, nxdnDebug, nxdnVerbose); - nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_controlChData, m_siteId, + nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_controlChData, m_siteId, m_sysId, m_channelId, m_channelNo, true); if (nxdnCtrlChannel) { @@ -897,9 +900,90 @@ int Host::run() nxdnFrameWriteThread.run(); nxdnFrameWriteThread.setName("nxdn:frame-w"); - Timer ccRegisterTimer(1000U, 120U); - ccRegisterTimer.start(); - bool hasInitialRegistered = false; + /** Network Presence Notification */ + ThreadFunc presenceThread([&, this]() { + if (g_killed) + return; + + Timer presenceNotifyTimer(1000U, m_presenceTime); + presenceNotifyTimer.start(); + bool hasInitialRegistered = false; + + StopWatch stopWatch; + stopWatch.start(); + + while (!g_killed) { + // scope is intentional + { + uint32_t ms = stopWatch.elapsed(); + stopWatch.start(); + + presenceNotifyTimer.clock(ms); + + // VC -> CC presence registration + if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr && m_RESTAPI != nullptr) { + if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || !hasInitialRegistered) { + LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", m_controlChData.address().c_str(), m_controlChData.port(), m_network->getPeerId()); + hasInitialRegistered = true; + + std::string localAddress = network::udp::Socket::getLocalAddress(); + if (m_restAddress == "0.0.0.0") { + m_restAddress = localAddress; + } + + // callback REST API to release the granted TG on the specified control channel + json::object req = json::object(); + req["channelNo"].set(m_channelNo); + uint32_t peerId = m_network->getPeerId(); + req["peerId"].set(peerId); + req["restAddress"].set(m_restAddress); + req["restPort"].set(m_restPort); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_REGISTER_CC_VC, req, m_controlChData.ssl(), REST_QUICK_WAIT, false); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", m_controlChData.address().c_str(), m_controlChData.port()); + } + + presenceNotifyTimer.start(); + } + } + + // CC -> FNE registered VC announcement + if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) { + if (presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) { + if (m_network != nullptr && m_voiceChPeerId.size() > 0) { + LogMessage(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", m_network->getPeerId()); + + std::vector peers; + for (auto it : m_voiceChPeerId) { + peers.push_back(it.second); + } + + m_network->announceSiteVCs(peers); + } + + presenceNotifyTimer.start(); + } + } + } + + if (m_state != STATE_IDLE) + Thread::sleep(m_activeTickDelay); + if (m_state == STATE_IDLE) + Thread::sleep(m_idleTickDelay); + } + }); + + if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr) { + presenceThread.run(); + presenceThread.setName("host:presence"); + } + + if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) { + presenceThread.run(); + presenceThread.setName("host:presence"); + } // main execution loop while (!killed) { @@ -1049,47 +1133,6 @@ int Host::run() if (nxdn != nullptr) nxdn->clock(ms); - ccRegisterTimer.clock(ms); - - // VC -> CC presence registration - if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr) { - if ((ccRegisterTimer.isRunning() && ccRegisterTimer.hasExpired()) || !hasInitialRegistered) { - LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", m_controlChData.address().c_str(), m_controlChData.port(), m_network->getPeerId()); - hasInitialRegistered = true; - - // callback REST API to release the granted TG on the specified control channel - json::object req = json::object(); - req["channelNo"].set(m_channelNo); - uint32_t peerId = m_network->getPeerId(); - req["peerId"].set(peerId); - - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_REGISTER_CC_VC, req, m_controlChData.ssl(), REST_QUICK_WAIT, false); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", m_controlChData.address().c_str(), m_controlChData.port()); - } - - ccRegisterTimer.start(); - } - } - - // CC -> FNE registered VC announcement - if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) { - if (ccRegisterTimer.isRunning() && ccRegisterTimer.hasExpired()) { - if (m_network != nullptr && m_voiceChPeerId.size() > 0) { - LogMessage(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", m_network->getPeerId()); - - std::vector peers; - for (auto it : m_voiceChPeerId) { - peers.push_back(it.second); - } - - m_network->announceSiteVCs(peers); - } - ccRegisterTimer.start(); - } - } - // ------------------------------------------------------ // -- Timer Clocking -- // ------------------------------------------------------ @@ -1610,6 +1653,10 @@ void Host::setState(uint8_t state) delete m_RESTAPI; } + if (m_channelLookup != nullptr) { + delete m_channelLookup; + } + if (m_tidLookup != nullptr) { m_tidLookup->stop(); delete m_tidLookup; diff --git a/src/host/Host.h b/src/host/Host.h index d6033f2d..577a9f82 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -9,7 +9,7 @@ * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017-2023 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL * */ #if !defined(__HOST_H__) @@ -18,6 +18,7 @@ #include "Defines.h" #include "common/Timer.h" #include "common/lookups/AffiliationLookup.h" +#include "common/lookups/ChannelLookup.h" #include "common/lookups/IdenTableLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" @@ -54,10 +55,8 @@ public: /// Executes the main modem host processing loop. int run(); - /// Gets the voice channel number list. - std::vector getVoiceChNo() const { return m_voiceChNo; } - /// Gets the voice channel data. - std::unordered_map getVoiceChData() const { return m_voiceChData; } + /// Gets the RF channel lookup class. + lookups::ChannelLookup* rfCh() const { return m_channelLookup; } private: const std::string& m_confFile; @@ -107,8 +106,7 @@ private: uint8_t m_channelId; uint32_t m_channelNo; - std::vector m_voiceChNo; - std::unordered_map m_voiceChData; + lookups::ChannelLookup* m_channelLookup; std::unordered_map m_voiceChPeerId; lookups::VoiceChData m_controlChData; @@ -126,6 +124,8 @@ private: bool m_nxdnCtrlChannel; bool m_nxdnCtrlBroadcast; + uint32_t m_presenceTime; + uint8_t m_siteId; uint32_t m_sysId; uint32_t m_dmrNetId; @@ -150,7 +150,9 @@ private: uint8_t m_idleTickDelay; friend class RESTAPI; - RESTAPI* m_RESTAPI; + std::string m_restAddress; + uint16_t m_restPort; + RESTAPI *m_RESTAPI; /// Modem port open callback. bool rmtPortModemOpen(modem::Modem* modem); diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index 4eabf3c5..748c397a 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -42,6 +42,7 @@ using namespace dmr; /// Instance of the Modem class. /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. +/// Instance of the ChannelLookup class. /// Instance of the RadioIdLookup class. /// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. @@ -53,7 +54,7 @@ using namespace dmr; /// Flag indicating whether DMR debug is enabled. /// 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::Network* network, bool duplex, + bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) : m_authoritative(authoritative), @@ -78,13 +79,14 @@ Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint m_debug(debug) { assert(modem != nullptr); + assert(chLookup != nullptr); assert(ridLookup != nullptr); assert(tidLookup != nullptr); assert(idenTable != nullptr); assert(rssiMapper != nullptr); acl::AccessControl::init(m_ridLookup, m_tidLookup); - Slot::init(this, authoritative, colorCode, SiteData(), embeddedLCOnly, dumpTAData, callHang, modem, network, duplex, m_ridLookup, m_tidLookup, m_idenTable, rssiMapper, jitter, verbose); + Slot::init(this, authoritative, colorCode, SiteData(), embeddedLCOnly, dumpTAData, callHang, modem, network, duplex, chLookup, m_ridLookup, m_tidLookup, m_idenTable, rssiMapper, jitter, verbose); lc::CSBK::setVerbose(m_dumpCSBKData); m_slot1 = new Slot(1U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, dumpCSBKData, debug, verbose); @@ -107,16 +109,14 @@ Control::~Control() /// /// Instance of the ConfigINI class. /// Flag indicating whether the DMR has supervisory functions. -/// Voice Channel Number list. -/// Voice Channel data map. /// Control Channel data. /// DMR Network ID. /// DMR Site ID. /// Channel ID. /// Channel Number. /// -void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector voiceChNo, const std::unordered_map voiceChData, - ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) +void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChData controlChData, + 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"]; @@ -142,7 +142,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector(false); @@ -481,7 +481,7 @@ void Control::touchGrantTG(uint32_t dstId, uint8_t slot) /// /// Gets instance of the AffiliationLookup class. /// -dmr::lookups::DMRAffiliationLookup Control::affiliations() +dmr::lookups::DMRAffiliationLookup* Control::affiliations() { switch (m_tsccSlotNo) { case 1U: @@ -493,7 +493,7 @@ dmr::lookups::DMRAffiliationLookup Control::affiliations() break; } - return 0; // ?? + return nullptr; } /// diff --git a/src/host/dmr/Control.h b/src/host/dmr/Control.h index b67bd706..909f2761 100644 --- a/src/host/dmr/Control.h +++ b/src/host/dmr/Control.h @@ -45,15 +45,15 @@ namespace dmr public: /// 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::Network* network, bool duplex, + bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* 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, bool supervisor, const std::vector voiceChNo, const std::unordered_map voiceChData, - ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); + void setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChData controlChData, + 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. bool getCCRunning() const { return m_ccRunning; } @@ -87,7 +87,7 @@ namespace dmr void touchGrantTG(uint32_t dstId, uint8_t slot); /// Gets instance of the DMRAffiliationLookup class. - lookups::DMRAffiliationLookup affiliations(); + lookups::DMRAffiliationLookup* affiliations(); /// Helper to return the slot carrying the TSCC. Slot* getTSCCSlot() const; diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 15e95358..e46dd8ac 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -740,7 +740,7 @@ void Slot::releaseGrantTG(uint32_t dstId) if (m_affiliations->isGranted(dstId)) { uint32_t chNo = m_affiliations->getGrantedCh(dstId); uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_DMR, "DMR Slot %u, VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); @@ -763,7 +763,7 @@ void Slot::touchGrantTG(uint32_t dstId) if (m_affiliations->isGranted(dstId)) { uint32_t chNo = m_affiliations->getGrantedCh(dstId); uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_DMR, "DMR Slot %u, VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); @@ -870,6 +870,7 @@ uint32_t Slot::getLastSrcId() const /// Instance of the Modem class. /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. +/// Instance of the ChannelLookup class. /// Instance of the RadioIdLookup class. /// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. @@ -877,11 +878,12 @@ uint32_t Slot::getLastSrcId() const /// /// void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, - network::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, + network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose) { assert(dmr != nullptr); assert(modem != nullptr); + assert(chLookup != nullptr); assert(ridLookup != nullptr); assert(tidLookup != nullptr); assert(idenTable != nullptr); @@ -906,7 +908,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 dmr::lookups::DMRAffiliationLookup(verbose); + m_affiliations = new dmr::lookups::DMRAffiliationLookup(chLookup, verbose); // set the grant release callback m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { @@ -917,7 +919,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s return; } - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["slot"].set(slot); @@ -973,16 +975,13 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s /// /// Sets local configured site data. /// -/// Voice Channel Number list. -/// Voice Channel data map. /// Control Channel data. /// DMR Network ID. /// DMR Site ID. /// Channel ID. /// Channel Number. /// -void Slot::setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, - ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg) +void Slot::setSiteData(::lookups::VoiceChData controlChData, 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; @@ -995,13 +994,6 @@ void Slot::setSiteData(const std::vector voiceChNo, const std::unorder } } - for (uint32_t chNo : voiceChNo) { - m_affiliations->addRFCh(chNo); - } - - std::unordered_map chData = std::unordered_map(voiceChData); - m_affiliations->setRFChData(chData); - m_controlChData = controlChData; lc::CSBK::setSiteData(m_siteData); diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index 26b2580b..2544422f 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -131,11 +131,10 @@ namespace dmr /// 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::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, + network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* 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, - ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq); + static void setSiteData(::lookups::VoiceChData controlChData, 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); diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.cpp b/src/host/dmr/lookups/DMRAffiliationLookup.cpp index f2c27a24..e2ce5b6b 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.cpp +++ b/src/host/dmr/lookups/DMRAffiliationLookup.cpp @@ -7,7 +7,7 @@ * @package DVM / Modem Host Software * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2023 Bryan Biedenkapp, N2PLL +* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL * */ #include "common/Log.h" @@ -24,8 +24,9 @@ using namespace dmr::lookups; /// /// Initializes a new instance of the DMRAffiliationLookup class. /// +/// Instance of the channel lookup class. /// Flag indicating whether verbose logging is enabled. -DMRAffiliationLookup::DMRAffiliationLookup(bool verbose) : ::lookups::AffiliationLookup("DMR Affiliation", verbose), +DMRAffiliationLookup::DMRAffiliationLookup(::lookups::ChannelLookup* chLookup, bool verbose) : ::lookups::AffiliationLookup("DMR Affiliation", chLookup, verbose), m_grantChSlotTable(), m_tsccChNo(0U), m_tsccSlot(0U) @@ -49,7 +50,7 @@ DMRAffiliationLookup::~DMRAffiliationLookup() = default; /// bool DMRAffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout, bool grp, bool netGranted) { - uint32_t chNo = m_rfChTable.at(0); + uint32_t chNo = m_chLookup->getFirstRFChannel(); uint8_t slot = getAvailableSlotForChannel(chNo); if (slot == 0U) { @@ -75,18 +76,17 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s return false; } - if (!isRFChAvailable()) { + if (!m_chLookup->isRFChAvailable()) { return false; } - uint32_t chNo = m_rfChTable.at(0); + uint32_t chNo = m_chLookup->getFirstRFChannel(); 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_chLookup->removeRFCh(chNo); } m_grantChTable[dstId] = chNo; @@ -156,10 +156,7 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) m_grantChSlotTable.erase(dstId); m_netGrantedTable.erase(dstId); - auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); - if (it == m_rfChTable.end()) { - m_rfChTable.push_back(chNo); - } + m_chLookup->addRFCh(chNo); if (m_rfGrantChCnt > 0U) { m_rfGrantChCnt--; diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.h b/src/host/dmr/lookups/DMRAffiliationLookup.h index 478c25c6..4e69dfd8 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.h +++ b/src/host/dmr/lookups/DMRAffiliationLookup.h @@ -7,7 +7,7 @@ * @package DVM / Modem Host Software * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2023 Bryan Biedenkapp, N2PLL +* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL * */ #if !defined(__DMR_AFFILIATION_LOOKUP_H__) @@ -15,6 +15,7 @@ #include "Defines.h" #include "common/lookups/AffiliationLookup.h" +#include "common/lookups/ChannelLookup.h" #include @@ -31,7 +32,7 @@ namespace dmr class HOST_SW_API DMRAffiliationLookup : public ::lookups::AffiliationLookup { public: /// Initializes a new instance of the DMRAffiliationLookup class. - DMRAffiliationLookup(bool verbose); + DMRAffiliationLookup(::lookups::ChannelLookup* chLookup, bool verbose); /// Finalizes a instance of the DMRAffiliationLookup class. ~DMRAffiliationLookup() override; diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index bf268d93..9f58b5df 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -905,7 +905,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } if (!m_tscc->m_affiliations->isGranted(dstId)) { - if (!m_tscc->m_affiliations->isRFChAvailable()) { + if (!m_tscc->m_affiliations->rfCh()->isRFChAvailable()) { if (grp) { if (!net) { LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call) queued, no channels available, dstId = %u", m_tscc->m_slotNo, dstId); @@ -981,7 +981,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // callback REST API to permit the granted TG on the specified voice channel if (m_tscc->m_authoritative && m_tscc->m_supervisor && m_tscc->m_channelNo != chNo) { - ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); int state = modem::DVM_STATE::STATE_DMR; @@ -1030,7 +1030,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1059,7 +1059,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // callback REST API to permit the granted TG on the specified voice channel if (m_tscc->m_authoritative && m_tscc->m_supervisor && m_tscc->m_channelNo != chNo) { - ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); int state = modem::DVM_STATE::STATE_DMR; @@ -1106,7 +1106,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1196,7 +1196,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u } if (!m_tscc->m_affiliations->isGranted(dstId)) { - if (!m_tscc->m_affiliations->isRFChAvailable()) { + if (!m_tscc->m_affiliations->rfCh()->isRFChAvailable()) { if (grp) { if (!net) { LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_DATA_CALL (Group Data Call) queued, no channels available, dstId = %u", m_tscc->m_slotNo, dstId); @@ -1264,7 +1264,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1311,7 +1311,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index 4085eed1..863046f8 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -662,8 +662,9 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPPayload& request, HTTPPayload& reply, setResponseDefaultStatus(response); json::array channels = json::array(); - if (m_host->m_voiceChData.size() > 0) { - for (auto entry : m_host->m_voiceChData) { + if (m_host->rfCh()->rfChDataSize() > 0) { + auto voiceChData = m_host->rfCh()->rfChDataTable(); + for (auto entry : voiceChData) { uint32_t chNo = entry.first; lookups::VoiceChData data = entry.second; @@ -1109,7 +1110,8 @@ void RESTAPI::restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload& errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { - m_dmr->affiliations().releaseGrant(0, true); + if (m_dmr->affiliations() != nullptr) + m_dmr->affiliations()->releaseGrant(0, true); } if (m_p25 != nullptr) { @@ -1135,7 +1137,8 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& re errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { - m_dmr->affiliations().clearGroupAff(0, true); + if (m_dmr->affiliations() != nullptr) + m_dmr->affiliations()->clearGroupAff(0, true); } if (m_p25 != nullptr) { @@ -1179,7 +1182,7 @@ void RESTAPI::restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& r uint32_t channelNo = req["channelNo"].get(); - // validate channelNo is a string within the JSON blob + // validate peerId is a string within the JSON blob if (!req["peerId"].is()) { errorPayload(reply, "peerId was not a valid integer"); return; @@ -1189,11 +1192,39 @@ void RESTAPI::restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& r // LogDebug(LOG_REST, "restAPI_PutRegisterCCVC(): callback, channelNo = %u, peerId = %u", channelNo, peerId); - if (m_host->m_voiceChData.find(channelNo) != m_host->m_voiceChData.end()) { - ::lookups::VoiceChData voiceCh = m_host->m_voiceChData[channelNo]; + // validate restAddress is a string within the JSON blob + if (!req["restAddress"].is()) { + errorPayload(reply, "restAddress was not a valid string"); + return; + } + + if (!req["restPort"].is()) { + errorPayload(reply, "restPort was not a valid integer"); + return; + } + + std::string restAddress = req["restAddress"].get(); + uint16_t restPort = (uint16_t)req["restPort"].get(); + + auto voiceChData = m_host->rfCh()->rfChDataTable(); + if (voiceChData.find(channelNo) != voiceChData.end()) { + ::lookups::VoiceChData voiceCh = m_host->rfCh()->getRFChData(channelNo); + + if (voiceCh.address() == "0.0.0.0") { + voiceCh.address(restAddress); + } + + if (voiceCh.port() == 0U || voiceCh.port() == REST_API_DEFAULT_PORT) { + voiceCh.port(restPort); + } + + m_host->rfCh()->setRFChData(channelNo, voiceCh); m_host->m_voiceChPeerId[channelNo] = peerId; LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo); + LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port(), voiceCh.ssl()); + } else { + LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo); } } @@ -1790,17 +1821,19 @@ void RESTAPI::restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& rep setResponseDefaultStatus(response); json::array affs = json::array(); - std::unordered_map affTable = m_dmr->affiliations().grpAffTable(); - if (affTable.size() > 0) { - for (auto entry : affTable) { - uint32_t srcId = entry.first; - uint32_t grpId = entry.second; - - json::object aff = json::object(); - aff["srcId"].set(srcId); - aff["grpId"].set(grpId); - - affs.push_back(json::value(aff)); + if (m_dmr->affiliations() != nullptr) { + std::unordered_map affTable = m_dmr->affiliations()->grpAffTable(); + if (affTable.size() > 0) { + for (auto entry : affTable) { + uint32_t srcId = entry.first; + uint32_t grpId = entry.second; + + json::object aff = json::object(); + aff["srcId"].set(srcId); + aff["grpId"].set(grpId); + + affs.push_back(json::value(aff)); + } } } diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index 88756a4c..4d23e8ef 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -62,6 +62,7 @@ const uint8_t SCRAMBLER[] = { /// Instance of the Modem class. /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. +/// Instance of the ChannelLookup class. /// Instance of the RadioIdLookup class. /// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. @@ -70,7 +71,7 @@ const uint8_t SCRAMBLER[] = { /// Flag indicating whether P25 debug is enabled. /// Flag indicating whether P25 verbose logging is enabled. Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang, - modem::Modem* modem, network::Network* network, bool duplex, lookups::RadioIdLookup* ridLookup, + modem::Modem* modem, network::Network* network, bool duplex, lookups::ChannelLookup* chLookup, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, bool dumpRCCHData, bool debug, bool verbose) : m_voice(nullptr), @@ -94,7 +95,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), - m_affiliations("NXDN Affiliations", verbose), + m_affiliations("NXDN Affiliations", chLookup, verbose), m_controlChData(), m_idenEntry(), m_txImmQueue(queueSize, "NXDN Imm Frame"), @@ -131,6 +132,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_verbose(verbose), m_debug(debug) { + assert(chLookup != nullptr); assert(ridLookup != nullptr); assert(tidLookup != nullptr); assert(idenTable != nullptr); @@ -197,16 +199,13 @@ void Control::reset() /// Instance of the yaml::Node class. /// Flag indicating whether the DMR has supervisory functions. /// CW callsign of this host. -/// Voice Channel Number list. -/// Voice Channel data map. /// Control Channel data. /// NXDN Site Code. /// NXDN System Code. /// Channel ID. /// Channel Number. /// -void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, lookups::VoiceChData controlChData, +void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, lookups::VoiceChData controlChData, uint16_t siteId, uint32_t sysId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; @@ -274,20 +273,13 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_siteData = SiteData(locId, channelId, (channelNo & 0x3FF), serviceClass, false); m_siteData.setCallsign(cwCallsign); - for (uint32_t ch : voiceChNo) { - m_affiliations.addRFCh(ch); - } - - std::unordered_map chData = std::unordered_map(voiceChData); - m_affiliations.setRFChData(chData); - m_controlChData = controlChData; // set the grant release callback m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel if (m_authoritative && m_supervisor) { - ::lookups::VoiceChData voiceChData = m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_affiliations.rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_siteData.channelNo()) { json::object req = json::object(); @@ -747,7 +739,7 @@ void Control::releaseGrantTG(uint32_t dstId) if (m_affiliations.isGranted(dstId)) { uint32_t chNo = m_affiliations.getGrantedCh(dstId); uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_NXDN, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); @@ -770,7 +762,7 @@ void Control::touchGrantTG(uint32_t dstId) if (m_affiliations.isGranted(dstId)) { uint32_t chNo = m_affiliations.getGrantedCh(dstId); uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_NXDN, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index 0887c5ef..08b3fe22 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -56,7 +56,7 @@ namespace nxdn public: /// Initializes a new instance of the Control class. Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang, - modem::Modem* modem, network::Network* network, bool duplex, lookups::RadioIdLookup* ridLookup, + modem::Modem* modem, network::Network* network, bool duplex, lookups::ChannelLookup* chLookup, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, bool dumpRCCHData, bool debug, bool verbose); /// Finalizes a instance of the Control class. @@ -66,8 +66,7 @@ namespace nxdn void reset(); /// Helper to set NXDN configuration options. - void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, lookups::VoiceChData controlChData, + void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, lookups::VoiceChData controlChData, uint16_t siteId, uint32_t sysId, uint8_t channelId, uint32_t channelNo, bool printOptions); /// Gets a flag indicating whether the NXDN control channel is running. diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 9a6a711e..81704693 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -518,7 +518,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } if (!m_nxdn->m_affiliations.isGranted(dstId)) { - if (!m_nxdn->m_affiliations.isRFChAvailable()) { + if (!m_nxdn->m_affiliations.rfCh()->isRFChAvailable()) { if (grp) { if (!net) { LogWarning(LOG_RF, "NXDN, %s queued, no channels available, dstId = %u", rcch->toString().c_str(), dstId); @@ -593,7 +593,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin // callback REST API to permit the granted TG on the specified voice channel if (m_nxdn->m_authoritative && m_nxdn->m_supervisor) { - ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_nxdn->m_siteData.channelNo()) { json::object req = json::object(); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 41abaa59..8197cf5b 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -55,6 +55,7 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U; /// Transmit timeout. /// Amount of time to hang on the last talkgroup mode from RF. /// Flag indicating full-duplex operation. +/// Instance of the ChannelLookup class. /// Instance of the RadioIdLookup class. /// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. @@ -65,7 +66,7 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U; /// Flag indicating whether P25 debug is enabled. /// Flag indicating whether P25 verbose logging is enabled. Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::Network* network, - uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::RadioIdLookup* ridLookup, + uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, bool dumpPDUData, bool repeatPDU, bool dumpTSBKData, bool debug, bool verbose) : m_voice(nullptr), @@ -93,7 +94,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), - m_affiliations(this, verbose), + m_affiliations(this, chLookup, verbose), m_controlChData(), m_idenEntry(), m_txImmQueue(queueSize, "P25 Imm Frame"), @@ -140,6 +141,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_verbose(verbose), m_debug(debug) { + assert(chLookup != nullptr); assert(ridLookup != nullptr); assert(tidLookup != nullptr); assert(idenTable != nullptr); @@ -207,8 +209,6 @@ void Control::reset() /// Instance of the yaml::Node class. /// Flag indicating whether the DMR has supervisory functions. /// CW callsign of this host. -/// Voice Channel Number list. -/// Voice Channel data map. /// Control Channel data. /// /// P25 Network ID. @@ -218,8 +218,7 @@ void Control::reset() /// Channel ID. /// Channel Number. /// -void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, const ::lookups::VoiceChData controlChData, +void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const ::lookups::VoiceChData controlChData, 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"]; @@ -409,13 +408,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw } } - m_siteData.setChCnt((uint8_t)voiceChNo.size()); - for (uint32_t ch : voiceChNo) { - m_affiliations.addRFCh(ch); - } - - std::unordered_map chData = std::unordered_map(voiceChData); - m_affiliations.setRFChData(chData); + m_siteData.setChCnt((uint8_t)m_affiliations.rfCh()->rfChSize()); m_controlChData = controlChData; @@ -423,7 +416,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel if (m_authoritative && m_supervisor) { - ::lookups::VoiceChData voiceChData = m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_affiliations.rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_siteData.channelNo()) { json::object req = json::object(); @@ -999,7 +992,7 @@ void Control::releaseGrantTG(uint32_t dstId) if (m_affiliations.isGranted(dstId)) { uint32_t chNo = m_affiliations.getGrantedCh(dstId); uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); @@ -1022,7 +1015,7 @@ void Control::touchGrantTG(uint32_t dstId) if (m_affiliations.isGranted(dstId)) { uint32_t chNo = m_affiliations.getGrantedCh(dstId); uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 9eab8e95..80b80a4d 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -57,7 +57,7 @@ namespace p25 public: /// Initializes a new instance of the Control class. Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::Network* network, - uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::RadioIdLookup* ridLookup, + uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, bool dumpPDUData, bool repeatPDU, bool dumpTSBKData, bool debug, bool verbose); /// Finalizes a instance of the Control class. @@ -67,8 +67,7 @@ namespace p25 void reset(); /// Helper to set P25 configuration options. - void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, const ::lookups::VoiceChData controlChData, + void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const ::lookups::VoiceChData controlChData, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); /// Gets a flag indicating whether the P25 control channel is running. diff --git a/src/host/p25/lookups/P25AffiliationLookup.cpp b/src/host/p25/lookups/P25AffiliationLookup.cpp index ef4b2e82..c3432f51 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.cpp +++ b/src/host/p25/lookups/P25AffiliationLookup.cpp @@ -7,7 +7,7 @@ * @package DVM / Modem Host Software * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2022 Bryan Biedenkapp, N2PLL +* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ #include "common/Log.h" @@ -24,8 +24,9 @@ using namespace p25::lookups; /// Initializes a new instance of the P25AffiliationLookup class. /// /// Name of lookup table. +/// Instance of the channel lookup class. /// Flag indicating whether verbose logging is enabled. -P25AffiliationLookup::P25AffiliationLookup(Control* p25, bool verbose) : ::lookups::AffiliationLookup("P25 Affiliation", verbose), +P25AffiliationLookup::P25AffiliationLookup(Control* p25, ::lookups::ChannelLookup* chLookup, bool verbose) : ::lookups::AffiliationLookup("P25 Affiliation", chLookup, verbose), m_p25(p25) { /* stub */ @@ -46,10 +47,10 @@ bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll); if (ret) { if (m_rfGrantChCnt > 0U) { - m_p25->m_siteData.setChCnt(getRFChCnt() + m_rfGrantChCnt); + m_p25->m_siteData.setChCnt(m_chLookup->rfChSize() + m_rfGrantChCnt); } else { - m_p25->m_siteData.setChCnt(getRFChCnt()); + m_p25->m_siteData.setChCnt(m_chLookup->rfChSize()); } } diff --git a/src/host/p25/lookups/P25AffiliationLookup.h b/src/host/p25/lookups/P25AffiliationLookup.h index e6ccf392..eec6ec9e 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.h +++ b/src/host/p25/lookups/P25AffiliationLookup.h @@ -7,7 +7,7 @@ * @package DVM / Modem Host Software * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * -* Copyright (C) 2022 Bryan Biedenkapp, N2PLL +* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ #if !defined(__P25_AFFILIATION_LOOKUP_H__) @@ -15,6 +15,7 @@ #include "Defines.h" #include "common/lookups/AffiliationLookup.h" +#include "common/lookups/ChannelLookup.h" namespace p25 { @@ -35,7 +36,7 @@ namespace p25 class HOST_SW_API P25AffiliationLookup : public ::lookups::AffiliationLookup { public: /// Initializes a new instance of the P25AffiliationLookup class. - P25AffiliationLookup(Control* p25, bool verbose); + P25AffiliationLookup(Control* p25, ::lookups::ChannelLookup* chLookup, bool verbose); /// Finalizes a instance of the P25AffiliationLookup class. ~P25AffiliationLookup() override; diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 0c2fee64..57c4e6eb 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2201,7 +2201,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } if (!m_p25->m_affiliations.isGranted(dstId)) { - if (!m_p25->m_affiliations.isRFChAvailable()) { + if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) { if (grp) { if (!net) { LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request) queued, no channels available, dstId = %u", dstId); @@ -2234,7 +2234,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ else { if (m_p25->m_affiliations.grantCh(dstId, srcId, GRANT_TIMER_TIMEOUT, grp, net)) { chNo = m_p25->m_affiliations.getGrantedCh(dstId); - m_p25->m_siteData.setChCnt(m_p25->m_affiliations.getRFChCnt() + m_p25->m_affiliations.getGrantedRFChCnt()); + m_p25->m_siteData.setChCnt(m_p25->m_affiliations.rfCh()->rfChSize() + m_p25->m_affiliations.getGrantedRFChCnt()); } } } @@ -2274,7 +2274,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } if (chNo > 0U) { - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); if (grp) { if (!net) { @@ -2418,7 +2418,7 @@ void ControlSignaling::writeRF_TSDU_Grant_Update() uint32_t chNo = entry.second; bool grp = m_p25->m_affiliations.isGroup(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); if (chNo == 0U) { noData = true; @@ -2503,7 +2503,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId, } if (!m_p25->m_affiliations.isGranted(srcId)) { - if (!m_p25->m_affiliations.isRFChAvailable()) { + if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) { if (!net) { LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_ISP_SNDCP_CH_REQ (SNDCP Data Channel Request) queued, no channels available, srcId = %u", srcId); writeRF_TSDU_Queue(srcId, dstId, P25_QUE_RSN_CHN_RESOURCE_NOT_AVAIL, TSBK_ISP_SNDCP_CH_REQ); @@ -2517,18 +2517,18 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId, else { if (m_p25->m_affiliations.grantCh(srcId, srcId, GRANT_TIMER_TIMEOUT, false, net)) { uint32_t chNo = m_p25->m_affiliations.getGrantedCh(srcId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); osp->setGrpVchId(voiceChData.chId()); osp->setGrpVchNo(chNo); osp->setDataChnNo(chNo); - m_p25->m_siteData.setChCnt(m_p25->m_affiliations.getRFChCnt() + m_p25->m_affiliations.getGrantedRFChCnt()); + m_p25->m_siteData.setChCnt(m_p25->m_affiliations.rfCh()->rfChSize() + m_p25->m_affiliations.getGrantedRFChCnt()); } } } else { uint32_t chNo = m_p25->m_affiliations.getGrantedCh(srcId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); osp->setGrpVchId(voiceChData.chId()); osp->setGrpVchNo(chNo); diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 4a56e57c..3babd143 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -413,7 +413,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // if voice on control; insert grant updates before voice traffic if (m_p25->m_voiceOnControl) { uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); bool grp = m_p25->m_affiliations.isGroup(dstId); std::unique_ptr osp; @@ -516,7 +516,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // if voice on control; insert group voice channel updates directly after HDU but before LDUs if (m_p25->m_voiceOnControl) { uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); bool grp = m_p25->m_affiliations.isGroup(dstId); std::unique_ptr osp; @@ -1676,7 +1676,7 @@ void Voice::writeNet_LDU1() // if voice on control; insert grant updates before voice traffic if (m_p25->m_voiceOnControl) { uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); bool grp = m_p25->m_affiliations.isGroup(dstId); std::unique_ptr osp; diff --git a/src/remote/RESTClient.cpp b/src/remote/RESTClient.cpp index 8daad9a9..04ad98b9 100644 --- a/src/remote/RESTClient.cpp +++ b/src/remote/RESTClient.cpp @@ -182,6 +182,9 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin if (address.empty()) { return ERRNO_NO_ADDRESS; } + if (address == "0.0.0.0") { + return ERRNO_NO_ADDRESS; + } if (port <= 0U) { return ERRNO_NO_ADDRESS; }