From e0b6da51fb0258fd613d19ae623c795bc53084cd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 9 Apr 2024 14:39:11 -0400 Subject: [PATCH] make the network presence announcment (VC -> CC and CC -> FNE) timing configurable (this value is in seconds); refactor RF channel enumeration, RF channels are now enumerated in their own class ChannelLookup instead of being integrated into AffiliationLookup, this allows the flexibility to update and change channel information at runtime; add support for VC -> CC presence/registration to tell the CC what the REST information is for that VC, this makes the restAddress and restPort entries for the voiceChNo list in the config.yml optional, the only mandatory fields are channelId, channelNo and restPassword; --- configs/config.example.yml | 3 + src/common/lookups/AffiliationLookup.cpp | 44 ++--- src/common/lookups/AffiliationLookup.h | 99 ++---------- src/common/lookups/ChannelLookup.cpp | 97 +++++++++++ src/common/lookups/ChannelLookup.h | 143 +++++++++++++++++ src/common/network/udp/Socket.cpp | 59 ++++++- src/common/network/udp/Socket.h | 3 + src/fne/network/FNENetwork.cpp | 9 +- src/host/Host.Config.cpp | 17 +- src/host/Host.cpp | 151 ++++++++++++------ src/host/Host.h | 18 ++- src/host/dmr/Control.cpp | 18 +-- src/host/dmr/Control.h | 8 +- src/host/dmr/Slot.cpp | 24 +-- src/host/dmr/Slot.h | 5 +- src/host/dmr/lookups/DMRAffiliationLookup.cpp | 19 +-- src/host/dmr/lookups/DMRAffiliationLookup.h | 5 +- src/host/dmr/packet/ControlSignaling.cpp | 16 +- src/host/network/RESTAPI.cpp | 69 +++++--- src/host/nxdn/Control.cpp | 24 +-- src/host/nxdn/Control.h | 5 +- src/host/nxdn/packet/ControlSignaling.cpp | 4 +- src/host/p25/Control.cpp | 25 ++- src/host/p25/Control.h | 5 +- src/host/p25/lookups/P25AffiliationLookup.cpp | 9 +- src/host/p25/lookups/P25AffiliationLookup.h | 5 +- src/host/p25/packet/ControlSignaling.cpp | 16 +- src/host/p25/packet/Voice.cpp | 6 +- src/remote/RESTClient.cpp | 3 + 29 files changed, 585 insertions(+), 324 deletions(-) create mode 100644 src/common/lookups/ChannelLookup.cpp create mode 100644 src/common/lookups/ChannelLookup.h 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; }