From 21a4a97ec34b271b08e7eeae2f86ea77132c1a4d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 9 Jan 2024 22:22:18 -0500 Subject: [PATCH] ensure if the network changes the destination ID for P25 and NXDN in-flight the LC data is properly replaced; implement talkgroup mutation by peer on conference bridge FNE; --- configs/talkgroup_rules.example.yml | 41 +++++ src/common/lookups/TalkgroupRulesLookup.cpp | 54 +++++- src/common/lookups/TalkgroupRulesLookup.h | 63 ++++++- src/common/network/UDPSocket.h | 1 - src/fne/HostFNE.cpp | 8 +- src/fne/network/fne/TagDMRData.cpp | 166 +++++++++++++++--- src/fne/network/fne/TagDMRData.h | 9 +- src/fne/network/fne/TagNXDNData.cpp | 105 ++++++++++-- src/fne/network/fne/TagNXDNData.h | 9 +- src/fne/network/fne/TagP25Data.cpp | 176 +++++++++++++++++--- src/fne/network/fne/TagP25Data.h | 9 +- src/host/nxdn/packet/Data.cpp | 7 + src/host/nxdn/packet/Voice.cpp | 7 + src/host/p25/packet/ControlSignaling.cpp | 19 +-- src/host/p25/packet/Voice.cpp | 7 + 15 files changed, 583 insertions(+), 98 deletions(-) diff --git a/configs/talkgroup_rules.example.yml b/configs/talkgroup_rules.example.yml index ed827ef2..269cddad 100644 --- a/configs/talkgroup_rules.example.yml +++ b/configs/talkgroup_rules.example.yml @@ -20,6 +20,8 @@ groupVoice: inclusion: [] # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). exclusion: [] + # List of peer talkgroup mutations. + mutations: [] # # Source Configuration # @@ -43,6 +45,8 @@ groupVoice: inclusion: [] # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). exclusion: [] + # List of peer talkgroup mutations. + mutations: [] # # Source Configuration # @@ -52,6 +56,37 @@ groupVoice: # DMR slot number. slot: 1 + # Textual name of the talkgroup. + - name: Parrot Mutation Example + # + # Talkgroup Configuration + # + config: + # Flag indicating whether this talkgroup is active or not. + active: true + # Flag indicating whether or not this talkgroup is a parrot talkgroup. + parrot: true + # List of peer IDs included for this talkgroup (peers listed here will be selected for traffic). + inclusion: [] + # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). + exclusion: [] + # List of peer talkgroup mutations. + mutations: + # Network Peer ID to mutate. + - peerid: 9000990 + # Numerical talkgroup ID number. + tgid: 9999 + # DMR slot number. + slot: 1 + # + # Source Configuration + # + source: + # Numerical talkgroup ID number. + tgid: 9999 + # DMR slot number. + slot: 1 + # Textual name of the talkgroup. - name: System Wide P25 # @@ -64,6 +99,8 @@ groupVoice: inclusion: [] # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). exclusion: [] + # List of peer talkgroup mutations. + mutations: [] # # Source Configuration # @@ -85,6 +122,8 @@ groupVoice: inclusion: [] # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). exclusion: [] + # List of peer talkgroup mutations. + mutations: [] # # Source Configuration # @@ -106,6 +145,8 @@ groupVoice: inclusion: [] # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). exclusion: [] + # List of peer talkgroup mutations. + mutations: [] # # Source Configuration # diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index f3a4dff3..6479bba5 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -244,14 +244,58 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::find(uint32_t id, uint8_t slot) m_mutex.lock(); { - auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), - [&](TalkgroupRuleGroupVoice x) - { + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), + [&](TalkgroupRuleGroupVoice x) + { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; } - return x.source().tgId() == id; + return x.source().tgId() == id; + }); + if (it != m_groupVoice.end()) { + entry = *it; + } else { + entry = TalkgroupRuleGroupVoice(); + } + } + m_mutex.unlock(); + + return entry; +} + +/// +/// Finds a table entry in this lookup table. +/// +/// Unique identifier for table entry. +/// Unique identifier for table entry. +/// DMR slot this talkgroup is valid on. +/// Table entry. +TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByMutation(uint32_t peerId, uint32_t id, uint8_t slot) +{ + TalkgroupRuleGroupVoice entry; + + m_mutex.lock(); + { + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), + [&](TalkgroupRuleGroupVoice x) + { + if (x.config().mutation().size() == 0) + return false; + + auto innerIt = std::find_if(x.config().mutation().begin(), x.config().mutation().end(), + [&](TalkgroupRuleMutation y) + { + if (slot != 0U) { + return y.peerId() == peerId && y.tgId() == id && y.tgSlot() == slot; + } + + return y.peerId() == peerId && y.tgId() == id; + }); + + if (innerIt != x.config().mutation().end()) + return true; + return false; }); if (it != m_groupVoice.end()) { entry = *it; diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index 500d9f61..55409403 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,6 +79,52 @@ namespace lookups __PROPERTY_PLAIN(uint8_t, tgSlot, tgSlot); }; + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an mutation block for a routing rule. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupRuleMutation { + public: + /// Initializes a new insatnce of the TalkgroupRuleMutation class. + TalkgroupRuleMutation() : + m_peerId(0U), + m_tgId(0U), + m_tgSlot(0U) + { + /* stub */ + } + /// Initializes a new insatnce of the TalkgroupRuleMutation class. + /// + TalkgroupRuleMutation(yaml::Node& node) : + TalkgroupRuleMutation() + { + m_peerId = node["peerId"].as(0U); + m_tgId = node["tgid"].as(0U); + m_tgSlot = (uint8_t)node["slot"].as(0U); + } + + /// Equals operator. Copies this TalkgroupRuleMutation to another TalkgroupRuleMutation. + virtual TalkgroupRuleMutation& operator=(const TalkgroupRuleMutation& data) + { + if (this != &data) { + m_peerId = data.m_peerId; + m_tgId = data.m_tgId; + m_tgSlot = data.m_tgSlot; + } + + return *this; + } + + public: + /// Peer ID. + __PROPERTY_PLAIN(uint32_t, peerId, peerId); + /// Talkgroup ID. + __PROPERTY_PLAIN(uint32_t, tgId, tgId); + /// Talkgroup DMR slot. + __PROPERTY_PLAIN(uint8_t, tgSlot, tgSlot); + }; + // --------------------------------------------------------------------------- // Class Declaration // Represents an configuration block for a routing rule. @@ -91,7 +137,8 @@ namespace lookups m_active(false), m_parrot(false), m_inclusion(), - m_exclusion() + m_exclusion(), + m_mutation() { /* stub */ } @@ -118,6 +165,14 @@ namespace lookups m_exclusion.push_back(peerId); } } + + yaml::Node& mutationList = node["mutations"]; + if (mutationList.size() > 0U) { + for (size_t i = 0; i < mutationList.size(); i++) { + TalkgroupRuleMutation mutation = TalkgroupRuleMutation(mutationList[i]); + m_mutation.push_back(mutation); + } + } } /// Equals operator. Copies this TalkgroupRuleConfig to another TalkgroupRuleConfig. @@ -142,6 +197,8 @@ namespace lookups __PROPERTY_PLAIN(std::vector, inclusion, inclusion); /// List of peer IDs excluded by this rule. __PROPERTY_PLAIN(std::vector, exclusion, exclusion); + /// List of mutations performed by this rule. + __PROPERTY_PLAIN(std::vector, mutation, mutation); }; // --------------------------------------------------------------------------- @@ -230,6 +287,8 @@ namespace lookups void eraseEntry(uint32_t id, uint8_t slot); /// Finds a table entry in this lookup table. virtual TalkgroupRuleGroupVoice find(uint32_t id, uint8_t slot = 0U); + /// Finds a table entry in this lookup table by mutation. + virtual TalkgroupRuleGroupVoice findByMutation(uint32_t peerId, uint32_t id, uint8_t slot = 0U); /// Flag indicating whether talkgroup ID access control is enabled or not. bool getACL(); diff --git a/src/common/network/UDPSocket.h b/src/common/network/UDPSocket.h index afb7aa3a..7cdcf856 100644 --- a/src/common/network/UDPSocket.h +++ b/src/common/network/UDPSocket.h @@ -91,7 +91,6 @@ namespace network sockaddr_storage address; uint32_t addrLen; - }; /* Vector of buffers that contain a full frames */ diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index e8edf223..891000bc 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -431,7 +431,7 @@ void HostFNE::processPeer(network::Network* peerNetwork) uint32_t slotNo = (data[15U] & 0x80U) == 0x80U ? 2U : 1U; uint32_t streamId = peerNetwork->getDMRStreamId(slotNo); - m_network->dmrTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId); + m_network->dmrTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); } } @@ -444,7 +444,7 @@ void HostFNE::processPeer(network::Network* peerNetwork) uint32_t peerId = peerNetwork->getPeerId(); uint32_t streamId = peerNetwork->getP25StreamId(); - m_network->p25TrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId); + m_network->p25TrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); } } @@ -457,7 +457,7 @@ void HostFNE::processPeer(network::Network* peerNetwork) uint32_t peerId = peerNetwork->getPeerId(); uint32_t streamId = peerNetwork->getNXDNStreamId(); - m_network->nxdnTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId); + m_network->nxdnTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); } } } \ No newline at end of file diff --git a/src/fne/network/fne/TagDMRData.cpp b/src/fne/network/fne/TagDMRData.cpp index 2ad28a8b..6eb7eb0c 100644 --- a/src/fne/network/fne/TagDMRData.cpp +++ b/src/fne/network/fne/TagDMRData.cpp @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "Defines.h" +#include "common/dmr/lc/LC.h" +#include "common/dmr/lc/FullLC.h" #include "common/Clock.h" #include "common/Log.h" #include "common/StopWatch.h" @@ -35,6 +37,7 @@ using namespace system_clock; using namespace network; using namespace network::fne; +using namespace dmr; #include #include @@ -76,23 +79,28 @@ TagDMRData::~TagDMRData() /// Peer ID /// /// Stream ID +/// Flag indicating whether this traffic is from a peer connection or not. /// -bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromPeer) { hrc::hrc_t pktTime = hrc::now(); + uint8_t buffer[len]; + ::memset(buffer, 0x00U, len); + ::memcpy(buffer, data, len); + uint8_t seqNo = data[4U]; uint32_t srcId = __GET_UINT16(data, 5U); uint32_t dstId = __GET_UINT16(data, 8U); - uint8_t flco = (data[15U] & 0x40U) == 0x40U ? dmr::FLCO_PRIVATE : dmr::FLCO_GROUP; + uint8_t flco = (data[15U] & 0x40U) == 0x40U ? FLCO_PRIVATE : FLCO_GROUP; uint32_t slotNo = (data[15U] & 0x80U) == 0x80U ? 2U : 1U; uint8_t dataType = data[15U] & 0x0FU; - dmr::data::Data dmrData; + data::Data dmrData; dmrData.setSeqNo(seqNo); dmrData.setSlotNo(slotNo); dmrData.setSrcId(srcId); @@ -109,16 +117,22 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } else if (voiceSync) { dmrData.setData(data + 20U); - dmrData.setDataType(dmr::DT_VOICE_SYNC); + dmrData.setDataType(DT_VOICE_SYNC); dmrData.setN(0U); } else { uint8_t n = data[15U] & 0x0FU; dmrData.setData(data + 20U); - dmrData.setDataType(dmr::DT_VOICE); + dmrData.setDataType(DT_VOICE); dmrData.setN(n); } + // is this data from a peer connection? + if (fromPeer) { + // perform TGID mutations if configured + mutateBuffer(buffer, peerId, dmrData, dataType, dstId, slotNo, false); + } + // is the stream valid? if (validate(peerId, dmrData, streamId)) { // is this peer ignored? @@ -127,7 +141,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // is this the end of the call stream? - if (dataSync && (dataType == dmr::DT_TERMINATOR_WITH_LC)) { + if (dataSync && (dataType == DT_TERMINATOR_WITH_LC)) { RxStatus status; auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); if (it == m_status.end()) { @@ -160,7 +174,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // is this a new call stream? - if (dataSync && (dataType == dmr::DT_VOICE_LC_HEADER)) { + if (dataSync && (dataType == DT_VOICE_LC_HEADER)) { auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); if (it != m_status.end()) { RxStatus status = it->second; @@ -203,7 +217,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); if (tg.config().parrot()) { uint8_t* copy = new uint8_t[len]; - ::memcpy(copy, data, len); + ::memcpy(copy, buffer, len); m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq, streamId)); } @@ -215,7 +229,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId continue; } - m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, buffer, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u", peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId); @@ -227,7 +241,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // repeat traffic to upstream peers - if (m_network->m_host->m_peerNetworks.size() > 0) { + if (m_network->m_host->m_peerNetworks.size() > 0 && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t peerId = peer.second->getPeerId(); @@ -236,7 +250,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId continue; } - peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId); + uint8_t outboundPeerBuffer[len]; + ::memset(outboundPeerBuffer, 0x00U, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID mutations if configured + mutateBuffer(outboundPeerBuffer, peerId, dmrData, dataType, dstId, slotNo); + + peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); } } @@ -278,6 +299,111 @@ void TagDMRData::playbackParrot() // Private Class Members // --------------------------------------------------------------------------- +/// +/// Helper to mutate the network data buffer. +/// +/// +/// Peer ID +/// +/// +/// +/// +/// +void TagDMRData::mutateBuffer(uint8_t* buffer, uint32_t peerId, dmr::data::Data dmrData, uint8_t dataType, uint32_t dstId, uint32_t slotNo, bool outbound) +{ + uint32_t mutatedDstId = dstId; + uint32_t mutatedSlotNo = slotNo; + + // does the data require mutation? + if (peerMutate(peerId, mutatedDstId, mutatedSlotNo, outbound)) { + // rewrite destination TGID in the frame + __SET_UINT16(mutatedDstId, buffer, 8U); + + // set or clear the e.Slot flag (if 0x80 is set Slot 2 otherwise Slot 1) + if (mutatedSlotNo == 2 && (buffer[15U] & 0x80U) == 0x00U) + buffer[15U] |= 0x80; + if (mutatedSlotNo == 1 && (buffer[15U] & 0x80U) == 0x80U) + buffer[15U] = buffer[15U] & ~0x80U; + + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + dmrData.getData(data + 2U); + + if (dataType == DT_VOICE_LC_HEADER || + dataType == DT_TERMINATOR_WITH_LC) { + // decode and reconstruct embedded DMR data + lc::FullLC fullLC; + std::unique_ptr lc = fullLC.decode(data + 2U, dataType); + if (lc == nullptr) { + LogWarning(LOG_NET, "DMR Slot %u, bad LC received from the network, replacing", slotNo); + lc = new_unique(lc::LC, dmrData.getFLCO(), dmrData.getSrcId(), mutatedDstId); + } + + lc->setDstId(mutatedDstId); + + // Regenerate the LC data + fullLC.encode(*lc, data + 2U, dataType); + dmrData.setData(data + 2U); + } + else if (dataType == DT_VOICE_PI_HEADER) { + // decode and reconstruct embedded DMR data + lc::FullLC fullLC; + std::unique_ptr lc = fullLC.decodePI(data + 2U); + if (lc == nullptr) { + LogWarning(LOG_NET, "DMR Slot %u, DT_VOICE_PI_HEADER, bad LC received, replacing", slotNo); + lc = new_unique(lc::PrivacyLC); + } + + lc->setDstId(mutatedDstId); + + // Regenerate the LC data + fullLC.encodePI(*lc, data + 2U); + dmrData.setData(data + 2U); + } + + dmrData.getData(buffer + 20U); + } +} + +/// +/// Helper to mutate destination ID and slot. +/// +/// Peer ID +/// +/// +/// +bool TagDMRData::peerMutate(uint32_t peerId, uint32_t& dstId, uint32_t& slotNo, bool outbound) +{ + lookups::TalkgroupRuleGroupVoice tg; + if (outbound) { + tg = m_network->m_tidLookup->find(dstId, slotNo); + } + else { + tg = m_network->m_tidLookup->findByMutation(peerId, dstId, slotNo); + } + + std::vector mutations = tg.config().mutation(); + + bool mutated = false; + if (mutations.size() > 0) { + for (auto entry : mutations) { + if (entry.peerId() == peerId) { + if (outbound) { + dstId = tg.source().tgId(); + slotNo = tg.source().tgSlot(); + } + else { + dstId = entry.tgId(); + slotNo = entry.tgSlot(); + } + mutated = true; + break; + } + } + } + + return mutated; +} + /// /// Helper to determine if the peer is permitted for traffic. /// @@ -285,16 +411,16 @@ void TagDMRData::playbackParrot() /// /// Stream ID /// -bool TagDMRData::isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_t streamId) +bool TagDMRData::isPeerPermitted(uint32_t peerId, data::Data& data, uint32_t streamId) { // private calls are always permitted - if (data.getDataType() == dmr::FLCO_PRIVATE) { + if (data.getDataType() == FLCO_PRIVATE) { return true; } // is this a group call? - if (data.getDataType() == dmr::FLCO_GROUP) { - lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + if (data.getDataType() == FLCO_GROUP) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId(), data.getSlotNo()); std::vector inclusion = tg.config().inclusion(); std::vector exclusion = tg.config().exclusion(); @@ -326,7 +452,7 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_ /// /// Stream ID /// -bool TagDMRData::validate(uint32_t peerId, dmr::data::Data& data, uint32_t streamId) +bool TagDMRData::validate(uint32_t peerId, data::Data& data, uint32_t streamId) { // is the source ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(data.getSrcId()); @@ -337,11 +463,11 @@ bool TagDMRData::validate(uint32_t peerId, dmr::data::Data& data, uint32_t strea } // always validate a terminator if the source is valid - if (data.getDataType() == dmr::DT_TERMINATOR_WITH_LC) + if (data.getDataType() == DT_TERMINATOR_WITH_LC) return true; // is this a private call? - if (data.getDataType() == dmr::FLCO_PRIVATE) { + if (data.getDataType() == FLCO_PRIVATE) { // is the destination ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(data.getDstId()); if (!rid.radioDefault()) { @@ -352,7 +478,7 @@ bool TagDMRData::validate(uint32_t peerId, dmr::data::Data& data, uint32_t strea } // is this a group call? - if (data.getDataType() == dmr::FLCO_GROUP) { + if (data.getDataType() == FLCO_GROUP) { lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); // check the DMR slot number diff --git a/src/fne/network/fne/TagDMRData.h b/src/fne/network/fne/TagDMRData.h index 34ea737f..ccf9444e 100644 --- a/src/fne/network/fne/TagDMRData.h +++ b/src/fne/network/fne/TagDMRData.h @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,7 +51,7 @@ namespace network ~TagDMRData(); /// Process a data frame from the network. - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromPeer = false); /// Helper to playback a parrot frame to the network. void playbackParrot(); @@ -77,6 +77,11 @@ namespace network bool m_debug; + /// Helper to mutate the network data buffer. + void mutateBuffer(uint8_t* buffer, uint32_t peerId, dmr::data::Data dmrData, uint8_t dataType, uint32_t dstId, uint32_t slotNo, bool outbound = true); + /// Helper to mutate destination ID and slot. + bool peerMutate(uint32_t peerId, uint32_t& dstId, uint32_t& slotNo, bool outbound = true); + /// Helper to determine if the peer is permitted for traffic. bool isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_t streamId); /// Helper to validate the DMR call stream. diff --git a/src/fne/network/fne/TagNXDNData.cpp b/src/fne/network/fne/TagNXDNData.cpp index 6f9474f7..b5326a73 100644 --- a/src/fne/network/fne/TagNXDNData.cpp +++ b/src/fne/network/fne/TagNXDNData.cpp @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,6 +36,7 @@ using namespace system_clock; using namespace network; using namespace network::fne; +using namespace nxdn; #include #include @@ -77,17 +78,22 @@ TagNXDNData::~TagNXDNData() /// Peer ID /// /// Stream ID +/// Flag indicating whether this traffic is from a peer connection or not. /// -bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromPeer) { hrc::hrc_t pktTime = hrc::now(); + uint8_t buffer[len]; + ::memset(buffer, 0x00U, len); + ::memcpy(buffer, data, len); + uint8_t messageType = data[4U]; uint32_t srcId = __GET_UINT16(data, 5U); uint32_t dstId = __GET_UINT16(data, 8U); - nxdn::lc::RTCH lc; + lc::RTCH lc; lc.setMessageType(messageType); lc.setSrcId((uint16_t)srcId & 0xFFFFU); @@ -96,6 +102,12 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI bool group = (data[15U] & 0x40U) == 0x40U ? false : true; lc.setGroup(group); + // is this data from a peer connection? + if (fromPeer) { + // perform TGID mutations if configured + mutateBuffer(buffer, peerId, messageType, dstId, false); + } + // is the stream valid? if (validate(peerId, lc, messageType, streamId)) { // is this peer ignored? @@ -104,11 +116,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } // specifically only check the following logic for end of call, voice or data frames - if ((messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL || messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX) || - (messageType == nxdn::RTCH_MESSAGE_TYPE_VCALL || messageType == nxdn::RTCH_MESSAGE_TYPE_DCALL_HDR || - messageType == nxdn::RTCH_MESSAGE_TYPE_DCALL_DATA)) { + if ((messageType == RTCH_MESSAGE_TYPE_TX_REL || messageType == RTCH_MESSAGE_TYPE_TX_REL_EX) || + (messageType == RTCH_MESSAGE_TYPE_VCALL || messageType == RTCH_MESSAGE_TYPE_DCALL_HDR || + messageType == RTCH_MESSAGE_TYPE_DCALL_DATA)) { // is this the end of the call stream? - if (messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL || messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX) { + if (messageType == RTCH_MESSAGE_TYPE_TX_REL || messageType == RTCH_MESSAGE_TYPE_TX_REL_EX) { RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); @@ -132,7 +144,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } // is this a new call stream? - if ((messageType != nxdn::RTCH_MESSAGE_TYPE_TX_REL && messageType != nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX)) { + if ((messageType != RTCH_MESSAGE_TYPE_TX_REL && messageType != RTCH_MESSAGE_TYPE_TX_REL_EX)) { auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; @@ -175,7 +187,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); if (tg.config().parrot()) { uint8_t *copy = new uint8_t[len]; - ::memcpy(copy, data, len); + ::memcpy(copy, buffer, len); m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq, streamId)); } @@ -187,7 +199,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI continue; } - m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, data, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, buffer, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u", peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId); @@ -199,7 +211,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } // repeat traffic to upstream peers - if (m_network->m_host->m_peerNetworks.size() > 0) { + if (m_network->m_host->m_peerNetworks.size() > 0 && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t peerId = peer.second->getPeerId(); @@ -208,7 +220,14 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI continue; } - peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, data, len, pktSeq, streamId); + uint8_t outboundPeerBuffer[len]; + ::memset(outboundPeerBuffer, 0x00U, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID mutations if configured + mutateBuffer(outboundPeerBuffer, peerId, messageType, dstId); + + peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); } } @@ -250,6 +269,62 @@ void TagNXDNData::playbackParrot() // Private Class Members // --------------------------------------------------------------------------- +/// +/// Helper to mutate the network data buffer. +/// +/// +/// Peer ID +/// +/// +/// +void TagNXDNData::mutateBuffer(uint8_t* buffer, uint32_t peerId, uint8_t messageType, uint32_t dstId, bool outbound) +{ + uint32_t mutatedDstId = dstId; + + // does the data require mutation? + if (peerMutate(peerId, mutatedDstId, outbound)) { + // rewrite destination TGID in the frame + __SET_UINT16(mutatedDstId, buffer, 8U); + } +} + +/// +/// Helper to mutate destination ID. +/// +/// Peer ID +/// +/// +bool TagNXDNData::peerMutate(uint32_t peerId, uint32_t& dstId, bool outbound) +{ + lookups::TalkgroupRuleGroupVoice tg; + if (outbound) { + tg = m_network->m_tidLookup->find(dstId); + } + else { + tg = m_network->m_tidLookup->findByMutation(peerId, dstId); + } + + std::vector mutations = tg.config().mutation(); + + bool mutated = false; + if (mutations.size() > 0) { + for (auto entry : mutations) { + if (entry.peerId() == peerId) { + if (outbound) { + dstId = tg.source().tgId(); + } + else { + dstId = entry.tgId(); + } + mutated = true; + break; + } + } + } + + return mutated; +} + /// /// Helper to determine if the peer is permitted for traffic. /// @@ -258,7 +333,7 @@ void TagNXDNData::playbackParrot() /// /// Stream ID /// -bool TagNXDNData::isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId) +bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId) { // private calls are always permitted if (!lc.getGroup()) { @@ -300,7 +375,7 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t m /// /// Stream ID /// -bool TagNXDNData::validate(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId) +bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId) { // is the source ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(lc.getSrcId()); @@ -311,7 +386,7 @@ bool TagNXDNData::validate(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageT } // always validate a terminator if the source is valid - if (messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL || messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX) + if (messageType == RTCH_MESSAGE_TYPE_TX_REL || messageType == RTCH_MESSAGE_TYPE_TX_REL_EX) return true; // is this a private call? diff --git a/src/fne/network/fne/TagNXDNData.h b/src/fne/network/fne/TagNXDNData.h index 424429da..53145497 100644 --- a/src/fne/network/fne/TagNXDNData.h +++ b/src/fne/network/fne/TagNXDNData.h @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,7 +51,7 @@ namespace network ~TagNXDNData(); /// Process a data frame from the network. - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromPeer = false); /// Helper to playback a parrot frame to the network. void playbackParrot(); @@ -76,6 +76,11 @@ namespace network bool m_debug; + /// Helper to mutate the network data buffer. + void mutateBuffer(uint8_t* buffer, uint32_t peerId, uint8_t messageType, uint32_t dstId, bool outbound = true); + /// Helper to mutate destination ID. + bool peerMutate(uint32_t peerId, uint32_t& dstId, bool outbound = true); + /// Helper to determine if the peer is permitted for traffic. bool isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId); /// Helper to validate the NXDN call stream. diff --git a/src/fne/network/fne/TagP25Data.cpp b/src/fne/network/fne/TagP25Data.cpp index e401546e..4e9c5d90 100644 --- a/src/fne/network/fne/TagP25Data.cpp +++ b/src/fne/network/fne/TagP25Data.cpp @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "Defines.h" +#include "common/p25/lc/tsbk/TSBKFactory.h" +#include "common/p25/Sync.h" #include "common/Clock.h" #include "common/Log.h" #include "common/StopWatch.h" @@ -79,11 +81,16 @@ TagP25Data::~TagP25Data() /// Peer ID /// /// Stream ID +/// Flag indicating whether this traffic is from a peer connection or not. /// -bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromPeer) { hrc::hrc_t pktTime = hrc::now(); + uint8_t buffer[len]; + ::memset(buffer, 0x00U, len); + ::memcpy(buffer, data, len); + uint8_t lco = data[4U]; uint32_t srcId = __GET_UINT16(data, 5U); @@ -95,34 +102,34 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint8_t lsd2 = data[21U]; uint8_t duid = data[22U]; - uint8_t frameType = p25::P25_FT_DATA_UNIT; + uint8_t frameType = P25_FT_DATA_UNIT; - p25::lc::LC control; - p25::data::LowSpeedData lsd; + lc::LC control; + data::LowSpeedData lsd; // is this a LDU1, is this the first of a call? - if (duid == p25::P25_DUID_LDU1) { + if (duid == P25_DUID_LDU1) { frameType = data[180U]; if (m_debug) { LogDebug(LOG_NET, "P25, frameType = $%02X", frameType); } - if (frameType == p25::P25_FT_HDU_VALID) { + if (frameType == P25_FT_HDU_VALID) { uint8_t algId = data[181U]; uint32_t kid = (data[182U] << 8) | (data[183U] << 0); // copy MI data - uint8_t mi[p25::P25_MI_LENGTH_BYTES]; - ::memset(mi, 0x00U, p25::P25_MI_LENGTH_BYTES); + uint8_t mi[P25_MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, P25_MI_LENGTH_BYTES); - for (uint8_t i = 0; i < p25::P25_MI_LENGTH_BYTES; i++) { + for (uint8_t i = 0; i < P25_MI_LENGTH_BYTES; i++) { mi[i] = data[184U + i]; } if (m_debug) { LogDebug(LOG_NET, "P25, HDU algId = $%02X, kId = $%02X", algId, kid); - Utils::dump(1U, "P25 HDU Network MI", mi, p25::P25_MI_LENGTH_BYTES); + Utils::dump(1U, "P25 HDU Network MI", mi, P25_MI_LENGTH_BYTES); } control.setAlgId(algId); @@ -139,6 +146,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId lsd.setLSD1(lsd1); lsd.setLSD2(lsd2); + // is this data from a peer connection? + if (fromPeer) { + // perform TGID mutations if configured + mutateBuffer(buffer, peerId, duid, dstId, false); + } + // is the stream valid? if (validate(peerId, control, duid, streamId)) { // is this peer ignored? @@ -147,9 +160,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // specifically only check the following logic for end of call or voice frames - if (duid != p25::P25_DUID_TSDU && duid != p25::P25_DUID_PDU) { + if (duid != P25_DUID_TSDU && duid != P25_DUID_PDU) { // is this the end of the call stream? - if ((duid == p25::P25_DUID_TDU) || (duid == p25::P25_DUID_TDULC)) { + if ((duid == P25_DUID_TDU) || (duid == P25_DUID_TDULC)) { RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); @@ -174,11 +187,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // is this a new call stream? - if ((duid != p25::P25_DUID_TDU) && (duid != p25::P25_DUID_TDULC)) { + if ((duid != P25_DUID_TDU) && (duid != P25_DUID_TDULC)) { auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; - if (streamId != status.streamId && ((duid != p25::P25_DUID_TDU) && (duid != p25::P25_DUID_TDULC))) { + if (streamId != status.streamId && ((duid != P25_DUID_TDU) && (duid != P25_DUID_TDULC))) { if (status.srcId != 0U && status.srcId != srcId) { LogWarning(LOG_NET, "P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); return false; @@ -217,7 +230,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); if (tg.config().parrot()) { uint8_t *copy = new uint8_t[len]; - ::memcpy(copy, data, len); + ::memcpy(copy, buffer, len); m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq, streamId, srcId, dstId)); } @@ -229,7 +242,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId continue; } - m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, data, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, buffer, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u", peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId); @@ -241,7 +254,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } // repeat traffic to upstream peers - if (m_network->m_host->m_peerNetworks.size() > 0) { + if (m_network->m_host->m_peerNetworks.size() > 0 && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t peerId = peer.second->getPeerId(); @@ -250,7 +263,14 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId continue; } - peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, data, len, pktSeq, streamId); + uint8_t outboundPeerBuffer[len]; + ::memset(outboundPeerBuffer, 0x00U, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID mutations if configured + mutateBuffer(outboundPeerBuffer, peerId, duid, dstId); + + peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); } } @@ -323,6 +343,108 @@ void TagP25Data::playbackParrot() // Private Class Members // --------------------------------------------------------------------------- +/// +/// Helper to mutate the network data buffer. +/// +/// +/// Peer ID +/// +/// +/// +void TagP25Data::mutateBuffer(uint8_t* buffer, uint32_t peerId, uint8_t duid, uint32_t dstId, bool outbound) +{ + uint32_t srcId = __GET_UINT16(buffer, 5U); + uint32_t frameLength = buffer[23U]; + + uint32_t mutatedDstId = dstId; + + // does the data require mutation? + if (peerMutate(peerId, mutatedDstId, outbound)) { + // rewrite destination TGID in the frame + __SET_UINT16(mutatedDstId, buffer, 8U); + + UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer + 24U, frameLength); + + // are we receiving a TSDU? + if (duid == P25_DUID_TSDU) { + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + if (tsbk != nullptr) { + // handle standard P25 reference opcodes + switch (tsbk->getLCO()) { + case TSBK_IOSP_GRP_VCH: + { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); + + tsbk->setDstId(dstId); + } + break; + } + + // regenerate TSDU + uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate TSBK block + tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU + tsbk->encode(data + 2U); + + if (m_debug) { + LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", + tsbk->getLCO(), tsbk->getMFId(), tsbk->getLastBlock(), tsbk->getAIV(), tsbk->getEX(), tsbk->getSrcId(), tsbk->getDstId(), + tsbk->getSysId(), tsbk->getNetId()); + + Utils::dump(1U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES + 2U, P25_TSBK_FEC_LENGTH_BYTES); + } + + ::memcpy(buffer + 24U, data + 2U, p25::P25_TSDU_FRAME_LENGTH_BYTES); + } + } + } +} + +/// +/// Helper to mutate destination ID. +/// +/// Peer ID +/// +/// +bool TagP25Data::peerMutate(uint32_t peerId, uint32_t& dstId, bool outbound) +{ + lookups::TalkgroupRuleGroupVoice tg; + if (outbound) { + tg = m_network->m_tidLookup->find(dstId); + } + else { + tg = m_network->m_tidLookup->findByMutation(peerId, dstId); + } + + std::vector mutations = tg.config().mutation(); + + bool mutated = false; + if (mutations.size() > 0) { + for (auto entry : mutations) { + if (entry.peerId() == peerId) { + if (outbound) { + dstId = tg.source().tgId(); + } + else { + dstId = entry.tgId(); + } + mutated = true; + break; + } + } + } + + return mutated; +} + /// /// Helper to determine if the peer is permitted for traffic. /// @@ -331,19 +453,19 @@ void TagP25Data::playbackParrot() /// /// Stream ID /// -bool TagP25Data::isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId) +bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, uint8_t duid, uint32_t streamId) { // private calls are always permitted - if (control.getLCO() == p25::LC_PRIVATE) { + if (control.getLCO() == LC_PRIVATE) { return true; } // always permit a TSDU or PDU - if (duid == p25::P25_DUID_TSDU || duid == p25::P25_DUID_PDU) + if (duid == P25_DUID_TSDU || duid == P25_DUID_PDU) return true; // always permit a terminator - if (duid == p25::P25_DUID_TDU || duid == p25::P25_DUID_TDULC) + if (duid == P25_DUID_TDU || duid == P25_DUID_TDULC) return true; // is this a group call? @@ -379,7 +501,7 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t /// /// Stream ID /// -bool TagP25Data::validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId) +bool TagP25Data::validate(uint32_t peerId, lc::LC& control, uint8_t duid, uint32_t streamId) { // is the source ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(control.getSrcId()); @@ -390,15 +512,15 @@ bool TagP25Data::validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, u } // always validate a TSDU or PDU if the source is valid - if (duid == p25::P25_DUID_TSDU || duid == p25::P25_DUID_PDU) + if (duid == P25_DUID_TSDU || duid == P25_DUID_PDU) return true; // always validate a terminator if the source is valid - if (duid == p25::P25_DUID_TDU || duid == p25::P25_DUID_TDULC) + if (duid == P25_DUID_TDU || duid == P25_DUID_TDULC) return true; // is this a private call? - if (control.getLCO() == p25::LC_PRIVATE) { + if (control.getLCO() == LC_PRIVATE) { // is the destination ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); if (!rid.radioDefault()) { diff --git a/src/fne/network/fne/TagP25Data.h b/src/fne/network/fne/TagP25Data.h index 6173ba18..a7380a29 100644 --- a/src/fne/network/fne/TagP25Data.h +++ b/src/fne/network/fne/TagP25Data.h @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -57,7 +57,7 @@ namespace network ~TagP25Data(); /// Process a data frame from the network. - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromPeer = false); /// Helper to playback a parrot frame to the network. void playbackParrot(); @@ -83,6 +83,11 @@ namespace network bool m_debug; + /// Helper to mutate the network data buffer. + void mutateBuffer(uint8_t* buffer, uint32_t peerId, uint8_t duid, uint32_t dstId, bool outbound = true); + /// Helper to mutate destination ID. + bool peerMutate(uint32_t peerId, uint32_t& dstId, bool outbound = true); + /// Helper to determine if the peer is permitted for traffic. bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId); /// Helper to validate the P25 call stream. diff --git a/src/host/nxdn/packet/Data.cpp b/src/host/nxdn/packet/Data.cpp index 5a8ad24c..2b9130d8 100644 --- a/src/host/nxdn/packet/Data.cpp +++ b/src/host/nxdn/packet/Data.cpp @@ -319,6 +319,13 @@ bool Data::processNetwork(uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32 uint16_t srcId = lc.getSrcId(); bool group = lc.getGroup(); + // overwrite the destination ID if the network message header and + // decoded network LC data don't agree (this can happen if the network is dynamically + // altering the destination ID in-flight) + if (lc.getDstId() != netLC.getDstId()) { + lc.setDstId(netLC.getDstId()); + } + if (m_nxdn->m_netState == RS_NET_IDLE) { uint8_t type = lc.getMessageType(); if (type != RTCH_MESSAGE_TYPE_DCALL_HDR) diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 9d28a557..da8c26d0 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -710,6 +710,13 @@ bool Voice::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t bool group = lc.getGroup(); bool encrypted = lc.getEncrypted(); + // overwrite the destination ID if the network message header and + // decoded network LC data don't agree (this can happen if the network is dynamically + // altering the destination ID in-flight) + if (lc.getDstId() != netLC.getDstId()) { + lc.setDstId(netLC.getDstId()); + } + // don't process network frames if this modem isn't authoritative if (!m_nxdn->m_authoritative && m_nxdn->m_permittedDstId != dstId) { if (m_nxdn->m_netState != RS_NET_AUDIO) { diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index ccda0dbd..dc6703d3 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -782,23 +782,6 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr // handle standard P25 reference opcodes switch (tsbk->getLCO()) { case TSBK_IOSP_GRP_VCH: - { - if (m_p25->m_enableControl) { - if (!m_p25->m_affiliations.isGranted(dstId)) { - if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); - } - - uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag - (tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag - (tsbk->getPriority() & 0x07U); // Priority - - writeRF_TSDU_Grant(srcId, dstId, serviceOptions, true, true); - } - } - } - return true; // don't allow this to write to the air case TSBK_IOSP_UU_VCH: { if (m_p25->m_enableControl) { @@ -812,7 +795,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr (tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag (tsbk->getPriority() & 0x07U); // Priority - writeRF_TSDU_Grant(srcId, dstId, serviceOptions, false, true); + writeRF_TSDU_Grant(srcId, dstId, serviceOptions, (tsbk->getLCO() == TSBK_IOSP_GRP_VCH), true); } } } diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index a8aeafe1..e4556a05 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1034,6 +1034,13 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L m_dfsiLC.control()->setNetId(control.getNetId()); m_dfsiLC.control()->setSysId(control.getSysId()); + // overwrite the destination ID if the network message header and + // decoded DFSI data don't agree (this can happen if the network is dynamically + // altering the destination ID in-flight) + if (m_dfsiLC.control()->getDstId() != control.getDstId()) { + m_dfsiLC.control()->setDstId(control.getDstId()); + } + m_netLastLDU1 = control; m_netLastFrameType = frameType;