From 06df86e7134f0e0e19dde4120d5ddf7d7a89c104 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 22 May 2026 15:33:30 -0400 Subject: [PATCH] add support for group affiliation timeout similar to unit registration timeout (should handle #122); add support for bulk announcement of peer unit registration data to the FNE; move announcement handling from main traffic port to metadata port (for now there is a code redirect on the traffic port that will be maintained for a few versions before being deprecated; --- configs/config.example.yml | 6 + src/common/lookups/AffiliationLookup.cpp | 83 +++++- src/common/lookups/AffiliationLookup.h | 22 +- src/common/network/BaseNetwork.cpp | 33 ++- src/common/network/BaseNetwork.h | 21 ++ src/common/network/RTPFNEHeader.h | 3 +- src/fne/network/MetadataNetwork.cpp | 316 ++++++++++++++++++++++- src/fne/network/TrafficNetwork.cpp | 270 +------------------ src/host/dmr/Control.cpp | 11 + src/host/dmr/Slot.cpp | 7 + src/host/nxdn/Control.cpp | 15 +- src/host/p25/Control.cpp | 15 +- 12 files changed, 514 insertions(+), 288 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index b35268e4..6c637ee9 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -185,6 +185,8 @@ protocols: dumpCsbkData: false # Flag indicating unit registration will be verified after some operations. verifyReg: false + # Flag indicating automated 12-hour idle group affiliation timeout is disabled. + disableGrpAffTimeout: false # Flag indicating automated 12-hour idle unit registration timeout is disabled. disableUnitRegTimeout: false # Specifies the random wait delay for a subscriber. @@ -286,6 +288,8 @@ protocols: verifyAff: false # Flag indicating the host should verify unit registration. verifyReg: false + # Flag indicating automated 12-hour idle group affiliation timeout is disabled. + disableGrpAffTimeout: false # Flag indicating automated 12-hour idle unit registration timeout is disabled. disableUnitRegTimeout: false # Flag indicating the host requires LLA verification before allowing unit registration. @@ -361,6 +365,8 @@ protocols: verifyAff: false # Flag indicating the host should verify unit registration. verifyReg: false + # Flag indicating automated 12-hour idle group affiliation timeout is disabled. + disableGrpAffTimeout: false # Flag indicating automated 12-hour idle unit registration timeout is disabled. disableUnitRegTimeout: false # Flag indicating whether verbose dumping of NXDN RCCH data is enabled. diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 3d924bca..a5570791 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -19,6 +19,7 @@ using namespace lookups; // --------------------------------------------------------------------------- const uint32_t UNIT_REG_TIMEOUT = 43200U; // 12 hours +const uint32_t GRP_AFF_TIMEOUT = 43200U; // 12 hours // --------------------------------------------------------------------------- // Public Class Members @@ -31,15 +32,18 @@ AffiliationLookup::AffiliationLookup(const std::string name, ChannelLookup* chan m_unitRegTable(), m_unitRegTimers(), m_grpAffTable(), + m_grpAffTimers(), m_grantChTable(), m_grantSrcIdTable(), m_uuGrantedTable(), m_netGrantedTable(), m_grantTimers(), m_releaseGrant(nullptr), + m_unitDereg(nullptr), m_name(), m_chLookup(channelLookup), m_disableUnitRegTimeout(false), + m_disableGrpAffTimeout(false), m_verbose(verbose) { m_name = name; @@ -47,6 +51,7 @@ AffiliationLookup::AffiliationLookup(const std::string name, ChannelLookup* chan m_unitRegTable.clear(); m_unitRegTimers.clear(); m_grpAffTable.clear(); + m_grpAffTimers.clear(); m_grantChTable.clear(); m_grantSrcIdTable.clear(); @@ -133,9 +138,15 @@ void AffiliationLookup::touchUnitReg(uint32_t srcId) __spinlock(); + // restart the unit registration timer if (isUnitReg(srcId)) { m_unitRegTimers[srcId].start(); } + + // restart the group affiliation timer if the source ID is group affiliated + if (isSrcIdGrpAff(srcId)) { + m_grpAffTimers[srcId].start(); + } } /* Gets the current timer timeout for this unit registration. */ @@ -195,7 +206,6 @@ bool AffiliationLookup::isUnitReg(uint32_t srcId) const void AffiliationLookup::clearUnitReg() { __lock(); - std::vector srcToRel = std::vector(); LogWarning(LOG_HOST, "%s, releasing all unit registrations", m_name.c_str()); m_unitRegTable.clear(); __unlock(); @@ -211,6 +221,9 @@ void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId) // update dynamic affiliation table m_grpAffTable[srcId] = dstId; + m_grpAffTimers[srcId] = Timer(1000U, GRP_AFF_TIMEOUT); + m_grpAffTimers[srcId].start(); + if (m_verbose) { LogInfoEx(LOG_HOST, "%s, group affiliation, srcId = %u, dstId = %u", m_name.c_str(), srcId, dstId); @@ -233,23 +246,17 @@ bool AffiliationLookup::groupUnaff(uint32_t srcId) LogInfoEx(LOG_HOST, "%s, group unaffiliation, srcId = %u, dstId = %u", m_name.c_str(), srcId, it->second); } - } else { - __unlock(); - return false; - } - // remove dynamic affiliation table entry - try { - uint32_t entry = m_grpAffTable.at(srcId); // this value will get discarded - (void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard m_grpAffTable.erase(srcId); + + m_grpAffTimers[srcId].stop(); + __unlock(); return true; - } - catch (...) { - __unlock(); - return false; - } + } + + __unlock(); + return false; } /* Helper to determine if the group destination ID has any affiations. */ @@ -689,6 +696,31 @@ void AffiliationLookup::clock(uint32_t ms) releaseGrant(dstId, false); } + if (!m_disableGrpAffTimeout) { + m_grpAffTable.spinlock(); + + // clock all the group affiliation timers + m_grpAffTable.lock(false); + std::vector affsToRel = std::vector(); + for (auto entry : m_grpAffTable) { + uint32_t srcId = entry.first; + auto it = m_grpAffTimers.find(srcId); + if (it != m_grpAffTimers.end()) { + it->second.clock(ms); + if (it->second.isRunning() && it->second.hasExpired()) { + affsToRel.push_back(srcId); + } + } + } + m_grpAffTable.unlock(); + + // release group affiliations that have timed out + for (uint32_t srcId : affsToRel) { + LogWarning(LOG_HOST, "%s, clearing stale group affiliation, srcId = %u", m_name.c_str(), srcId); + groupUnaff(srcId); + } + } + if (!m_disableUnitRegTimeout) { m_unitRegTable.spinlock(); @@ -708,7 +740,30 @@ void AffiliationLookup::clock(uint32_t ms) // release units registrations that have timed out for (uint32_t srcId : unitsToDereg) { + LogWarning(LOG_HOST, "%s, clearing stale unit deregistration, srcId = %u", m_name.c_str(), srcId); unitDereg(srcId, true); } } } + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/* Helper to determine if the source ID has group affiliations. */ + +bool AffiliationLookup::isSrcIdGrpAff(uint32_t srcId) const +{ + __spinlock(); + + // lookup dynamic affiliation table entry + m_grpAffTable.lock(false); + auto it = m_grpAffTable.find(srcId); + if (it != m_grpAffTable.end()) { + m_grpAffTable.unlock(); + return true; + } + m_grpAffTable.unlock(); + + return false; +} diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index f14cbd9a..5366a801 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/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) 2022,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2024,2026 Bryan Biedenkapp, N2PLL * */ /** @@ -264,6 +264,17 @@ namespace lookups */ void setDisableUnitRegTimeout(bool disabled) { m_disableUnitRegTimeout = disabled; } + /** + * @brief Helper to determine if the group affiliation timeout is enabled or not. + * @returns bool True, if idle group affiliation timeouts are disabled, otherwise false. + */ + virtual bool isDisableGrpAffTimeout() const { return m_disableGrpAffTimeout; } + /** + * @brief Disables the group affiliation timeout. + * @param disable Flag indicating idle group affiliation timeout should be disabled. + */ + void setDisableGrpAffTimeout(bool disabled) { m_disableGrpAffTimeout = disabled; } + /** * @brief Helper to set the release grant callback. * @note Do not call AffiliationLookup get functions from within this callback, deadlock protection @@ -285,6 +296,7 @@ namespace lookups concurrent::vector m_unitRegTable; concurrent::unordered_map m_unitRegTimers; concurrent::unordered_map m_grpAffTable; + concurrent::unordered_map m_grpAffTimers; concurrent::unordered_map m_grantChTable; concurrent::unordered_map m_grantSrcIdTable; @@ -301,8 +313,16 @@ namespace lookups ChannelLookup* m_chLookup; bool m_disableUnitRegTimeout; + bool m_disableGrpAffTimeout; bool m_verbose; + + /** + * @brief Helper to determine if the source ID has group affiliations. + * @param srcId Source Radio ID. + * @returns bool True, if the source ID has group affiliations, otherwise false. + */ + bool isSrcIdGrpAff(uint32_t srcId) const; }; } // namespace lookups diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index f1426b56..0a816168 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -250,7 +250,7 @@ bool BaseNetwork::announceGroupAffiliation(uint32_t srcId, uint32_t dstId) SET_UINT24(srcId, buffer, 0U); SET_UINT24(dstId, buffer, 3U); - return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, buffer, MSG_ANNC_GRP_AFFIL, RTP_END_OF_CALL_SEQ, 0U); + return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, buffer, MSG_ANNC_GRP_AFFIL, RTP_END_OF_CALL_SEQ, 0U, true); } /* Writes a group affiliation removal to the network. */ @@ -264,7 +264,7 @@ bool BaseNetwork::announceGroupAffiliationRemoval(uint32_t srcId) SET_UINT24(srcId, buffer, 0U); - return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, buffer, MSG_ANNC_GRP_UNAFFIL, RTP_END_OF_CALL_SEQ, 0U); + return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, buffer, MSG_ANNC_GRP_UNAFFIL, RTP_END_OF_CALL_SEQ, 0U, true); } /* Writes a unit registration to the network. */ @@ -278,7 +278,7 @@ bool BaseNetwork::announceUnitRegistration(uint32_t srcId) SET_UINT24(srcId, buffer, 0U); - return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, buffer, MSG_ANNC_UNIT_REG, RTP_END_OF_CALL_SEQ, 0U); + return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, buffer, MSG_ANNC_UNIT_REG, RTP_END_OF_CALL_SEQ, 0U, true); } /* Writes a unit deregistration to the network. */ @@ -292,7 +292,7 @@ bool BaseNetwork::announceUnitDeregistration(uint32_t srcId) SET_UINT24(srcId, buffer, 0U); - return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, buffer, MSG_ANNC_UNIT_REG, RTP_END_OF_CALL_SEQ, 0U); + return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, buffer, MSG_ANNC_UNIT_REG, RTP_END_OF_CALL_SEQ, 0U, true); } /* Writes a complete update of the peer affiliation list to the network. */ @@ -314,7 +314,28 @@ bool BaseNetwork::announceAffiliationUpdate(const std::unordered_map regs) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + DECLARE_UINT8_ARRAY(buffer, 4U + (regs.size() * 3U)); + + SET_UINT32(regs.size(), buffer, 0U); + + // write unit IDs to active unit registration payload + uint32_t offs = 4U; + for (auto it : regs) { + SET_UINT24(it, buffer, offs); + offs += 3U; + } + + return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS }, buffer, 4U + (regs.size() * 3U), RTP_END_OF_CALL_SEQ, 0U, true); } /* Writes a complete update of the peer's voice channel list to the network. */ @@ -335,7 +356,7 @@ bool BaseNetwork::announceSiteVCs(const std::vector peers) offs += 4U; } - return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, buffer, 4U + (peers.size() * 4U), RTP_END_OF_CALL_SEQ, 0U); + return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, buffer, 4U + (peers.size() * 4U), RTP_END_OF_CALL_SEQ, 0U, true); } /* Resets the DMR ring buffer for the given slot. */ diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index e6538f00..21f0fe45 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -681,6 +681,27 @@ namespace network */ virtual bool announceAffiliationUpdate(const std::unordered_map affs); + /** + * @brief Writes a complete update of the peer's unit registration list to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is variable bytes in length. + * + * Each unit registration update entry is 3 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Source ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode + * @param regs Complete list of unit registrations. + * @returns bool True, if unit registration update announcement was sent, otherwise false. + */ + virtual bool announceUnitRegUpdate(const std::vector regs); + /** * @brief Writes a complete update of the peer's voice channel list to the network. * \code{.unparsed} diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 7d01b57a..fafa9788 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.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) 2023,2024,2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL * */ /** @@ -106,6 +106,7 @@ namespace network ANNC_SUBFUNC_UNIT_DEREG = 0x02U, //!< Announce Unit Deregistration ANNC_SUBFUNC_GRP_UNAFFIL = 0x03U, //!< Announce Group Affiliation Removal ANNC_SUBFUNC_AFFILS = 0x90U, //!< Update All Affiliations + ANNC_SUBFUNC_UNIT_REGS = 0x91U, //!< Update All Unit Registrations ANNC_SUBFUNC_SITE_VC = 0x9AU, //!< Announce Site VCs REPL_TALKGROUP_LIST = 0x00U, //!< FNE Replication Talkgroup Transfer diff --git a/src/fne/network/MetadataNetwork.cpp b/src/fne/network/MetadataNetwork.cpp index 560faaeb..98d724c5 100644 --- a/src/fne/network/MetadataNetwork.cpp +++ b/src/fne/network/MetadataNetwork.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" @@ -192,6 +192,7 @@ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) if (req->length > 0) { uint32_t peerId = req->fneHeader.getPeerId(); + uint32_t ssrc = req->rtpHeader.getSSRC(); uint32_t streamId = req->fneHeader.getStreamId(); // process incoming message function opcodes @@ -383,6 +384,319 @@ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) } break; + case NET_FUNC::ANNOUNCE: // Announce + { + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL: // Announce Group Affiliation + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address + uint32_t dstId = GET_UINT24(req->buffer, 3U); // Destination Address + aff->groupUnaff(srcId); + aff->groupAff(srcId, dstId); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG: // Announce Unit Registration + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + 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) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true, 0U, ssrc); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG: // Announce Unit Deregistration + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + 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) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL: // Announce Group Affiliation Removal + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + 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) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_AFFILS: // Announce Update All Affiliations + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + if (aff != nullptr) { + aff->clearGroupAff(0U, true); + + // update TGID lists + uint32_t len = GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t srcId = GET_UINT24(req->buffer, offs); + 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); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS: // Announce Update All Unit Registrations + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + if (aff != nullptr) { + aff->clearUnitReg(); + + // update unit registration lists + uint32_t len = GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t srcId = GET_UINT24(req->buffer, offs); + + aff->unitReg(srcId, ssrc); + network->m_globalAff->unitReg(srcId, ssrc); + offs += 3U; + } + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u unit registrations", peerId, connection->identWithQualifier().c_str(), len); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC: // Announce Site VCs + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + std::vector vcPeers; + + // update peer association + uint32_t len = GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t vcPeerId = GET_UINT32(req->buffer, offs); + if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) { + FNEPeerConnection* vcConnection = network->m_peers[vcPeerId]; + if (vcConnection != nullptr) { + vcConnection->ccPeerId(peerId); + vcPeers.push_back(vcPeerId); + } + } + offs += 4U; + } + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u VCs", peerId, connection->identWithQualifier().c_str(), len); + network->m_ccPeerMap[peerId] = vcPeers; + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + default: + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_ILLEGAL_PACKET); + Utils::dump("Unknown announcement opcode from the peer", req->buffer, req->length); + } + } + break; + case NET_FUNC::REPL: if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_ACT_PEER_LIST) { // Peer Replication Active Peer List if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index 0a5842b9..908079e0 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.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" @@ -1878,268 +1878,11 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) case NET_FUNC::TRANSFER: // Transfer // transfer command is not supported for performance reasons on the main traffic port break; - - case NET_FUNC::ANNOUNCE: // Announce - { - // process incoming message subfunction opcodes - switch (req->fneHeader.getSubFunction()) { - case NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL: // Announce Group Affiliation - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - uint32_t dstId = GET_UINT24(req->buffer, 3U); // Destination Address - aff->groupUnaff(srcId); - aff->groupAff(srcId, dstId); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG: // Announce Unit Registration - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - 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) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, 0U, ssrc); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG: // Announce Unit Deregistration - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - 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) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL: // Announce Group Affiliation Removal - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - 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) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_AFFILS: // Announce Update All Affiliations - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - if (aff != nullptr) { - aff->clearGroupAff(0U, true); - - // update TGID lists - uint32_t len = GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t srcId = GET_UINT24(req->buffer, offs); - 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); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); - } - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC: // Announce Site VCs - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - std::vector vcPeers; - - // update peer association - uint32_t len = GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t vcPeerId = GET_UINT32(req->buffer, offs); - if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) { - FNEPeerConnection* vcConnection = network->m_peers[vcPeerId]; - if (vcConnection != nullptr) { - vcConnection->ccPeerId(peerId); - vcPeers.push_back(vcPeerId); - } - } - offs += 4U; - } - LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u VCs", peerId, connection->identWithQualifier().c_str(), len); - network->m_ccPeerMap[peerId] = vcPeers; - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - default: - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("Unknown announcement opcode from the peer", req->buffer, req->length); - } - } - break; + case NET_FUNC::ANNOUNCE: // Announce + // bryanb: temporary support to allow announce packets on the traffic port but ultimately + // this should be removed and handled like TRANSFER is handled here + network->m_host->m_mdNetwork->taskNetworkRx(req); + return; // don't break, return because taskNetworkRx will cleanup req default: Utils::dump("Unknown opcode from the peer", req->buffer, req->length); @@ -2248,6 +1991,7 @@ void TrafficNetwork::createPeerAffiliations(uint32_t peerId, std::string peerNam }); aff->setDisableUnitRegTimeout(true); // FNE doesn't allow unit registration timeouts (notification must come from the peers) + aff->setDisableGrpAffTimeout(true); // FNE doesn't allow group affiliation timeouts (notification must come from the peers) m_peerAffiliations.insert(peerId, aff); } diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index a8609a48..6e480a5c 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -152,6 +152,9 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_slot1->setNotifyCC(notifyCC); m_slot2->setNotifyCC(notifyCC); + bool disableGrpAffTimeout = dmrProtocol["disableGrpAffTimeout"].as(false); + m_slot1->s_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); + m_slot2->s_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); bool disableUnitRegTimeout = dmrProtocol["disableUnitRegTimeout"].as(false); m_slot1->s_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); m_slot2->s_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); @@ -231,6 +234,14 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa LogInfo(" Default Network Idle Talkgroup: %u", defaultNetIdleTalkgroup); } + if (disableGrpAffTimeout) { + LogInfo(" Disable Group Affiliation Timeout: yes"); + } + + if (disableUnitRegTimeout) { + LogInfo(" Disable Unit Registration Timeout: yes"); + } + LogInfo(" Ignore Affiliation Check: %s", ignoreAffiliationCheck ? "yes" : "no"); LogInfo(" Legacy Group Registration: %s", legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", notifyCC ? "yes" : "no"); diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 6aa2e37b..0cee8287 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -813,6 +813,13 @@ void Slot::clockSiteData(uint32_t ms) if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { m_control->writeAdjSSNetwork(); if (s_network != nullptr) { + // network announce our unit registration table if we have one + if (s_affiliations->unitRegSize() > 0) { + auto regs = s_affiliations->unitRegTable(); + s_network->announceUnitRegUpdate(regs); + } + + // network announce our affiliation table if we have one if (s_affiliations->grpAffSize() > 0) { auto affs = s_affiliations->grpAffTable(); s_network->announceAffiliationUpdate(affs); diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index e2e6b1b8..4813d0eb 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2026 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -274,6 +274,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_controlChData = controlChData; + bool disableGrpAffTimeout = nxdnProtocol["disableGrpAffTimeout"].as(false); + m_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); bool disableUnitRegTimeout = nxdnProtocol["disableUnitRegTimeout"].as(false); m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); @@ -342,6 +344,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Verify Affiliation: %s", m_control->m_verifyAff ? "yes" : "no"); LogInfo(" Verify Registration: %s", m_control->m_verifyReg ? "yes" : "no"); + if (disableGrpAffTimeout) { + LogInfo(" Disable Group Affiliation Timeout: yes"); + } + if (disableUnitRegTimeout) { LogInfo(" Disable Unit Registration Timeout: yes"); } @@ -634,6 +640,13 @@ void Control::clock() if (m_adjSiteUpdate.isRunning() && m_adjSiteUpdate.hasExpired()) { if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { if (m_network != nullptr) { + // network announce our unit registration table if we have one + if (m_affiliations->unitRegSize() > 0) { + auto regs = m_affiliations->unitRegTable(); + m_network->announceUnitRegUpdate(regs); + } + + // network announce our affiliation table if we have one if (m_affiliations->grpAffSize() > 0) { auto affs = m_affiliations->grpAffTable(); m_network->announceAffiliationUpdate(affs); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 96e86cdc..f982929f 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017,2018 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -458,6 +458,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_controlChData = controlChData; + bool disableGrpAffTimeout = p25Protocol["disableGrpAffTimeout"].as(false); + m_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); bool disableUnitRegTimeout = p25Protocol["disableUnitRegTimeout"].as(false); m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); @@ -573,6 +575,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Notify VCs of Active TGs: %s", m_ccNotifyActiveTG ? "yes" : "no"); + if (disableGrpAffTimeout) { + LogInfo(" Disable Group Affiliation Timeout: yes"); + } + if (disableUnitRegTimeout) { LogInfo(" Disable Unit Registration Timeout: yes"); } @@ -1100,6 +1106,13 @@ void Control::clockSiteData(uint32_t ms) if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { m_control->writeAdjSSNetwork(); if (m_network != nullptr) { + // network announce our unit registration table if we have one + if (m_affiliations->unitRegSize() > 0) { + auto regs = m_affiliations->unitRegTable(); + m_network->announceUnitRegUpdate(regs); + } + + // network announce our affiliation table if we have one if (m_affiliations->grpAffSize() > 0) { auto affs = m_affiliations->grpAffTable(); m_network->announceAffiliationUpdate(affs);