diff --git a/src/fne/lookups/AffiliationLookup.cpp b/src/fne/lookups/AffiliationLookup.cpp index b8494432..6a7a32e8 100644 --- a/src/fne/lookups/AffiliationLookup.cpp +++ b/src/fne/lookups/AffiliationLookup.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025-2026 Bryan Biedenkapp, N2PLL * */ #include "common/Log.h" @@ -79,9 +79,9 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) uint32_t AffiliationLookup::getSSRCByUnitReg(uint32_t srcId) { - // lookup dynamic channel grant table entry + // lookup dynamic unit registration table entry m_unitRegPeerTable.lock(false); - for (auto entry : m_grantChTable) { + for (auto entry : m_unitRegPeerTable) { if (entry.first == srcId) { m_unitRegPeerTable.unlock(); return entry.second; @@ -99,3 +99,126 @@ void AffiliationLookup::clearUnitReg() ::lookups::AffiliationLookup::clearUnitReg(); m_unitRegPeerTable.clear(); } + +/* Helper to grant a channel. */ + +bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t ssrc, uint32_t grantTimeout, bool grp) +{ + if (dstId == 0U) { + return false; + } + + __lock(); + + m_grantChTable[dstId] = 1U; // the FNE doesn't contain channels so internally all grants will be tracked with a + // channel number of 1, but this allows us to track the grant by the destination ID + m_grantSrcIdTable[dstId] = srcId; + m_grantSSRCTable[dstId] = ssrc; + m_rfGrantChCnt++; + + m_uuGrantedTable[dstId] = !grp; + + m_grantTimers[dstId] = Timer(1000U, grantTimeout); + m_grantTimers[dstId].start(); + + if (m_verbose) { + LogInfoEx(LOG_MASTER, "%s, granting talkgroup call, dstId = %u, srcId = %u, ssrc = %u, group = %u", + m_name.c_str(), dstId, srcId, ssrc, grp); + } + + __unlock(); + + return true; +} + +/* Helper to get the originating network peer ID by the registered source ID. */ + +uint32_t AffiliationLookup::getSSRCByGrant(uint32_t srcId) +{ + // lookup dynamic channel grant table entry + m_grantSSRCTable.lock(false); + for (auto entry : m_grantSSRCTable) { + if (entry.first == srcId) { + m_grantSSRCTable.unlock(); + return entry.second; + } + } + m_grantSSRCTable.unlock(); + + return 0U; +} + +/* Helper to release the channel grant for the destination ID. */ + +bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) +{ + if (dstId == 0U && !releaseAll) { + return false; + } + + // are we trying to release all grants? + if (dstId == 0U && releaseAll) { + LogWarning(LOG_MASTER, "%s, force releasing all talkgroup call grants", m_name.c_str()); + + m_grantSSRCTable.lock(false); + std::vector gntsToRel = std::vector(); + for (auto entry : m_grantSSRCTable) { + uint32_t dstId = entry.first; + gntsToRel.push_back(dstId); + } + m_grantSSRCTable.unlock(); + + // release grants + for (uint32_t dstId : gntsToRel) { + releaseGrant(dstId, false); + } + + return true; + } + + if (isGranted(dstId)) { + uint32_t ssrc = 0U; + m_grantSSRCTable.lock(false); + for (auto entry : m_grantSSRCTable) { + if (entry.first == dstId) { + ssrc = entry.second; + break; + } + } + m_grantSSRCTable.unlock(); + + uint32_t srcId = getGrantedSrcId(dstId); + + if (m_verbose) { + LogInfoEx(LOG_MASTER, "%s, releasing talkgroup call grant, ssrc = %u, dstId = %u", + m_name.c_str(), ssrc, dstId); + } + + if (m_releaseGrant != nullptr) { + m_releaseGrant(0U, srcId, dstId, 0U); + } + + __lock(); + + m_grantChTable.erase(dstId); + m_grantSSRCTable.erase(dstId); + m_grantSrcIdTable.erase(dstId); + m_uuGrantedTable.erase(dstId); + m_netGrantedTable.erase(dstId); + + if (m_rfGrantChCnt > 0U) { + m_rfGrantChCnt--; + } + else { + m_rfGrantChCnt = 0U; + } + + m_grantTimers[dstId].stop(); + + __unlock(); + + return true; + } + + return false; +} diff --git a/src/fne/lookups/AffiliationLookup.h b/src/fne/lookups/AffiliationLookup.h index d6faac60..f18c0270 100644 --- a/src/fne/lookups/AffiliationLookup.h +++ b/src/fne/lookups/AffiliationLookup.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025-2026 Bryan Biedenkapp, N2PLL * */ /** @@ -26,6 +26,12 @@ namespace fne_lookups { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t GRANT_TIMER_TIMEOUT = 15U; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -75,8 +81,45 @@ namespace fne_lookups void clearUnitReg() override; /** @} */ + /** @name Channel Grants */ + /** + * @brief Gets the grant table. + * @returns std::unordered_map Channel Grant Table. + */ + std::unordered_map grantSrcIdTable() const { return m_grantSrcIdTable.get(); } + /** + * @brief Gets the grant SSRC table. + * @returns std::unordered_map Source Peer Grant Table. + */ + std::unordered_map grantSSRCTable() const { return m_grantSSRCTable.get(); } + /** + * @brief Helper to grant a channel. + * @param dstId Destination Address. + * @param srcId Source Radio ID. + * @param ssrc Originating Peer ID. + * @param grantTimeout Time before the grant times out from inactivity. + * @param grp Flag indicating the grant is for a talkgroup. + * @returns bool True, if the destination address has been granted to the source ID, otherwise false. + */ + bool grantCh(uint32_t dstId, uint32_t srcId, uint32_t ssrc, uint32_t grantTimeout, bool grp); + /** + * @brief Helper to get the originating network peer ID by the granted destination ID. + * @param dstId Destination Address. + * @returns uint32_t Originating Peer ID. + */ + uint32_t getSSRCByGrant(uint32_t dstId); + /** + * @brief Helper to release the channel grant for the destination ID. + * @param dstId Destination Address. + * @param releaseAll Flag indicating all channel grants should be released. + * @returns bool True, if channel grant was released, otherwise false. + */ + bool releaseGrant(uint32_t dstId, bool releaseAll = false) override; + /** @} */ + protected: concurrent::unordered_map m_unitRegPeerTable; + concurrent::unordered_map m_grantSSRCTable; }; } // namespace fne_lookups diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index 8db7f6c0..f03061bd 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -99,6 +99,7 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16 m_peerAffiliations(), m_ccPeerMap(), m_peerReplicaKeyQueue(), + m_globalAff(nullptr), m_treeRoot(nullptr), m_treeLock(), m_peerReplicaHAParams(), @@ -169,6 +170,8 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16 m_p25OTARService = new P25OTARService(this, m_tagP25->packetData(), kmfDebug, verbose); + m_globalAff = new fne_lookups::AffiliationLookup("GlobalAffiliations", nullptr, false); + SpanningTree::s_maxUpdatesBeforeReparent = (uint8_t)host->m_maxMissedPings; m_treeRoot = new SpanningTree(peerId, peerId, nullptr); m_treeRoot->identity(identity); @@ -674,6 +677,8 @@ void TrafficNetwork::clock(uint32_t ms) m_updateLookupTimer.start(); } + m_globalAff->clock(ms); + // if HA is enabled perform HA parameter updates if (m_haEnabled) { m_haUpdateTimer.clock(ms); @@ -1928,6 +1933,7 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip && aff != nullptr) { uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address aff->unitReg(srcId, ssrc); + network->m_globalAff->unitReg(srcId, ssrc); // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { @@ -1964,6 +1970,7 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip && aff != nullptr) { uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address aff->unitDereg(srcId); + network->m_globalAff->unitDereg(srcId); // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { @@ -2001,6 +2008,7 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip && aff != nullptr) { uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address aff->groupUnaff(srcId); + network->m_globalAff->groupUnaff(srcId); // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { @@ -2048,6 +2056,7 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) uint32_t dstId = GET_UINT24(req->buffer, offs + 4U); aff->groupAff(srcId, dstId); + network->m_globalAff->groupAff(srcId, dstId); offs += 8U; } LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u affiliations", peerId, connection->identWithQualifier().c_str(), len); diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index 653ff74b..85e5dbfe 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -345,6 +345,8 @@ namespace network static std::timed_mutex s_keyQueueMutex; std::unordered_map m_peerReplicaKeyQueue; + fne_lookups::AffiliationLookup* m_globalAff; + SpanningTree* m_treeRoot; std::mutex m_treeLock; diff --git a/src/fne/network/callhandler/TagAnalogData.cpp b/src/fne/network/callhandler/TagAnalogData.cpp index bd5db529..2e0bb92c 100644 --- a/src/fne/network/callhandler/TagAnalogData.cpp +++ b/src/fne/network/callhandler/TagAnalogData.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025-2026 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -13,6 +13,7 @@ #include "common/Clock.h" #include "common/Log.h" #include "common/Utils.h" +#include "lookups/AffiliationLookup.h" #include "network/TrafficNetwork.h" #include "network/callhandler/TagAnalogData.h" #include "HostFNE.h" @@ -121,6 +122,8 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee } } + m_network->m_globalAff->releaseGrant(dstId); + #define CALL_END_LOG "Analog, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_END_LOG); @@ -279,6 +282,9 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee m_network->m_totalActiveCalls++; } + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, true); + #define CALL_START_LOG "Analog, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_START_LOG); diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 4680310c..c643a72b 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -16,6 +16,7 @@ #include "common/Clock.h" #include "common/Log.h" #include "common/Utils.h" +#include "lookups/AffiliationLookup.h" #include "network/TrafficNetwork.h" #include "network/callhandler/TagDMRData.h" #include "HostFNE.h" @@ -193,6 +194,9 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_statusPVCall.end()) { m_statusPVCall[dstId].reset(); + + m_network->m_globalAff->releaseGrant(dstId); + #define PRV_CALL_END_LOG "DMR, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, PRV_CALL_END_LOG); @@ -200,6 +204,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId LogInfoEx(LOG_MASTER, PRV_CALL_END_LOG); } else { + m_network->m_globalAff->releaseGrant(dstId); + #define CALL_END_LOG "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_END_LOG); @@ -407,6 +413,9 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_statusPVCall[dstId].dstPeerId = regSSRC; m_statusPVCall.unlock(); + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, false); + #define PRV_CALL_START_LOG "DMR, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, PRV_CALL_START_LOG); @@ -414,6 +423,9 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId LogInfoEx(LOG_MASTER, PRV_CALL_START_LOG); } else { + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, true); + #define CALL_START_LOG "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_START_LOG); @@ -673,7 +685,13 @@ bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, b } } - return true; + if (!m_network->m_globalAff->isGranted(dstId)) { + if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) { + return true; + } + } + + return false; } /* Helper to trigger a call takeover from a In-Call control event. */ diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 54b6e5b4..e34b75e5 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -20,6 +20,7 @@ #include "common/Clock.h" #include "common/Log.h" #include "common/Utils.h" +#include "lookups/AffiliationLookup.h" #include "network/TrafficNetwork.h" #include "network/callhandler/TagNXDNData.h" #include "HostFNE.h" @@ -232,6 +233,9 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI }); if (it != m_statusPVCall.end()) { m_statusPVCall[dstId].reset(); + + m_network->m_globalAff->releaseGrant(dstId); + #define PRV_CALL_END_LOG "NXDN, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, PRV_CALL_END_LOG); @@ -239,6 +243,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI LogInfoEx(LOG_MASTER, PRV_CALL_END_LOG); } else { + m_network->m_globalAff->releaseGrant(dstId); + #define CALL_END_LOG "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_END_LOG); @@ -432,6 +438,9 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_statusPVCall[dstId].dstPeerId = regSSRC; m_statusPVCall.unlock(); + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, false); + #define PRV_CALL_START_LOG "NXDN, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, PRV_CALL_START_LOG); @@ -439,6 +448,9 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI LogInfoEx(LOG_MASTER, PRV_CALL_START_LOG); } else { + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, true); + #define CALL_START_LOG "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_START_LOG); @@ -692,7 +704,13 @@ bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUni } } - return true; + if (!m_network->m_globalAff->isGranted(dstId)) { + if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) { + return true; + } + } + + return false; } /* Helper to trigger a call takeover from a In-Call control event. */ diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index ea40561a..b73c87a3 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -15,6 +15,7 @@ #include "common/Log.h" #include "common/Thread.h" #include "common/Utils.h" +#include "lookups/AffiliationLookup.h" #include "network/TrafficNetwork.h" #include "network/callhandler/TagP25Data.h" #include "HostFNE.h" @@ -30,12 +31,6 @@ using namespace p25::defines; #include #include -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const uint32_t GRANT_TIMER_TIMEOUT = 15U; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -282,6 +277,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_statusPVCall.end()) { m_statusPVCall[dstId].reset(); + + m_network->m_globalAff->releaseGrant(dstId); + #define PRV_CALL_END_LOG "P25, Private Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, PRV_CALL_END_LOG); @@ -289,6 +287,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId LogInfoEx(LOG_MASTER, PRV_CALL_END_LOG); } else { + m_network->m_globalAff->releaseGrant(dstId); + #define CALL_END_LOG "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_END_LOG); @@ -493,6 +493,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_statusPVCall[dstId].dstPeerId = regSSRC; m_statusPVCall.unlock(); + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, false); + #define PRV_CALL_START_LOG "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, PRV_CALL_START_LOG); @@ -500,6 +503,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId LogInfoEx(LOG_MASTER, PRV_CALL_START_LOG); } else { + if (!m_network->m_globalAff->isGranted(dstId)) + m_network->m_globalAff->grantCh(dstId, srcId, ssrc, fne_lookups::GRANT_TIMER_TIMEOUT, true); + #define CALL_START_LOG "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, fromUpstream if (m_network->m_logUpstreamCallStartEnd && fromUpstream) LogInfoEx(LOG_PEER, CALL_START_LOG); @@ -782,7 +788,13 @@ bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit } m_network->m_peers.shared_unlock(); - return true; + if (!m_network->m_globalAff->isGranted(dstId)) { + if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) { + return true; + } + } + + return false; } /* Helper to trigger a call takeover from a In-Call control event. */ diff --git a/src/fne/restapi/RESTAPI.cpp b/src/fne/restapi/RESTAPI.cpp index 65d9f236..6ab8f166 100644 --- a/src/fne/restapi/RESTAPI.cpp +++ b/src/fne/restapi/RESTAPI.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL * Copyright (C) 2024 Patrick McDonnell, W3AXL * */ @@ -676,7 +676,9 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(FNE_GET_RESET_TOTAL_CALLS).get(REST_API_BIND(RESTAPI::restAPI_GetResetTotalCalls, this)); m_dispatcher.match(FNE_GET_RESET_ACTIVE_CALLS).get(REST_API_BIND(RESTAPI::restAPI_GetResetActiveCalls, this)); m_dispatcher.match(FNE_GET_RESET_CALL_COLLISIONS).get(REST_API_BIND(RESTAPI::restAPI_GetResetCallCollisions, this)); + m_dispatcher.match(FNE_GET_UNIT_REG_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetUnitRegList, this)); m_dispatcher.match(FNE_GET_AFF_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetAffList, this)); + m_dispatcher.match(FNE_GET_GRANT_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetGrantList, this)); m_dispatcher.match(FNE_GET_SPANNING_TREE).get(REST_API_BIND(RESTAPI::restAPI_GetSpanningTree, this)); @@ -2033,6 +2035,34 @@ void RESTAPI::restAPI_GetResetCallCollisions(const HTTPPayload& request, HTTPPay reply.payload(response); } +/* REST API endpoint; implements get unit registration list request. */ + +void RESTAPI::restAPI_GetUnitRegList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + json::array units = json::array(); + if (m_network != nullptr) { + std::vector unitRegTable = m_network->m_globalAff->unitRegTable(); + + for (auto entry : unitRegTable) { + uint32_t srcId = entry; + units.push_back(json::value((double)srcId)); + } + + uint32_t totalUnits = unitRegTable.size(); + response["totalUnits"].set(totalUnits); + } + + response["units"].set(units); + reply.payload(response); +} + /* REST API endpoint; implements get affiliation list request. */ void RESTAPI::restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) @@ -2046,6 +2076,7 @@ void RESTAPI::restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, json::array affs = json::array(); if (m_network != nullptr) { + uint32_t totalAffiliations = 0U; if (m_network->m_peers.size() > 0) { for (auto entry : m_network->m_peers) { uint32_t peerId = entry.first; @@ -2073,16 +2104,58 @@ void RESTAPI::restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, peerObj["affiliations"].set(peerAffs); affs.push_back(json::value(peerObj)); + ++totalAffiliations; } } } } + + response["totalAffiliations"].set(totalAffiliations); } response["affiliations"].set(affs); reply.payload(response); } +/* REST API endpoint; implements get talkgroup grant list request. */ + +void RESTAPI::restAPI_GetGrantList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + json::array grants = json::array(); + if (m_network != nullptr) { + std::unordered_map grantSrcIdTable = m_network->m_globalAff->grantSrcIdTable(); + std::unordered_map grantSSRCTable = m_network->m_globalAff->grantSSRCTable(); + + for (auto entry : grantSrcIdTable) { + uint32_t dstId = entry.first; + uint32_t srcId = entry.second; + + json::object grantObj = json::object(); + grantObj["srcId"].set(srcId); + grantObj["dstId"].set(dstId); + + uint32_t ssrc = 0U; + grantSSRCTable.find(dstId) != grantSSRCTable.end() ? ssrc = grantSSRCTable[dstId] : ssrc = 0U; + grantObj["ssrc"].set(ssrc); + + grants.push_back(json::value(grantObj)); + } + + uint32_t totalGrants = grantSrcIdTable.size(); + response["totalGrants"].set(totalGrants); + } + + response["grants"].set(grants); + reply.payload(response); +} + /* REST API endpoint; implements get spanning tree list request. */ void RESTAPI::restAPI_GetSpanningTree(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) diff --git a/src/fne/restapi/RESTAPI.h b/src/fne/restapi/RESTAPI.h index 2e16fd57..7bd7550f 100644 --- a/src/fne/restapi/RESTAPI.h +++ b/src/fne/restapi/RESTAPI.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL * */ /** @@ -400,6 +400,14 @@ private: */ void restAPI_GetResetCallCollisions(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); + /** + * @brief REST API endpoint; implements get unit registration list request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetUnitRegList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); + /** * @brief REST API endpoint; implements get affiliation list request. * @param request HTTP request. @@ -408,6 +416,14 @@ private: */ void restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); + /** + * @brief REST API endpoint; implements get talkgroup grant list request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetGrantList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); + /** * @brief REST API endpoint; implements get spanning tree list request. * @param request HTTP request. diff --git a/src/fne/restapi/RESTDefines.h b/src/fne/restapi/RESTDefines.h index 6aec5402..254c0131 100644 --- a/src/fne/restapi/RESTDefines.h +++ b/src/fne/restapi/RESTDefines.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL * */ /** @@ -63,7 +63,9 @@ #define FNE_GET_RESET_TOTAL_CALLS "/stat-reset-total-calls" #define FNE_GET_RESET_ACTIVE_CALLS "/stat-reset-active-calls" #define FNE_GET_RESET_CALL_COLLISIONS "/stat-reset-call-collisions" +#define FNE_GET_UNIT_REG_LIST "/report-unit-regs" #define FNE_GET_AFF_LIST "/report-affiliations" +#define FNE_GET_GRANT_LIST "/report-grants" #define FNE_GET_SPANNING_TREE "/spanning-tree" diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp index 83833c99..6072341c 100644 --- a/src/remote/RESTClientMain.cpp +++ b/src/remote/RESTClientMain.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL * */ #include "remote/RESTClient.h" @@ -41,7 +41,9 @@ #define RCD_FNE_GET_PEERCOUNT "fne-peercount" #define RCD_FNE_GET_TGIDLIST "fne-tgidlist" #define RCD_FNE_GET_FORCEUPDATE "fne-force-update" +#define RCD_FNE_GET_UNITREGS "fne-unitregs" #define RCD_FNE_GET_AFFLIST "fne-affs" +#define RCD_FNE_GET_GRANTLIST "fne-grants" #define RCD_FNE_GET_RELOADTGS "fne-reload-tgs" #define RCD_FNE_GET_RELOADRIDS "fne-reload-rids" #define RCD_FNE_GET_RELOADPEERLIST "fne-reload-peerlist" @@ -220,7 +222,9 @@ void usage(const char* message, const char* arg) reply += " fne-peercount Retrieves the count of connected peers (Converged FNE only)\r\n"; reply += " fne-tgidlist Retrieves the list of configured TGIDs (Converged FNE only)\r\n"; reply += " fne-force-update Forces the FNE to send list update (Converged FNE only)\r\n"; + reply += " fne-unitregs Retrieves the list of currently registered unit IDs (Converged FNE only)\r\n"; reply += " fne-affs Retrieves the list of currently affiliated SUs (Converged FNE only)\r\n"; + reply += " fne-grants Retrieves the list of currently granted talkgroups (Converged FNE only)\r\n"; reply += " fne-reload-tgs Forces the FNE to reload its TGID list from disk (Converged FNE only)\r\n"; reply += " fne-reload-rids Forces the FNE to reload its RID list from disk (Converged FNE only)\r\n"; reply += " fne-reload-peerlist Forces the FNE to reload its peer list from disk (Converged FNE only)\r\n"; @@ -901,9 +905,15 @@ int main(int argc, char** argv) else if (rcom == RCD_FNE_GET_FORCEUPDATE) { retCode = client->send(HTTP_GET, FNE_GET_FORCE_UPDATE, json::object(), response); } + else if (rcom == RCD_FNE_GET_UNITREGS) { + retCode = client->send(HTTP_GET, FNE_GET_UNIT_REG_LIST, json::object(), response); + } else if (rcom == RCD_FNE_GET_AFFLIST) { retCode = client->send(HTTP_GET, FNE_GET_AFF_LIST, json::object(), response); } + else if (rcom == RCD_FNE_GET_GRANTLIST) { + retCode = client->send(HTTP_GET, FNE_GET_GRANT_LIST, json::object(), response); + } else if (rcom == RCD_FNE_GET_RELOADTGS) { retCode = client->send(HTTP_GET, FNE_GET_RELOAD_TGS, json::object(), response); }