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;