From 8b001c2937a1d96cffb6766cbc8c930e899b6738 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 13:51:39 -0400 Subject: [PATCH 001/133] implement user-defined adj site mapping to allow fine grained control over neighboring site announcements; --- configs/adj_site_map.example.yml | 14 + configs/fne-config.example.yml | 9 + src/common/lookups/AdjSiteMapLookup.cpp | 288 ++++++++++++++++++++ src/common/lookups/AdjSiteMapLookup.h | 258 ++++++++++++++++++ src/common/lookups/TalkgroupRulesLookup.cpp | 2 +- src/fne/HostFNE.cpp | 14 +- src/fne/HostFNE.h | 2 + src/fne/network/FNENetwork.cpp | 5 +- src/fne/network/FNENetwork.h | 5 +- src/fne/network/callhandler/TagP25Data.cpp | 21 ++ 10 files changed, 614 insertions(+), 4 deletions(-) create mode 100644 configs/adj_site_map.example.yml create mode 100644 src/common/lookups/AdjSiteMapLookup.cpp create mode 100644 src/common/lookups/AdjSiteMapLookup.h diff --git a/configs/adj_site_map.example.yml b/configs/adj_site_map.example.yml new file mode 100644 index 000000000..cc1d0cf52 --- /dev/null +++ b/configs/adj_site_map.example.yml @@ -0,0 +1,14 @@ +# +# Digital Voice Modem - Adj. Site Map +# + +# +# Peer List +# +peers: + # Peer ID. + - peerId: 1234567 + # Flag indicating whether this talkgroup is active or not. + active: true + # List of peer IDs that are neighbors for this peer. + neighbors: [] diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 036a55442..e2999dbd6 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -150,6 +150,15 @@ master: # Amount of time between updates of talkgroup rules file. (minutes) time: 30 + # + # Adj. Site Map Configuration + # + adj_site_map: + # Full path to the Adj. Site Map file. + file: adj_site_map.yml + # Amount of time between updates of Adj. Site Map file. (minutes) + time: 30 + # # External Peers # diff --git a/src/common/lookups/AdjSiteMapLookup.cpp b/src/common/lookups/AdjSiteMapLookup.cpp new file mode 100644 index 000000000..9d5686ff0 --- /dev/null +++ b/src/common/lookups/AdjSiteMapLookup.cpp @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "lookups/AdjSiteMapLookup.h" +#include "Log.h" +#include "Timer.h" +#include "Utils.h" + +using namespace lookups; + +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex AdjSiteMapLookup::m_mutex; +bool AdjSiteMapLookup::m_locked = false; + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +// Lock the table. +#define __LOCK_TABLE() \ + std::lock_guard lock(m_mutex); \ + m_locked = true; + +// Unlock the table. +#define __UNLOCK_TABLE() m_locked = false; + +// Spinlock wait for table to be read unlocked. +#define __SPINLOCK() \ + if (m_locked) { \ + while (m_locked) \ + Thread::sleep(2U); \ + } + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the AdjSiteMapLookup class. */ + +AdjSiteMapLookup::AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime) : Thread(), + m_rulesFile(filename), + m_reloadTime(reloadTime), + m_rules(), + m_stop(false), + m_adjPeerMap() +{ + /* stub */ +} + +/* Finalizes a instance of the AdjSiteMapLookup class. */ + +AdjSiteMapLookup::~AdjSiteMapLookup() = default; + +/* Thread entry point. This function is provided to run the thread for the lookup table. */ + +void AdjSiteMapLookup::entry() +{ + if (m_reloadTime == 0U) { + return; + } + + Timer timer(1U, 60U * m_reloadTime); + timer.start(); + + while (!m_stop) { + sleep(1000U); + + timer.clock(); + if (timer.hasExpired()) { + load(); + timer.start(); + } + } +} + +/* Stops and unloads this lookup table. */ + +void AdjSiteMapLookup::stop(bool noDestroy) +{ + if (m_reloadTime == 0U) { + if (!noDestroy) + delete this; + return; + } + + m_stop = true; + + wait(); +} + +/* Reads the lookup table from the specified lookup table file. */ + +bool AdjSiteMapLookup::read() +{ + bool ret = load(); + + if (m_reloadTime > 0U) + run(); + setName("host:adj-site-map"); + + return ret; +} + +/* Clears all entries from the lookup table. */ + +void AdjSiteMapLookup::clear() +{ + __LOCK_TABLE(); + + m_adjPeerMap.clear(); + + __UNLOCK_TABLE(); +} + +/* Adds a new entry to the lookup table by the specified unique ID. */ + +void AdjSiteMapLookup::addEntry(AdjPeerMapEntry entry) +{ + uint32_t id = entry.peerId(); + + __LOCK_TABLE(); + + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), + [&](AdjPeerMapEntry x) + { + return x.peerId() == id; + }); + if (it != m_adjPeerMap.end()) { + m_adjPeerMap[it - m_adjPeerMap.begin()] = entry; + } + else { + m_adjPeerMap.push_back(entry); + } + + __UNLOCK_TABLE(); +} + +/* Erases an existing entry from the lookup table by the specified unique ID. */ + +void AdjSiteMapLookup::eraseEntry(uint32_t id) +{ + __LOCK_TABLE(); + + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), [&](AdjPeerMapEntry x) { return x.peerId() == id; }); + if (it != m_adjPeerMap.end()) { + m_adjPeerMap.erase(it); + } + + __UNLOCK_TABLE(); +} + +/* Finds a table entry in this lookup table. */ + +AdjPeerMapEntry AdjSiteMapLookup::find(uint32_t id) +{ + AdjPeerMapEntry entry; + + __SPINLOCK(); + + std::lock_guard lock(m_mutex); + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), + [&](AdjPeerMapEntry x) + { + return x.peerId() == id; + }); + if (it != m_adjPeerMap.end()) { + entry = *it; + } else { + entry = AdjPeerMapEntry(); + } + + return entry; +} + +/* Saves loaded talkgroup rules. */ + +bool AdjSiteMapLookup::commit() +{ + return save(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Loads the table from the passed lookup table file. */ + +bool AdjSiteMapLookup::load() +{ + if (m_rulesFile.length() <= 0) { + return false; + } + + try { + bool ret = yaml::Parse(m_rules, m_rulesFile.c_str()); + if (!ret) { + LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s - error parsing YML", m_rulesFile.c_str()); + return false; + } + } + catch (yaml::OperationException const& e) { + LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + return false; + } + + // clear table + clear(); + + __LOCK_TABLE(); + + yaml::Node& peerList = m_rules["peers"]; + + if (peerList.size() == 0U) { + ::LogError(LOG_HOST, "No adj site map peer list defined!"); + m_locked = false; + return false; + } + + for (size_t i = 0; i < peerList.size(); i++) { + AdjPeerMapEntry entry = AdjPeerMapEntry(peerList[i]); + m_adjPeerMap.push_back(entry); + } + + __UNLOCK_TABLE(); + + size_t size = m_adjPeerMap.size(); + if (size == 0U) { + return false; + } + + LogInfoEx(LOG_HOST, "Loaded %lu entries into adjacent site map table", size); + + return true; +} + +/* Saves the table to the passed lookup table file. */ + +bool AdjSiteMapLookup::save() +{ + // Make sure file is valid + if (m_rulesFile.length() <= 0) { + return false; + } + + std::lock_guard lock(m_mutex); + + // New list for our new group voice rules + yaml::Node peerList; + yaml::Node newRules; + + for (auto entry : m_adjPeerMap) { + yaml::Node& gv = peerList.push_back(); + entry.getYaml(gv); + } + + // Set the new rules + newRules["peers"] = peerList; + + // Make sure we actually did stuff right + if (newRules["peers"].size() != m_adjPeerMap.size()) { + LogError(LOG_HOST, "Generated YAML node for group lists did not match loaded map size! (%u != %u)", newRules["peers"].size(), m_adjPeerMap.size()); + return false; + } + + try { + LogMessage(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str()); + yaml::Serialize(newRules, m_rulesFile.c_str()); + LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str()); + } + catch (yaml::OperationException const& e) { + LogError(LOG_HOST, "Cannot save the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + return false; + } + + return true; +} diff --git a/src/common/lookups/AdjSiteMapLookup.h b/src/common/lookups/AdjSiteMapLookup.h new file mode 100644 index 000000000..ca196f6cf --- /dev/null +++ b/src/common/lookups/AdjSiteMapLookup.h @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup lookups_asm Adjacent Site Map Lookups + * @brief Implementation for adjacent site map lookup tables. + * @ingroup lookups + * + * @file AdjSiteMapLookup.h + * @ingroup lookups_asm + * @file AdjSiteMapLookup.cpp + * @ingroup lookups_asm + */ +#if !defined(__ADJ_SITE_MAP_LOOKUP_H__) +#define __ADJ_SITE_MAP_LOOKUP_H__ + +#include "common/Defines.h" +#include "common/lookups/LookupTable.h" +#include "common/yaml/Yaml.h" +#include "common/Utils.h" + +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents an adjacent peer map entry. + * @ingroup lookups_asm + */ + class HOST_SW_API AdjPeerMapEntry { + public: + /** + * @brief Initializes a new instance of the AdjPeerMapEntry class. + */ + AdjPeerMapEntry() : + m_active(false), + m_peerId(0U), + m_neighbors() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the AdjPeerMapEntry class. + * @param node YAML node for this configuration block. + */ + AdjPeerMapEntry(yaml::Node& node) : + AdjPeerMapEntry() + { + m_active = node["active"].as(false); + m_peerId = node["peer_id"].as(0U); + + yaml::Node& neighborList = node["neighbors"]; + if (neighborList.size() > 0U) { + for (size_t i = 0; i < neighborList.size(); i++) { + uint32_t peerId = neighborList[i].as(0U); + m_neighbors.push_back(peerId); + } + } + } + + /** + * @brief Equals operator. Copies this AdjPeerMapEntry to another AdjPeerMapEntry. + * @param data Instance of AdjPeerMapEntry to copy. + */ + virtual AdjPeerMapEntry& operator= (const AdjPeerMapEntry& data) + { + if (this != &data) { + m_active = data.m_active; + m_peerId = data.m_peerId; + m_neighbors = data.m_neighbors; + } + + return *this; + } + + /** + * @brief Gets the count of neighbors. + * @returns uint8_t Total count of peer neighbors. + */ + uint8_t neighborSize() const { return m_neighbors.size(); } + + /** + * @brief Helper to quickly determine if a entry is valid. + * @returns bool True, if entry is valid, otherwise false. + */ + bool isEmpty() const + { + if (m_neighbors.size() > 0U) + return true; + return false; + } + + /** + * @brief Return the YAML structure for this TalkgroupRuleConfig. + * @param[out] node YAML node. + */ + void getYaml(yaml::Node &node) + { + // We have to convert the bools back to strings to pass to the yaml node + node["active"] = __BOOL_STR(m_active); + node["peerId"] = __INT_STR(m_peerId); + + // Get the lists + yaml::Node neighborList; + if (m_neighbors.size() > 0U) { + for (auto neighbor : m_neighbors) { + yaml::Node& newNeighbor = neighborList.push_back(); + newNeighbor = __INT_STR(neighbor); + } + } + } + + public: + /** + * @brief Flag indicating whether the rule is active. + */ + DECLARE_PROPERTY_PLAIN(bool, active); + /** + * @brief Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, peerId); + + /** + * @brief List of neighbor peers. + */ + DECLARE_PROPERTY_PLAIN(std::vector, neighbors); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a threading lookup table class that contains adjacent site map + * information. + * @ingroup lookups_asm + */ + class HOST_SW_API AdjSiteMapLookup : public Thread { + public: + /** + * @brief Initializes a new instance of the AdjSiteMapLookup class. + * @param filename Full-path to the routing rules file. + * @param reloadTime Interval of time to reload the routing rules. + */ + AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime); + /** + * @brief Finalizes a instance of the AdjSiteMapLookup class. + */ + ~AdjSiteMapLookup() override; + + /** + * @brief Thread entry point. This function is provided to run the thread + * for the lookup table. + */ + void entry() override; + + /** + * @brief Stops and unloads this lookup table. + * @param noDestroy Flag indicating the lookup table should remain resident in memory after stopping. + */ + void stop(bool noDestroy = false); + /** + * @brief Reads the lookup table from the specified lookup table file. + * (NOTE: If the reload time for this lookup table is set to 0, a call to stop will also delete the object.) + * @returns bool True, if lookup table was read, otherwise false. + */ + bool read(); + /** + * @brief Reads the lookup table from the specified lookup table file. + * @returns bool True, if lookup table was read, otherwise false. + */ + bool reload() { return load(); } + /** + * @brief Clears all entries from the lookup table. + */ + void clear(); + + /** + * @brief Adds a new entry to the lookup table. + * @param entry Peer map entry. + */ + void addEntry(AdjPeerMapEntry entry); + /** + * @brief Erases an existing entry from the lookup table by the specified unique ID. + * @param id Unique ID to erase. + */ + void eraseEntry(uint32_t id); + /** + * @brief Finds a table entry in this lookup table. + * @param id Unique identifier for table entry. + * @returns AdjPeerMapEntry Table entry. + */ + virtual AdjPeerMapEntry find(uint32_t id); + + /** + * @brief Saves loaded talkgroup rules. + */ + bool commit(); + + /** + * @brief Returns the filename used to load this lookup table. + * @return std::string Full-path to the lookup table file. + */ + const std::string filename() { return m_rulesFile; }; + /** + * @brief Sets the filename used to load this lookup table. + * @param filename Full-path to the routing rules file. + */ + void filename(std::string filename) { m_rulesFile = filename; }; + + /** + * @brief Helper to set the reload time of this lookup table. + * @param reloadTime Lookup time in seconds. + */ + void setReloadTime(uint32_t reloadTime) { m_reloadTime = reloadTime; } + + private: + std::string m_rulesFile; + uint32_t m_reloadTime; + yaml::Node m_rules; + + bool m_stop; + + static std::mutex m_mutex; //! Mutex used for change locking. + static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + /** + * @brief Loads the table from the passed lookup table file. + * @return True, if lookup table was loaded, otherwise false. + */ + bool load(); + /** + * @brief Saves the table to the passed lookup table file. + * @return True, if lookup table was saved, otherwise false. + */ + bool save(); + + public: + /** + * @brief List of adjacent site map entries. + */ + DECLARE_PROPERTY_PLAIN(std::vector, adjPeerMap); + }; +} // namespace lookups + +#endif // __ADJ_SITE_MAP_LOOKUP_H__ diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index 90fa01484..5e6d175cd 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -420,7 +420,7 @@ bool TalkgroupRulesLookup::save() LogDebug(LOG_HOST, "Saved TGID config file to %s", m_rulesFile.c_str()); } catch (yaml::OperationException const& e) { - LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + LogError(LOG_HOST, "Cannot save the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); return false; } diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index f42e9d945..593c05097 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -388,6 +388,10 @@ bool HostFNE::readParams() std::string talkgroupConfig = talkgroupRules["file"].as(); uint32_t talkgroupConfigReload = talkgroupRules["time"].as(30U); + yaml::Node adjSiteMapRules = masterConf["adj_site_map"]; + std::string adjSiteMapConfig = adjSiteMapRules["file"].as(); + uint32_t adjSiteMapReload = adjSiteMapRules["time"].as(30U); + yaml::Node cryptoContainer = masterConf["crypto_container"]; bool cryptoContainerEnabled = cryptoContainer["enable"].as(false); #if !defined(ENABLE_SSL) @@ -420,6 +424,14 @@ bool HostFNE::readParams() m_peerListLookup = new PeerListLookup(peerListLookupFile, peerListConfigReload, peerListLookupEnable); m_peerListLookup->read(); + LogInfo("Adjacent Site Map Lookups"); + LogInfo(" File: %s", adjSiteMapConfig.length() > 0U ? adjSiteMapConfig.c_str() : "None"); + if (adjSiteMapReload > 0U) + LogInfo(" Reload: %u mins", adjSiteMapReload); + + m_adjSiteMapLookup = new AdjSiteMapLookup(adjSiteMapConfig, adjSiteMapReload); + m_adjSiteMapLookup->read(); + // try to load peer whitelist/blacklist LogInfo("Crypto Container Lookups"); LogInfo(" Enabled: %s", cryptoContainerEnabled ? "yes" : "no"); @@ -609,7 +621,7 @@ bool HostFNE::createMasterNetwork() parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime, workerCnt); m_network->setOptions(masterConf, true); - m_network->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup); + m_network->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup, m_adjSiteMapLookup); if (m_RESTAPI != nullptr) { m_RESTAPI->setNetwork(m_network); diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index d3e0da486..038dd460a 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -20,6 +20,7 @@ #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" +#include "common/lookups/AdjSiteMapLookup.h" #include "common/network/viface/VIFace.h" #include "common/yaml/Yaml.h" #include "common/Timer.h" @@ -104,6 +105,7 @@ class HOST_SW_API HostFNE { lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; + lookups::AdjSiteMapLookup* m_adjSiteMapLookup; CryptoContainer* m_cryptoLookup; diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 3e75a1a7a..62b82afbe 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -76,6 +76,8 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), + m_adjSiteMapLookup(nullptr), + m_cryptoLookup(nullptr), m_status(NET_STAT_INVALID), m_peers(), m_peerLinkPeers(), @@ -221,12 +223,13 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) /* Sets the instances of the Radio ID, Talkgroup ID Peer List, and Crypto lookup tables. */ void FNENetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::PeerListLookup* peerListLookup, - CryptoContainer* cryptoLookup) + CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; m_peerListLookup = peerListLookup; m_cryptoLookup = cryptoLookup; + m_adjSiteMapLookup = adjSiteMapLookup; } /* Sets endpoint preshared encryption key. */ diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index d19baa64f..9122b53ac 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -32,6 +32,7 @@ #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" +#include "common/lookups/AdjSiteMapLookup.h" #include "common/network/Network.h" #include "common/network/PacketBuffer.h" #include "common/ThreadPool.h" @@ -472,9 +473,10 @@ namespace network * @param tidLookup Talkgroup Rules Lookup Table Instance * @param peerListLookup Peer List Lookup Table Instance * @param cryptoLookup Crypto Container Lookup Table Instance + * @param adjSiteMapLookup Adjacent Site Map Lookup Table Instance */ void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::PeerListLookup* peerListLookup, - CryptoContainer* cryptoLookup); + CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup); /** * @brief Sets endpoint preshared encryption key. * @param presharedKey Encryption preshared key for networking. @@ -549,6 +551,7 @@ namespace network lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; + lookups::AdjSiteMapLookup* m_adjSiteMapLookup; CryptoContainer* m_cryptoLookup; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 766240c3f..1e39d8f7f 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -754,6 +754,27 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X, peerId = %u", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass(), peerId); } + + // check if the sending peer is mapped + lookups::AdjPeerMapEntry adjPeerMap = m_network->m_adjSiteMapLookup->find(peerId); + if (!adjPeerMap.isEmpty()) { + if (!adjPeerMap.active()) { + // LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to other peers is disabled, dropping", peerId); + return false; + } else { + // if the peer is mapped, we can repeat the ADJ_STS_BCAST to other peers + if (m_network->m_peers.size() > 0U) { + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + write_TSDU(peer.first, osp); + } + } + + // this seems strange -- but we want to prevent the main processing loop from repeating the ADJ_STS_BCAST + return false; + } + } + } } } break; From cb175e2cc8eb91f33004686b7d6ec7d4b6e0b026 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 15:09:25 -0400 Subject: [PATCH 002/133] BUGFIX: patch issue where the FNE would not maintain the source peer ID for certain routing scenarios; --- src/common/network/Network.cpp | 2 +- src/fne/network/FNENetwork.cpp | 36 +++++++++++-------- src/fne/network/FNENetwork.h | 5 +-- src/fne/network/callhandler/TagDMRData.cpp | 10 +++--- src/fne/network/callhandler/TagNXDNData.cpp | 8 ++--- src/fne/network/callhandler/TagP25Data.cpp | 14 ++++---- .../callhandler/packetdata/DMRPacketData.cpp | 2 +- .../callhandler/packetdata/P25PacketData.cpp | 18 +++++----- .../callhandler/packetdata/P25PacketData.h | 6 ++-- 9 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 7581c239a..739dd76b9 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -236,7 +236,7 @@ void Network::clock(uint32_t ms) } if (m_debug) { - LogDebugEx(LOG_NET, "Network::clock()", "RTP, peerId = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSequence(), + LogDebugEx(LOG_NET, "Network::clock()", "RTP, peerId = %u, ssrc = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSSRC(), rtpHeader.getSequence(), fneHeader.getStreamId(), fneHeader.getFunction(), fneHeader.getSubFunction()); } diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 3e75a1a7a..87d4ebbc4 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -497,7 +497,8 @@ void FNENetwork::close() uint32_t streamId = createStreamId(); for (auto peer : m_peers) { - writePeer(peer.first, { NET_FUNC::MST_DISC, NET_SUBFUNC::NOP }, buffer, 1U, RTP_END_OF_CALL_SEQ, streamId, false); + writePeer(peer.first, m_peerId, { NET_FUNC::MST_DISC, NET_SUBFUNC::NOP }, buffer, 1U, RTP_END_OF_CALL_SEQ, + streamId, false); } } @@ -1208,7 +1209,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) modifyKeyRsp.encode(buffer + 11U); - network->writePeer(peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, RTP_END_OF_CALL_SEQ, network->createStreamId(), false, false, true); } else { // attempt to forward KMM key request to Peer-Link masters @@ -1810,7 +1811,7 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE LogInfoEx(LOG_NET, "PEER %u Peer-Link, RID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, + writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); Thread::sleep(60U); // pace block transmission } @@ -1999,7 +2000,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP LogInfoEx(LOG_NET, "PEER %u Peer-Link, TGID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, + writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); Thread::sleep(60U); // pace block transmission } @@ -2177,7 +2178,7 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) LogInfoEx(LOG_NET, "PEER %u Peer-Link, PID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, + writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); Thread::sleep(60U); // pace block transmission } @@ -2206,12 +2207,12 @@ bool FNENetwork::writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::E SET_UINT24(dstId, buffer, 11U); // Destination ID buffer[14U] = slotNo; // DMR Slot No - return writePeer(peerId, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false); + return writePeer(peerId, m_peerId, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false); } /* Helper to send a data message to the specified peer with a explicit packet sequence. */ -bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, +bool FNENetwork::writePeer(uint32_t peerId, uint32_t srcPeerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq, bool directWrite) const { if (streamId == 0U) { @@ -2229,10 +2230,17 @@ bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const pktSeq = connection->incStreamPktSeq(streamId, pktSeq); } + if (connection->isExternalPeer() && !connection->isPeerLink()) { + // if the peer is an external peer, and not a Peer-Link peer, we need to send the packet + // to the external peer with our peer ID as the source instead of the originating peer + // because we have routed it + srcPeerId = m_peerId; + } + if (directWrite) - return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, srcPeerId, m_peerId, opcode, pktSeq, addr, addrLen); else { - m_frameQueue->enqueueMessage(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); + m_frameQueue->enqueueMessage(data, length, streamId, srcPeerId, m_peerId, opcode, pktSeq, addr, addrLen); if (queueOnly) return true; return m_frameQueue->flushQueue(); @@ -2259,7 +2267,7 @@ bool FNENetwork::writePeerCommand(uint32_t peerId, FrameQueue::OpcodePair opcode } uint32_t len = length + 6U; - return writePeer(peerId, opcode, buffer, len, RTP_END_OF_CALL_SEQ, streamId, false, incPktSeq, true); + return writePeer(peerId, m_peerId, opcode, buffer, len, RTP_END_OF_CALL_SEQ, streamId, false, incPktSeq, true); } /* Helper to send a ACK response to the specified peer. */ @@ -2275,8 +2283,8 @@ bool FNENetwork::writePeerACK(uint32_t peerId, uint32_t streamId, const uint8_t* ::memcpy(buffer + 6U, data, length); } - return writePeer(peerId, { NET_FUNC::ACK, NET_SUBFUNC::NOP }, buffer, length + 10U, RTP_END_OF_CALL_SEQ, streamId, - false, false, true); + return writePeer(peerId, m_peerId, { NET_FUNC::ACK, NET_SUBFUNC::NOP }, buffer, length + 10U, RTP_END_OF_CALL_SEQ, + streamId, false, false, true); } /* Helper to log a warning specifying which NAK reason is being sent a peer. */ @@ -2332,7 +2340,7 @@ bool FNENetwork::writePeerNAK(uint32_t peerId, uint32_t streamId, const char* ta SET_UINT16((uint16_t)reason, buffer, 10U); // Reason logPeerNAKReason(peerId, tag, reason); - return writePeer(peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 10U, RTP_END_OF_CALL_SEQ, streamId, false); + return writePeer(peerId, m_peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 10U, RTP_END_OF_CALL_SEQ, streamId, false); } /* Helper to send a NAK response to the specified peer. */ @@ -2410,7 +2418,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin modifyKeyRsp.encode(buffer + 11U); - writePeer(peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + writePeer(peerId, m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, RTP_END_OF_CALL_SEQ, createStreamId(), false, false, true); peersToRemove.push_back(peerId); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index d19baa64f..9af040462 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -736,7 +736,8 @@ namespace network /** * @brief Helper to send a data message to the specified peer with a explicit packet sequence. - * @param peerId Peer ID. + * @param peerId Destination Peer ID. + * @param srcPeerId Source Peer ID. * @param opcode FNE network opcode pair. * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. @@ -746,7 +747,7 @@ namespace network * @param incPktSeq Flag indicating the message should increment the packet sequence after transmission. * @param directWrite Flag indicating this message should be immediately directly written. */ - bool writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, + bool writePeer(uint32_t peerId, uint32_t srcPeerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq = false, bool directWrite = false) const; /** diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index e0064539a..110ee86c0 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -316,7 +316,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, dmrData, dataType, dstId, slotNo); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, 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, external = %u", peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); @@ -436,7 +436,7 @@ void TagDMRData::playbackParrot() auto& pkt = m_parrotFrames[0]; if (pkt.buffer != nullptr) { if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -445,7 +445,7 @@ void TagDMRData::playbackParrot() else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -1090,7 +1090,7 @@ void TagDMRData::write_CSBK(uint32_t peerId, uint8_t slot, lc::CSBK* csbk) } if (peerId > 0U) { - m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); } else { // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { @@ -1101,7 +1101,7 @@ void TagDMRData::write_CSBK(uint32_t peerId, uint8_t slot, lc::CSBK* csbk) m_network->m_frameQueue->flushQueue(); } - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); + m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, peer = %u, slotNo = %u, len = %u, stream = %u", peer.first, slot, messageLength, streamId); diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 3efc82f96..dc68eb09c 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -261,7 +261,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, messageType, dstId); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, 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, external = %u", peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); @@ -381,7 +381,7 @@ void TagNXDNData::playbackParrot() auto& pkt = m_parrotFrames[0]; if (pkt.buffer != nullptr) { if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -390,7 +390,7 @@ void TagNXDNData::playbackParrot() else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -865,5 +865,5 @@ void TagNXDNData::write_Message(uint32_t peerId, lc::RCCH* rcch) } uint32_t streamId = m_network->createStreamId(); - m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 766240c3f..edb92afbe 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -349,7 +349,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, duid, dstId); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, 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, external = %u", peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); @@ -493,13 +493,13 @@ void TagP25Data::playbackParrot() if (message != nullptr) { if (m_network->m_parrotOnlyOriginating) { LogMessage(LOG_NET, "P25, Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", pkt.peerId, srcId, dstId); - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_network->createStreamId(), false); } else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { LogMessage(LOG_NET, "P25, Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", peer.first, srcId, dstId); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_network->createStreamId(), false); } } @@ -510,7 +510,7 @@ void TagP25Data::playbackParrot() } if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -518,7 +518,7 @@ void TagP25Data::playbackParrot() } else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -1518,7 +1518,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) uint32_t streamId = m_network->createStreamId(); if (peerId > 0U) { - m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); } else { // repeat traffic to the connected peers @@ -1530,7 +1530,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) m_network->m_frameQueue->flushQueue(); } - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, peer = %u, len = %u, streamId = %u", diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp index e12fc72cb..969f508c9 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp @@ -283,7 +283,7 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, m_network->m_frameQueue->flushQueue(); } - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, slotNo = %u, len = %u, pktSeq = %u, stream = %u", peerId, peer.first, seqNo, srcId, dstId, status->slotNo, len, pktSeq, streamId); diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index 97965f0fb..667eee8bc 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -719,7 +719,7 @@ void P25PacketData::dispatchToFNE(uint32_t peerId) m_network->m_frameQueue->flushQueue(); } - write_PDU_User(peer.first, nullptr, status->header, status->extendedAddress, status->pduUserData, true); + write_PDU_User(peer.first, peerId, nullptr, status->header, status->extendedAddress, status->pduUserData, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peerId, peer.first, DUID::PDU, srcId, dstId); @@ -749,7 +749,7 @@ void P25PacketData::dispatchToFNE(uint32_t peerId) continue; } - write_PDU_User(dstPeerId, peer.second, status->header, status->extendedAddress, status->pduUserData); + write_PDU_User(dstPeerId, peerId, peer.second, status->header, status->extendedAddress, status->pduUserData); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peerId, dstPeerId, DUID::PDU, srcId, dstId); @@ -786,7 +786,7 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo m_network->m_frameQueue->flushQueue(); } - write_PDU_User(peer.first, nullptr, dataHeader, extendedAddress, pduUserData, true); + write_PDU_User(peer.first, m_network->m_peerId, nullptr, dataHeader, extendedAddress, pduUserData, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peer.first, DUID::PDU, srcId, dstId); @@ -1023,7 +1023,7 @@ void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, ui /* Helper to write user data as a P25 PDU packet. */ -void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNet, data::DataHeader& dataHeader, +void P25PacketData::write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool queueOnly) { uint32_t streamId = m_network->createStreamId(); @@ -1042,7 +1042,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe // generate the PDU header and 1/2 rate Trellis dataHeader.encode(buffer); - writeNetwork(peerId, peerNet, dataHeader, 0U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); + writeNetwork(peerId, srcPeerId, peerNet, dataHeader, 0U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); if (pduUserData == nullptr) return; @@ -1060,7 +1060,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); dataHeader.encodeExtAddr(buffer); - writeNetwork(peerId, peerNet, dataHeader, 1U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); + writeNetwork(peerId, srcPeerId, peerNet, dataHeader, 1U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); ++pktSeq; dataOffset += P25_PDU_HEADER_LENGTH_BYTES; @@ -1104,7 +1104,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); dataBlock.encode(buffer); - writeNetwork(peerId, peerNet, dataHeader, networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, (dataBlock.getLastBlock()) ? RTP_END_OF_CALL_SEQ : pktSeq, streamId); + writeNetwork(peerId, srcPeerId, peerNet, dataHeader, networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, (dataBlock.getLastBlock()) ? RTP_END_OF_CALL_SEQ : pktSeq, streamId); ++pktSeq; dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; @@ -1116,7 +1116,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe /* Write data processed to the network. */ -bool P25PacketData::writeNetwork(uint32_t peerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, +bool P25PacketData::writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, const uint8_t *data, uint32_t len, uint16_t pktSeq, uint32_t streamId, bool queueOnly) { assert(data != nullptr); @@ -1130,7 +1130,7 @@ bool P25PacketData::writeNetwork(uint32_t peerId, network::PeerNetwork* peerNet, if (peerNet != nullptr) { return peerNet->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId); } else { - return m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId, false); + return m_network->writePeer(peerId, srcPeerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId, false); } } diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index f33d80970..04d496e36 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -240,17 +240,19 @@ namespace network /** * @brief Helper to write user data as a P25 PDU packet. * @param peerId Peer ID. + * @param srcPeerId Source Peer ID. * @param peerNet Instance of PeerNetwork to use to send traffic. * @param dataHeader Instance of a PDU data header. * @param extendedAddress Flag indicating whether or not to extended addressing is in use. * @param pduUserData Buffer containing user data to transmit. */ - void write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNet, p25::data::DataHeader& dataHeader, + void write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, p25::data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool queueOnly = false); /** * @brief Write data processed to the network. * @param peerId Peer ID. + * @param srcPeerId Source Peer ID. * @param peerNet Instance of PeerNetwork to use to send traffic. * @param dataHeader Instance of a PDU data header. * @param currentBlock Current Block ID. @@ -259,7 +261,7 @@ namespace network * @param pktSeq RTP packet sequence. * @param streamId Stream ID. */ - bool writeNetwork(uint32_t peerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, + bool writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, const uint8_t* data, uint32_t len, uint16_t pktSeq, uint32_t streamId, bool queueOnly = false); /** From f225ac51a98ed03237f58f63faf336141a354f00 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 15:23:28 -0400 Subject: [PATCH 003/133] fix typo in original implementation, peerId should be the peerId and ssrc should become the originating peer ID; remove peer check throwing a warning; --- src/common/network/Network.cpp | 6 ------ src/fne/network/FNENetwork.cpp | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 739dd76b9..6bd771f0c 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -240,12 +240,6 @@ void Network::clock(uint32_t ms) fneHeader.getStreamId(), fneHeader.getFunction(), fneHeader.getSubFunction()); } - // ensure the RTP synchronization source ID matches the FNE peer ID - if (m_remotePeerId != 0U && rtpHeader.getSSRC() != m_remotePeerId) { - LogWarning(LOG_NET, "RTP header and traffic session do not agree on remote peer ID? %u != %u", rtpHeader.getSSRC(), m_remotePeerId); - // should this be a fatal error? - } - // is this RTP packet destined for us? uint32_t peerId = fneHeader.getPeerId(); if ((m_peerId != peerId) && !m_promiscuousPeer) { diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 87d4ebbc4..1ea23df50 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -2238,9 +2238,9 @@ bool FNENetwork::writePeer(uint32_t peerId, uint32_t srcPeerId, FrameQueue::Opco } if (directWrite) - return m_frameQueue->write(data, length, streamId, srcPeerId, m_peerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, peerId, srcPeerId, opcode, pktSeq, addr, addrLen); else { - m_frameQueue->enqueueMessage(data, length, streamId, srcPeerId, m_peerId, opcode, pktSeq, addr, addrLen); + m_frameQueue->enqueueMessage(data, length, streamId, peerId, srcPeerId, opcode, pktSeq, addr, addrLen); if (queueOnly) return true; return m_frameQueue->flushQueue(); From 5e2615fc951a7aa81b7a34651d058764b032ca44 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 15:37:02 -0400 Subject: [PATCH 004/133] log ssrc for various data points (the ssrc is the RTP originating sync source which would be the peer ID of the origination of a RTP packet); --- src/fne/network/FNENetwork.cpp | 7 ++--- src/fne/network/callhandler/TagDMRData.cpp | 30 ++++++++++----------- src/fne/network/callhandler/TagDMRData.h | 3 ++- src/fne/network/callhandler/TagNXDNData.cpp | 24 ++++++++--------- src/fne/network/callhandler/TagNXDNData.h | 3 ++- src/fne/network/callhandler/TagP25Data.cpp | 28 +++++++++---------- src/fne/network/callhandler/TagP25Data.h | 3 ++- 7 files changed, 51 insertions(+), 47 deletions(-) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 1ea23df50..a5f579005 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -547,6 +547,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (req->length > 0) { uint32_t peerId = req->fneHeader.getPeerId(); + uint32_t ssrc = req->rtpHeader.getSSRC(); uint32_t streamId = req->fneHeader.getStreamId(); // determine if this packet is late (i.e. are we processing this packet more than 200ms after it was received?) @@ -615,7 +616,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { if (network->m_dmrEnabled) { if (network->m_tagDMR != nullptr) { - network->m_tagDMR->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagDMR->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); } } else { network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); @@ -641,7 +642,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { if (network->m_p25Enabled) { if (network->m_tagP25 != nullptr) { - network->m_tagP25->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagP25->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); } } else { network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); @@ -667,7 +668,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { if (network->m_nxdnEnabled) { if (network->m_tagNXDN != nullptr) { - network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); } } else { network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 110ee86c0..fce2e83bf 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -63,7 +63,7 @@ TagDMRData::~TagDMRData() /* Process a data frame from the network. */ -bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) { hrc::hrc_t pktTime = hrc::now(); @@ -134,7 +134,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 == DataType::TERMINATOR_WITH_LC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, srcId, dstId, slotNo, streamId, external); + LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, slotNo, streamId, external); return false; } @@ -147,8 +147,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; }); if (it == m_status.end()) { - LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", - peerId, srcId, dstId, slotNo, streamId, external); + LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, external); } else { status = it->second; @@ -172,13 +172,13 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (tg.config().parrot()) { if (m_parrotFrames.size() > 0) { m_parrotFramesReady = true; - LogMessage(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + LogMessage(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); m_network->m_parrotDelayTimer.start(); } } - LogMessage(LOG_NET, "DMR, Call End, peer = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, slotNo, duration / 1000, streamId, external); + LogMessage(LOG_NET, "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -203,7 +203,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if (dataSync && (dataType == DataType::VOICE_LC_HEADER)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "DMR, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } @@ -225,8 +225,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_callInProgress = false; } - LogWarning(LOG_NET, "DMR, Call Collision, peer = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", - peerId, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); + LogWarning(LOG_NET, "DMR, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); return false; } } @@ -256,7 +256,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "DMR, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogMessage(LOG_NET, "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } @@ -318,8 +318,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, 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, external = %u", - peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -361,8 +361,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); 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, external = %u", - peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index 9a20d8cb4..44270cff8 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -56,12 +56,13 @@ namespace network * @param data Network data buffer. * @param len Length of data. * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @param external Flag indicating traffic is from an external peer. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index dc68eb09c..09cb29bb4 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -59,7 +59,7 @@ TagNXDNData::~TagNXDNData() = default; /* Process a data frame from the network. */ -bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) { hrc::hrc_t pktTime = hrc::now(); @@ -104,7 +104,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this the end of the call stream? if (messageType == MessageType::RTCH_TX_REL || messageType == MessageType::RTCH_TX_REL_EX) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } @@ -131,8 +131,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } } - LogMessage(LOG_NET, "NXDN, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, duration / 1000, streamId, external); + LogMessage(LOG_NET, "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -156,7 +156,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this a new call stream? if ((messageType != MessageType::RTCH_TX_REL && messageType != MessageType::RTCH_TX_REL_EX)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } @@ -178,8 +178,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_network->m_callInProgress = false; } - LogWarning(LOG_NET, "NXDN, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + LogWarning(LOG_NET, "NXDN, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } } @@ -207,7 +207,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } @@ -263,8 +263,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, 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, external = %u", - peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -306,8 +306,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); 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, external = %u", - peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 35ec58cdd..5b6ca10eb 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -55,12 +55,13 @@ namespace network * @param data Network data buffer. * @param len Length of data. * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @param external Flag indicating traffic is from an external peer. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index edb92afbe..9cd53c22a 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -65,7 +65,7 @@ TagP25Data::~TagP25Data() /* Process a data frame from the network. */ -bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) { hrc::hrc_t pktTime = hrc::now(); @@ -165,7 +165,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this the end of the call stream? if ((duid == DUID::TDU) || (duid == DUID::TDULC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } @@ -190,8 +190,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_status.end()) { if (grantDemand) { - LogWarning(LOG_NET, "P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } else { @@ -208,8 +208,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - LogMessage(LOG_NET, "P25, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, duration / 1000, streamId, external); + LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -234,7 +234,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if ((duid != DUID::TDU) && (duid != DUID::TDULC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "P25, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); return false; } @@ -256,8 +256,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_callInProgress = false; } - LogWarning(LOG_NET, "P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } } @@ -285,7 +285,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "P25, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } @@ -351,8 +351,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, 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, external = %u", - peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -396,8 +396,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (processTSDUToExternal(outboundPeerBuffer, peerId, dstPeerId, duid)) { peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); 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, external = %u", - peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); } } diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index 324338409..21984ac2e 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -61,12 +61,13 @@ namespace network * @param data Network data buffer. * @param len Length of data. * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @param external Flag indicating traffic is from an external peer. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. From 829bb8680049ee709eb90e568c8ae54c6208ae13 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 15:37:19 -0400 Subject: [PATCH 005/133] bump build number; --- src/common/Defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Defines.h b/src/common/Defines.h index 2c4b29be3..c58243c5f 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -117,7 +117,7 @@ typedef unsigned long long ulong64_t; #define __EXE_NAME__ "" #define VERSION_MAJOR "04" -#define VERSION_MINOR "31" +#define VERSION_MINOR "32" #define VERSION_REV "H" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR From 52997742c87e54e7b351ccfb4f1be3c4c094b1fd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 17:44:38 -0400 Subject: [PATCH 006/133] add peer ID masking support (this is useful for FNEs that are "public" links where internal peer IDs don't need to be retained); more work for maintaining the originating stream sync source; refactor how the FNE handles peer network connections; --- configs/fne-config.example.yml | 4 + src/common/network/BaseNetwork.cpp | 8 +- src/common/network/BaseNetwork.h | 4 +- src/common/network/Network.cpp | 9 +- src/common/network/Network.h | 6 +- src/fne/HostFNE.cpp | 94 ++++++++++++--------- src/fne/HostFNE.h | 31 ++++++- src/fne/network/FNENetwork.cpp | 28 ++++-- src/fne/network/FNENetwork.h | 6 +- src/fne/network/PeerNetwork.cpp | 38 ++++++++- src/fne/network/PeerNetwork.h | 36 +++++++- src/fne/network/callhandler/TagDMRData.cpp | 8 +- src/fne/network/callhandler/TagNXDNData.cpp | 8 +- src/fne/network/callhandler/TagP25Data.cpp | 8 +- src/sysview/network/PeerNetwork.cpp | 3 +- src/sysview/network/PeerNetwork.h | 4 +- 16 files changed, 221 insertions(+), 74 deletions(-) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index e2999dbd6..17e2195ee 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -103,6 +103,10 @@ master: # Flag indicating whether or not a TDULC call terminations will pass to any peers. disallowCallTerm: false + # Flag indicating whether or not the FNE will mask outbound traffic to use the FNE's own peer ID. + # (This is useful for FNEs that are public facing, and the originating traffic peer ID should be masked.) + maskOutboundPeerID: false + # Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID). filterHeaders: true # Flag indicating that terminators will be filtered by destination ID (i.e. valid RID or valid TGID). diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index ead726ed1..9a64ab588 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -363,10 +363,12 @@ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const /* Helper to send a data message to the master. */ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, - bool queueOnly, bool useAlternatePort, uint32_t peerId) + bool queueOnly, bool useAlternatePort, uint32_t peerId, uint32_t ssrc) { if (peerId == 0U) peerId = m_peerId; + if (ssrc == 0U) + ssrc = m_peerId; if (useAlternatePort) { sockaddr_storage addr; @@ -377,14 +379,14 @@ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data if (udp::Socket::lookup(address, port, addr, addrLen) == 0) { if (!queueOnly) - return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); else m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, addr, addrLen); } } else { if (!queueOnly) - return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen); else m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); } diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index ccb2092bc..37f83244a 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -325,10 +325,12 @@ namespace network * @param queueOnly Flag indicating this message should be queued instead of send immediately. * @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics). * @param peerId If non-zero, overrides the peer ID sent in the packet to the master. + * @param ssrc If non-zero, overrides the RTP synchronization source ID sent in the packet to the master. * @returns bool True, if message was sent, otherwise false. */ bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, - uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U); + uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U, + uint32_t ssrc = 0U); // Digital Mobile Radio /** diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 6bd771f0c..d1a112804 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -9,8 +9,6 @@ */ #include "Defines.h" #include "common/edac/SHA256.h" -#include "common/network/RTPHeader.h" -#include "common/network/RTPFNEHeader.h" #include "common/network/json/json.h" #include "common/p25/kmm/KMMFactory.h" #include "common/Log.h" @@ -267,7 +265,7 @@ void Network::clock(uint32_t ms) // are protocol messages being user handled? if (m_userHandleProtocol) { userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, - buffer.get(), length, fneHeader.getStreamId()); + buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader); break; } @@ -832,7 +830,7 @@ void Network::clock(uint32_t ms) break; default: userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, - buffer.get(), length, fneHeader.getStreamId()); + buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader); break; } } @@ -930,7 +928,8 @@ void Network::enable(bool enabled) /* User overrideable handler that allows user code to process network packets not handled by this class. */ -void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { Utils::dump("unknown opcode from the master", data, length); } diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 290b77e2e..5ce70a7a9 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -18,6 +18,8 @@ #include "common/Defines.h" #include "common/network/BaseNetwork.h" +#include "common/network/RTPHeader.h" +#include "common/network/RTPFNEHeader.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/p25/kmm/KeysetItem.h" @@ -312,9 +314,11 @@ namespace network * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. * @param streamId Stream ID. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. */ virtual void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, - uint32_t streamId = 0U); + uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader()); /** * @brief Writes login request to the network. diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 593c05097..210b5b806 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -249,14 +249,6 @@ int HostFNE::run() network::PeerNetwork* peerNetwork = network.second; if (peerNetwork != nullptr) { peerNetwork->clock(ms); - - // skip peer if it isn't enabled - if (!peerNetwork->isEnabled()) { - continue; - } - - // process peer network traffic - processPeer(peerNetwork); } } @@ -839,6 +831,10 @@ bool HostFNE::createPeerNetworks() network->setPresharedKey(presharedKey); } + network->setDMRCallback(std::bind(&HostFNE::processPeerDMR, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setP25Callback(std::bind(&HostFNE::processPeerP25, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setNXDNCallback(std::bind(&HostFNE::processPeerNXDN, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + /* ** Block Traffic To Peers */ @@ -1044,52 +1040,68 @@ void* HostFNE::threadVirtualNetworkingClock(void* arg) } #endif // !defined(_WIN32) -/* Processes any peer network traffic. */ +/* Processes DMR peer network traffic. */ -void HostFNE::processPeer(network::PeerNetwork* peerNetwork) +void HostFNE::processPeerDMR(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) { if (peerNetwork == nullptr) return; // this shouldn't happen... + + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; + if (peerNetwork->getStatus() != NET_STAT_RUNNING) return; // process DMR data - if (peerNetwork->hasDMRData()) { - uint32_t length = 100U; - bool ret = false; - UInt8Array data = peerNetwork->readDMR(ret, length); - if (ret) { - uint32_t peerId = peerNetwork->getPeerId(); - uint32_t slotNo = (data[15U] & 0x80U) == 0x80U ? 2U : 1U; - uint32_t streamId = peerNetwork->getRxDMRStreamId(slotNo); - - m_network->dmrTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); - } + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->dmrTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } +} + +/* Processes P25 peer network traffic. */ + +void HostFNE::processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) +{ + if (peerNetwork == nullptr) + return; // this shouldn't happen... + + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; + + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; // process P25 data - if (peerNetwork->hasP25Data()) { - uint32_t length = 100U; - bool ret = false; - UInt8Array data = peerNetwork->readP25(ret, length); - if (ret) { - uint32_t peerId = peerNetwork->getPeerId(); - uint32_t streamId = peerNetwork->getRxP25StreamId(); - - m_network->p25TrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); - } + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->p25TrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } +} + +/* Processes NXDN peer network traffic. */ + +void HostFNE::processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) +{ + if (peerNetwork == nullptr) + return; // this shouldn't happen... + + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; + + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; // process NXDN data - if (peerNetwork->hasNXDNData()) { - uint32_t length = 100U; - bool ret = false; - UInt8Array data = peerNetwork->readNXDN(ret, length); - if (ret) { - uint32_t peerId = peerNetwork->getPeerId(); - uint32_t streamId = peerNetwork->getRxNXDNStreamId(); - - m_network->nxdnTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); - } + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->nxdnTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } } diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index 038dd460a..bd764e214 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -178,10 +178,35 @@ class HOST_SW_API HostFNE { static void* threadVirtualNetworkingClock(void* arg); #endif // !defined(_WIN32) /** - * @brief Processes any peer network traffic. - * @param peerNetwork Instance of PeerNetwork to process traffic for. + * @brief Processes DMR peer network traffic. + * @param data Buffer containing DMR data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. */ - void processPeer(network::PeerNetwork* peerNetwork); + void processPeerDMR(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Processes P25 peer network traffic. + * @param data Buffer containing P25 data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. + */ + void processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Processes NXDN peer network traffic. + * @param data Buffer containing NXDN data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. + */ + void processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); }; #endif // __HOST_FNE_H__ diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 8a26c0459..afa822ead 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -96,6 +96,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_restrictGrantToAffOnly(false), m_enableInCallCtrl(true), m_rejectUnknownRID(false), + m_maskOutboundPeerID(false), m_filterHeaders(true), m_filterTerminators(true), m_disallowU2U(false), @@ -142,6 +143,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_allowConvSiteAffOverride = conf["allowConvSiteAffOverride"].as(true); m_enableInCallCtrl = conf["enableInCallCtrl"].as(false); m_rejectUnknownRID = conf["rejectUnknownRID"].as(false); + m_maskOutboundPeerID = conf["maskOutboundPeerID"].as(false); m_disallowCallTerm = conf["disallowCallTerm"].as(false); m_softConnLimit = conf["connectionLimit"].as(MAX_HARD_CONN_CAP); @@ -204,6 +206,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no"); LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no"); LogInfo(" Reject Unknown RIDs: %s", m_rejectUnknownRID ? "yes" : "no"); + LogInfo(" Mask Outbound Traffic Peer ID: %s", m_maskOutboundPeerID ? "yes" : "no"); LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); @@ -2216,7 +2219,7 @@ bool FNENetwork::writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::E /* Helper to send a data message to the specified peer with a explicit packet sequence. */ -bool FNENetwork::writePeer(uint32_t peerId, uint32_t srcPeerId, FrameQueue::OpcodePair opcode, const uint8_t* data, +bool FNENetwork::writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq, bool directWrite) const { if (streamId == 0U) { @@ -2234,17 +2237,26 @@ bool FNENetwork::writePeer(uint32_t peerId, uint32_t srcPeerId, FrameQueue::Opco pktSeq = connection->incStreamPktSeq(streamId, pktSeq); } - if (connection->isExternalPeer() && !connection->isPeerLink()) { - // if the peer is an external peer, and not a Peer-Link peer, we need to send the packet - // to the external peer with our peer ID as the source instead of the originating peer - // because we have routed it - srcPeerId = m_peerId; + if (m_maskOutboundPeerID) + ssrc = m_peerId; // mask the source SSRC to our own peer ID + else { + if (connection->isExternalPeer() && !connection->isPeerLink()) { + // if the peer is an external peer, and not a Peer-Link peer, we need to send the packet + // to the external peer with our peer ID as the source instead of the originating peer + // because we have routed it + ssrc = m_peerId; + } + + if (ssrc == 0U) { + LogError(LOG_NET, "BUGBUG: PEER %u, trying to send data with a ssrc of 0?, pktSeq = %u, streamId = %u", peerId, pktSeq, streamId); + ssrc = m_peerId; // fallback to our own peer ID + } } if (directWrite) - return m_frameQueue->write(data, length, streamId, peerId, srcPeerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); else { - m_frameQueue->enqueueMessage(data, length, streamId, peerId, srcPeerId, opcode, pktSeq, addr, addrLen); + m_frameQueue->enqueueMessage(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); if (queueOnly) return true; return m_frameQueue->flushQueue(); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 9691f2c3b..d943467ab 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -600,6 +600,8 @@ namespace network bool m_enableInCallCtrl; bool m_rejectUnknownRID; + bool m_maskOutboundPeerID; + bool m_filterHeaders; bool m_filterTerminators; @@ -740,7 +742,7 @@ namespace network /** * @brief Helper to send a data message to the specified peer with a explicit packet sequence. * @param peerId Destination Peer ID. - * @param srcPeerId Source Peer ID. + * @param ssrc RTP synchronization source ID. * @param opcode FNE network opcode pair. * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. @@ -750,7 +752,7 @@ namespace network * @param incPktSeq Flag indicating the message should increment the packet sequence after transmission. * @param directWrite Flag indicating this message should be immediately directly written. */ - bool writePeer(uint32_t peerId, uint32_t srcPeerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, + bool writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq = false, bool directWrite = false) const; /** diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 6677afb3d..502e2fec4 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -50,6 +50,9 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc // never disable peer network services on ACL NAK from master m_neverDisableOnACLNAK = true; + + // FNE peer network manually handle protocol packets + m_userHandleProtocol = true; } /* Sets the instances of the Peer List lookup tables. */ @@ -140,9 +143,42 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) /* User overrideable handler that allows user code to process network packets not handled by this class. */ -void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { switch (opcode.first) { + case NET_FUNC::PROTOCOL: // Protocol + { + // process incomfing message subfunction opcodes + switch (opcode.second) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + { + if (m_dmrCallback != nullptr) + m_dmrCallback(this, data, length, streamId, fneHeader, rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + { + if (m_p25Callback != nullptr) + m_p25Callback(this, data, length, streamId, fneHeader, rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + { + if (m_nxdnCallback != nullptr) + m_nxdnCallback(this, data, length, streamId, fneHeader, rtpHeader); + } + break; + + default: + Utils::dump("unknown protocol opcode from the master", data, length); + break; + } + } + break; + case NET_FUNC::PEER_LINK: { switch (opcode.second) { diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 4e3016c55..4a36af463 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -81,6 +81,22 @@ namespace network */ uint32_t getRxNXDNStreamId() const { return m_rxNXDNStreamId; } + /** + * @brief Helper to set the DMR protocol callback. + * @param callback + */ + void setDMRCallback(std::function&& callback) { m_dmrCallback = callback; } + /** + * @brief Helper to set the P25 protocol callback. + * @param callback + */ + void setP25Callback(std::function&& callback) { m_p25Callback = callback; } + /** + * @brief Helper to set the NXDN protocol callback. + * @param callback + */ + void setNXDNCallback(std::function&& callback) { m_nxdnCallback = callback; } + /** * @brief Gets the blocked traffic peer ID table. * @returns std::vector List of peer IDs this peer network cannot send traffic to. @@ -125,6 +141,22 @@ namespace network protected: std::vector m_blockTrafficToTable; + /** + * @brief DMR Protocol Callback. + * (This is called when the master sends a DMR packet.) + */ + std::function m_dmrCallback; + /** + * @brief P25 Protocol Callback. + * (This is called when the master sends a P25 packet.) + */ + std::function m_p25Callback; + /** + * @brief NXDN Protocol Callback. + * (This is called when the master sends a NXDN packet.) + */ + std::function m_nxdnCallback; + /** * @brief User overrideable handler that allows user code to process network packets not handled by this class. * @param peerId Peer ID. @@ -132,9 +164,11 @@ namespace network * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. * @param streamId Stream ID. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. */ void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, - uint32_t streamId = 0U) override; + uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader()) override; /** * @brief Writes configuration to the network. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index fce2e83bf..4171e2936 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -316,7 +316,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, dmrData, dataType, dstId, slotNo); - m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", ssrc, peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); @@ -359,7 +359,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, dmrData, dataType, dstId, slotNo); - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", ssrc, peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 09cb29bb4..5913a2797 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -261,7 +261,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, messageType, dstId); - m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", ssrc, peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); @@ -304,7 +304,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, messageType, dstId); - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", ssrc, peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, external); diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index ea2ad9568..286760f12 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -349,7 +349,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, duid, dstId); - m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", ssrc, peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); @@ -394,7 +394,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // process TSDUs going to external peers if (processTSDUToExternal(outboundPeerBuffer, peerId, dstPeerId, duid)) { - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", ssrc, peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index cd8f174b5..a2e887981 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -56,7 +56,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc /* User overrideable handler that allows user code to process network packets not handled by this class. */ -void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { switch (opcode.first) { case NET_FUNC::TRANSFER: diff --git a/src/sysview/network/PeerNetwork.h b/src/sysview/network/PeerNetwork.h index 7c1aa20a9..b9038815f 100644 --- a/src/sysview/network/PeerNetwork.h +++ b/src/sysview/network/PeerNetwork.h @@ -88,9 +88,11 @@ namespace network * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. * @param streamId Stream ID. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. */ void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, - uint32_t streamId = 0U) override; + uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader()) override; /** * @brief Writes configuration to the network. From 89847e90566eaa871ddfb9a592ee4b3c7e29ca27 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 18:18:32 -0400 Subject: [PATCH 007/133] throw a warning if the user configured more then 8 upstream peer connections (more than this number can cause performance problems); --- src/fne/HostFNE.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 210b5b806..adef07324 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -42,6 +42,8 @@ using namespace lookups; #define MIN_WORKER_CNT 4U #define MAX_WORKER_CNT 128U +#define MAX_RECOMMENDED_PEER_NETWORKS 8U + #define THREAD_CYCLE_THRESHOLD 2U #define IDLE_WARMUP_MS 5U @@ -765,6 +767,10 @@ bool HostFNE::createPeerNetworks() { yaml::Node& peerList = m_conf["peers"]; if (peerList.size() > 0U) { + if (peerList.size() > MAX_RECOMMENDED_PEER_NETWORKS) { + LogWarning(LOG_HOST, "Peer network count (%zu) exceeds the recommended maximum of %u. This may result in poor performance.", peerList.size(), MAX_RECOMMENDED_PEER_NETWORKS); + } + for (size_t i = 0; i < peerList.size(); i++) { yaml::Node& peerConf = peerList[i]; From 81a87d7e9cbfd7faf083c0ef19a050e1c9f0d976 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 18:27:09 -0400 Subject: [PATCH 008/133] make non-peer-link peer ID masking optional; --- configs/fne-config.example.yml | 5 ++++- src/fne/network/FNENetwork.cpp | 7 ++++++- src/fne/network/FNENetwork.h | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 17e2195ee..ddaeb551f 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -103,9 +103,12 @@ master: # Flag indicating whether or not a TDULC call terminations will pass to any peers. disallowCallTerm: false - # Flag indicating whether or not the FNE will mask outbound traffic to use the FNE's own peer ID. + # Flag indicating whether or not the FNE will mask all outbound traffic to use the FNE's own peer ID. # (This is useful for FNEs that are public facing, and the originating traffic peer ID should be masked.) maskOutboundPeerID: false + # Flag indicating whether or not the FNE will mask only non-Peer-Link outbound traffic to use the FNE's own peer ID. + # (This is useful for networked FNEs that are have a mix of connections, and the originating traffic peer ID to non-Peer-Link FNEs should be masked.) + maskOutboundPeerIDForNonPeerLink: false # Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID). filterHeaders: true diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index afa822ead..c7c643e17 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -97,6 +97,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_enableInCallCtrl(true), m_rejectUnknownRID(false), m_maskOutboundPeerID(false), + m_maskOutboundPeerIDForNonPL(false), m_filterHeaders(true), m_filterTerminators(true), m_disallowU2U(false), @@ -144,6 +145,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_enableInCallCtrl = conf["enableInCallCtrl"].as(false); m_rejectUnknownRID = conf["rejectUnknownRID"].as(false); m_maskOutboundPeerID = conf["maskOutboundPeerID"].as(false); + m_maskOutboundPeerIDForNonPL = conf["maskOutboundPeerIDForNonPeerLink"].as(false); m_disallowCallTerm = conf["disallowCallTerm"].as(false); m_softConnLimit = conf["connectionLimit"].as(MAX_HARD_CONN_CAP); @@ -207,6 +209,9 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no"); LogInfo(" Reject Unknown RIDs: %s", m_rejectUnknownRID ? "yes" : "no"); LogInfo(" Mask Outbound Traffic Peer ID: %s", m_maskOutboundPeerID ? "yes" : "no"); + if (m_maskOutboundPeerIDForNonPL) { + LogInfo(" Mask Outbound Traffic Peer ID for Non-Peer Link: yes"); + } LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); @@ -2240,7 +2245,7 @@ bool FNENetwork::writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePai if (m_maskOutboundPeerID) ssrc = m_peerId; // mask the source SSRC to our own peer ID else { - if (connection->isExternalPeer() && !connection->isPeerLink()) { + if ((connection->isExternalPeer() && !connection->isPeerLink()) && m_maskOutboundPeerIDForNonPL) { // if the peer is an external peer, and not a Peer-Link peer, we need to send the packet // to the external peer with our peer ID as the source instead of the originating peer // because we have routed it diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index d943467ab..f5796d7b2 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -601,6 +601,7 @@ namespace network bool m_rejectUnknownRID; bool m_maskOutboundPeerID; + bool m_maskOutboundPeerIDForNonPL; bool m_filterHeaders; bool m_filterTerminators; From 5603575e6b700f29bf9c0893458d470793b50185 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 18:31:20 -0400 Subject: [PATCH 009/133] add checking for traffic repeat to verify we are not trying to send traffic back to the source; --- src/fne/network/callhandler/TagDMRData.cpp | 5 +++++ src/fne/network/callhandler/TagNXDNData.cpp | 5 +++++ src/fne/network/callhandler/TagP25Data.cpp | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 4171e2936..b5957181d 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -300,6 +300,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(peer.first, dmrData, streamId)) { continue; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 5913a2797..3ee7eced5 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -245,6 +245,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(peer.first, lc, messageType, streamId)) { continue; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 286760f12..b033c35b3 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -328,6 +328,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(peer.first, control, duid, streamId)) { continue; From cd96fd165e4eb4f4ba303e2df79cdd315c140c6e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 18:32:55 -0400 Subject: [PATCH 010/133] add checking for traffic repeat to verify we are not trying to send traffic back to an external source; --- src/fne/network/callhandler/TagDMRData.cpp | 5 +++++ src/fne/network/callhandler/TagNXDNData.cpp | 5 +++++ src/fne/network/callhandler/TagP25Data.cpp | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index b5957181d..5898c9044 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -343,6 +343,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // don't try to repeat traffic to the source peer...if this traffic // is coming from a external peer if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(dstPeerId, dmrData, streamId, true)) { continue; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 3ee7eced5..a7c0cc688 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -288,6 +288,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // don't try to repeat traffic to the source peer...if this traffic // is coming from a external peer if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(dstPeerId, lc, messageType, streamId, true)) { continue; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index b033c35b3..72d4ab8a0 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -376,6 +376,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // don't try to repeat traffic to the source peer...if this traffic // is coming from a external peer if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(dstPeerId, control, duid, streamId, true)) { continue; From 83e379add06bf7fc06d450eb37bca8a381d14fe3 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 20:20:33 -0400 Subject: [PATCH 011/133] convert peer network protocol packet processing to a threaded model; --- src/fne/network/PeerNetwork.cpp | 150 +++++++++++++++++++++++++------- src/fne/network/PeerNetwork.h | 49 ++++++++--- 2 files changed, 154 insertions(+), 45 deletions(-) diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 502e2fec4..5d815e07c 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -23,6 +23,14 @@ using namespace compress; #include #include +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define WORKER_CNT 8U + +const uint64_t PACKET_LATE_TIME = 200U; // 200ms + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -39,7 +47,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc m_peerLinkSavesACL(false), m_tgidPkt(true, "Peer-Link, TGID List"), m_ridPkt(true, "Peer-Link, RID List"), - m_pidPkt(true, "Peer-Link, PID List") + m_pidPkt(true, "Peer-Link, PID List"), + m_threadPool(WORKER_CNT, "peer") { assert(!address.empty()); assert(port > 0U); @@ -62,18 +71,28 @@ void PeerNetwork::setPeerLookups(lookups::PeerListLookup* pidLookup) m_pidLookup = pidLookup; } -/* Gets the received DMR stream ID. */ +/* Opens connection to the network. */ -uint32_t PeerNetwork::getRxDMRStreamId(uint32_t slotNo) const +bool PeerNetwork::open() { - assert(slotNo == 1U || slotNo == 2U); + if (!m_enabled) + return false; - if (slotNo == 1U) { - return m_rxDMRStreamId[0U]; - } - else { - return m_rxDMRStreamId[1U]; - } + // start thread pool + m_threadPool.start(); + + return Network::open(); +} + +/* Closes connection to the network. */ + +void PeerNetwork::close() +{ + // stop thread pool + m_threadPool.stop(); + m_threadPool.wait(); + + Network::close(); } /* Checks if the passed peer ID is blocked from sending to this peer. */ @@ -149,32 +168,30 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco switch (opcode.first) { case NET_FUNC::PROTOCOL: // Protocol { - // process incomfing message subfunction opcodes - switch (opcode.second) { - case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame - { - if (m_dmrCallback != nullptr) - m_dmrCallback(this, data, length, streamId, fneHeader, rtpHeader); - } - break; + PeerPacketRequest* req = new PeerPacketRequest(); + req->obj = this; + req->peerId = peerId; + req->streamId = streamId; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame - { - if (m_p25Callback != nullptr) - m_p25Callback(this, data, length, streamId, fneHeader, rtpHeader); - } - break; + req->rtpHeader = rtpHeader; + req->fneHeader = fneHeader; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame - { - if (m_nxdnCallback != nullptr) - m_nxdnCallback(this, data, length, streamId, fneHeader, rtpHeader); - } - break; + req->subFunc = opcode.second; - default: - Utils::dump("unknown protocol opcode from the master", data, length); - break; + req->pktRxTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + req->length = length; + req->buffer = new uint8_t[length]; + ::memcpy(req->buffer, data, length); + + // enqueue the task + if (!m_threadPool.enqueue(new_pooltask(taskNetworkRx, req))) { + LogError(LOG_NET, "Failed to task enqueue network packet request, peerId = %u", peerId); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } } } break; @@ -427,3 +444,70 @@ bool PeerNetwork::writeConfig() return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, pktSeq(), m_loginStreamId); } + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Process a data frames from the network. */ + +void PeerNetwork::taskNetworkRx(PeerPacketRequest* req) +{ + if (req != nullptr) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + PeerNetwork* network = static_cast(req->obj); + if (network == nullptr) { + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } + + return; + } + + if (req == nullptr) + return; + + if (req->length > 0) { + // determine if this packet is late (i.e. are we processing this packet more than 200ms after it was received?) + uint64_t dt = req->pktRxTime + PACKET_LATE_TIME; + if (dt < now) { + LogWarning(LOG_NET, "PEER %u packet processing latency >200ms, dt = %u, now = %u", req->peerId, dt, now); + } + + // process incomfing message subfunction opcodes + switch (req->subFunc) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + { + if (network->m_dmrCallback != nullptr) + network->m_dmrCallback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + { + if (network->m_p25Callback != nullptr) + network->m_p25Callback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + { + if (network->m_nxdnCallback != nullptr) + network->m_nxdnCallback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + default: + Utils::dump("unknown protocol opcode from the master", req->buffer, req->length); + break; + } + } + + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } +} diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 4a36af463..d6a6b0b50 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -20,6 +20,7 @@ #include "common/lookups/PeerListLookup.h" #include "common/network/Network.h" #include "common/network/PacketBuffer.h" +#include "common/ThreadPool.h" #include #include @@ -27,6 +28,28 @@ namespace network { + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents the data required for a network packet handler thread. + * @ingroup fne_network + */ + struct PeerPacketRequest : thread_t { + uint32_t peerId; //! Peer ID for this request. + uint32_t streamId; //! Stream ID for this request. + + frame::RTPHeader rtpHeader; //! RTP Header + frame::RTPFNEHeader fneHeader; //! RTP FNE Header + int length = 0U; //! Length of raw data buffer + uint8_t* buffer = nullptr; //! Raw data buffer + + network::NET_SUBFUNC::ENUM subFunc; //! Sub-function of the packet + + uint64_t pktRxTime; //! Packet receive time + }; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -65,21 +88,15 @@ namespace network void setPeerLookups(lookups::PeerListLookup* pidLookup); /** - * @brief Gets the received DMR stream ID. - * @param slotNo DMR slot to get stream ID for. - * @return uint32_t Stream ID for the given DMR slot. + * @brief Opens connection to the network. + * @returns bool True, if networking has started, otherwise false. */ - uint32_t getRxDMRStreamId(uint32_t slotNo) const; - /** - * @brief Gets the received P25 stream ID. - * @return uint32_t Stream ID. - */ - uint32_t getRxP25StreamId() const { return m_rxP25StreamId; } + bool open() override; + /** - * @brief Gets the received NXDN stream ID. - * @return uint32_t Stream ID. + * @brief Closes connection to the network. */ - uint32_t getRxNXDNStreamId() const { return m_rxNXDNStreamId; } + void close() override; /** * @brief Helper to set the DMR protocol callback. @@ -184,6 +201,14 @@ namespace network PacketBuffer m_tgidPkt; PacketBuffer m_ridPkt; PacketBuffer m_pidPkt; + + ThreadPool m_threadPool; + + /** + * @brief Entry point to process a given network packet. + * @param req Instance of the PeerPacketRequest structure. + */ + static void taskNetworkRx(PeerPacketRequest* req); }; } // namespace network From 28a120fc37ff92a2cf94a01f22e09d958e385db1 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 31 May 2025 20:59:27 -0400 Subject: [PATCH 012/133] update copyright dates; --- src/fne/HostFNE.h | 2 +- src/fne/network/PeerNetwork.cpp | 10 +++++----- src/fne/network/PeerNetwork.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index bd764e214..052753f55 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ /** diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 5d815e07c..d78fb3aa4 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "fne/Defines.h" @@ -196,10 +196,10 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_FUNC::PEER_LINK: + case NET_FUNC::PEER_LINK: // Peer Link { switch (opcode.second) { - case NET_SUBFUNC::PL_TALKGROUP_LIST: + case NET_SUBFUNC::PL_TALKGROUP_LIST: // Talkgroup List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -256,7 +256,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_RID_LIST: + case NET_SUBFUNC::PL_RID_LIST: // Radio ID List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -313,7 +313,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_PEER_LIST: + case NET_SUBFUNC::PL_PEER_LIST: // Peer List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index d6a6b0b50..3b9510ad6 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** From 6d632d64d52543b6fea2d704412089fe6d29e359 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 1 Jun 2025 09:24:01 -0400 Subject: [PATCH 013/133] implement REST API for manipulating adjacent site map entries; --- src/fne/network/RESTAPI.cpp | 140 +++++++++++++++++++++++++++++++++- src/fne/network/RESTAPI.h | 37 ++++++++- src/fne/network/RESTDefines.h | 7 +- 3 files changed, 180 insertions(+), 4 deletions(-) diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 5a0949f88..85a44615f 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -505,6 +505,7 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), + m_adjSiteMapLookup(nullptr), m_authTokens() { assert(!address.empty()); @@ -554,11 +555,13 @@ RESTAPI::~RESTAPI() /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ -void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, ::lookups::PeerListLookup* peerListLookup) +void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, + ::lookups::PeerListLookup* peerListLookup, ::lookups::AdjSiteMapLookup* adjMapLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; m_peerListLookup = peerListLookup; + m_adjSiteMapLookup = adjMapLookup; } /* Sets the instance of the FNE network. */ @@ -651,6 +654,11 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(FNE_PUT_PEER_DELETE).put(REST_API_BIND(RESTAPI::restAPI_PutPeerDelete, this)); m_dispatcher.match(FNE_GET_PEER_COMMIT).get(REST_API_BIND(RESTAPI::restAPI_GetPeerCommit, this)); + m_dispatcher.match(FNE_GET_ADJ_MAP_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetAdjMapList, this)); + m_dispatcher.match(FNE_PUT_ADJ_MAP_ADD).put(REST_API_BIND(RESTAPI::restAPI_PutAdjMapAdd, this)); + m_dispatcher.match(FNE_PUT_ADJ_MAP_DELETE).put(REST_API_BIND(RESTAPI::restAPI_PutAdjMapDelete, this)); + m_dispatcher.match(FNE_GET_ADJ_MAP_COMMIT).get(REST_API_BIND(RESTAPI::restAPI_GetAdjMapCommit, this)); + m_dispatcher.match(FNE_GET_FORCE_UPDATE).get(REST_API_BIND(RESTAPI::restAPI_GetForceUpdate, this)); m_dispatcher.match(FNE_GET_RELOAD_TGS).get(REST_API_BIND(RESTAPI::restAPI_GetReloadTGs, this)); @@ -1331,6 +1339,136 @@ void RESTAPI::restAPI_GetPeerCommit(const HTTPPayload& request, HTTPPayload& rep reply.payload(response); } + +/* REST API endpoint; implements get adjacent site map query request. */ + +void RESTAPI::restAPI_GetAdjMapList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + json::array peers = json::array(); + if (m_adjSiteMapLookup != nullptr) { + if (m_adjSiteMapLookup->adjPeerMap().size() > 0) { + for (auto entry : m_adjSiteMapLookup->adjPeerMap()) { + json::object peerObj = json::object(); + + uint32_t peerId = entry.peerId(); + peerObj["peerId"].set(peerId); + + json::array neighbors = json::array(); + std::vector neighbor = entry.neighbors(); + if (neighbor.size() > 0) { + for (auto neighEntry : neighbor) { + uint32_t peerId = neighEntry; + neighbors.push_back(json::value((double)peerId)); + } + } + peerObj["neighbors"].set(neighbors); + peers.push_back(json::value(peerObj)); + } + } + } + + response["peers"].set(peers); + reply.payload(response); +} + +/* REST API endpoint; implements put adjacent site map add request. */ + +void RESTAPI::restAPI_PutAdjMapAdd(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + // Validate peer ID (required) + if (!req["peerId"].is()) { + errorPayload(reply, "peerId was not a valid integer"); + return; + } + + // get + AdjPeerMapEntry entry = AdjPeerMapEntry(); + uint32_t peerId = req["peerId"].get(); + entry.peerId(peerId); + + if (!req["neighbors"].is()) { + errorPayload(reply, "Peer \"neighbors\" was not a valid JSON array"); + LogDebug(LOG_REST, "Peer \"neighbors\" was not a valid JSON array"); + return; + } + json::array neighbors = req["neighbors"].get(); + + std::vector neighbor = std::vector(); + if (neighbors.size() > 0) { + for (auto neighEntry : neighbors) { + if (!neighEntry.is()) { + errorPayload(reply, "Peer neighbor value was not a valid number"); + LogDebug(LOG_REST, "Peer neighbor value was not a valid number (was %s)", neighEntry.to_type().c_str()); + return; + } + + neighbor.push_back(neighEntry.get()); + } + entry.neighbors(neighbor); + } + + m_adjSiteMapLookup->addEntry(entry); +} + +/* REST API endpoint; implements put adjacent site map delete request. */ + +void RESTAPI::restAPI_PutAdjMapDelete(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + if (!req["peerId"].is()) { + errorPayload(reply, "peerId was not a valid integer"); + return; + } + + uint32_t peerId = req["peerId"].get(); + + m_adjSiteMapLookup->eraseEntry(peerId); +} + +/* REST API endpoint; implements put adjacent site map commit request. */ + +void RESTAPI::restAPI_GetAdjMapCommit(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + m_adjSiteMapLookup->commit(); + + reply.payload(response); +} + /* */ void RESTAPI::restAPI_GetForceUpdate(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) diff --git a/src/fne/network/RESTAPI.h b/src/fne/network/RESTAPI.h index d18fcf9e9..03a923de2 100644 --- a/src/fne/network/RESTAPI.h +++ b/src/fne/network/RESTAPI.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -20,6 +20,7 @@ #include "common/network/rest/RequestDispatcher.h" #include "common/network/rest/http/HTTPServer.h" #include "common/network/rest/http/SecureHTTPServer.h" +#include "common/lookups/AdjSiteMapLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/Thread.h" @@ -69,8 +70,10 @@ class HOST_SW_API RESTAPI : private Thread { * @param ridLookup Radio ID Lookup Table Instance * @param tidLookup Talkgroup Rules Lookup Table Instance * @param peerListLookup Peer List Lookup Table Instance + * @param adjPeerMapLookup Adjacent Site Map Lookup Table Instance */ - void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::PeerListLookup* peerListLookup); + void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, + ::lookups::PeerListLookup* peerListLookup, ::lookups::AdjSiteMapLookup* adjPeerMapLookup); /** * @brief Sets the instance of the FNE network. * @param network Instance oft he FNENetwork class. @@ -110,6 +113,7 @@ class HOST_SW_API RESTAPI : private Thread { ::lookups::RadioIdLookup* m_ridLookup; ::lookups::TalkgroupRulesLookup* m_tidLookup; ::lookups::PeerListLookup* m_peerListLookup; + ::lookups::AdjSiteMapLookup* m_adjSiteMapLookup; typedef std::unordered_map::value_type AuthTokenValueType; std::unordered_map m_authTokens; @@ -270,6 +274,35 @@ class HOST_SW_API RESTAPI : private Thread { */ void restAPI_GetPeerCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements get adjacent site map list query request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetAdjMapList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements put adjacent site map add request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_PutAdjMapAdd(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements put adjacent site map delete request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_PutAdjMapDelete(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements put adjacent site map commit request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetAdjMapCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** * @brief * @param request HTTP request. diff --git a/src/fne/network/RESTDefines.h b/src/fne/network/RESTDefines.h index 95208b5c3..66fe955ba 100644 --- a/src/fne/network/RESTDefines.h +++ b/src/fne/network/RESTDefines.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -44,6 +44,11 @@ #define FNE_PUT_PEER_DELETE "/peer/delete" #define FNE_GET_PEER_COMMIT "/peer/commit" +#define FNE_GET_ADJ_MAP_LIST "/adjmap/list" +#define FNE_PUT_ADJ_MAP_ADD "/adjmap/add" +#define FNE_PUT_ADJ_MAP_DELETE "/adjmap/delete" +#define FNE_GET_ADJ_MAP_COMMIT "/adjmap/commit" + #define FNE_GET_FORCE_UPDATE "/force-update" #define FNE_GET_RELOAD_TGS "/reload-tgs" From 30670361b11c3e40cc86d72866bcfab2bb255f8d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 1 Jun 2025 09:25:46 -0400 Subject: [PATCH 014/133] fix missed REST API initializer, need to pass the adj site map; --- src/fne/HostFNE.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index adef07324..c54f26308 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -499,7 +499,7 @@ bool HostFNE::initializeRESTAPI() // initialize network remote command if (restApiEnable) { m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, restApiSSLKey, restApiSSLCert, restApiEnableSSL, this, restApiDebug); - m_RESTAPI->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup); + m_RESTAPI->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_adjSiteMapLookup); bool ret = m_RESTAPI->open(); if (!ret) { delete m_RESTAPI; From a3fa75d9673fe6f80e9be7552990f99d56b211c0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 3 Jun 2025 15:21:28 -0400 Subject: [PATCH 015/133] add DFSI/V.24 full duplex option (this allows dvmhost to repeat incoming frames back out); --- configs/config.example.yml | 3 +++ src/host/p25/Control.cpp | 11 +++++++++++ src/host/p25/Control.h | 1 + src/host/p25/packet/Voice.cpp | 6 +++--- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index f7d045c6c..ffa69623f 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -626,6 +626,9 @@ system: jitter: 200 # Timer which will reset local/remote call flags if frames aren't received longer than this time in ms callTimeout: 200 + # Flag indicating whether or not the V.24 modem is operating in full-duplex mode. + # (This enables DVM to repeat incoming V.24 frames back out after processing.) + fullDuplex: false # Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection. fsc: false # Sets the heartbeat interval for the FSC connection. diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 6c8b775a0..a803a227f 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -80,6 +80,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_sndcpSupport(false), m_ignoreAffiliationCheck(false), m_demandUnitRegForRefusedAff(true), + m_dfsiFDX(false), m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), @@ -231,6 +232,12 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw yaml::Node systemConf = conf["system"]; yaml::Node p25Protocol = conf["protocols"]["p25"]; + if (m_isModemDFSI) { + yaml::Node modemConf = systemConf["modem"]; + yaml::Node dfsiConf = modemConf["dfsi"]; + m_dfsiFDX = dfsiConf["fullDuplex"].as(false); + } + m_supervisor = supervisor; m_tduPreambleCount = p25Protocol["tduPreambleCount"].as(8U); @@ -491,6 +498,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Control Data Only: yes"); } + if (m_isModemDFSI && m_dfsiFDX) { + LogInfo(" DFSI Full Duplex: yes"); + } + LogInfo(" Patch Super Group: $%04X", m_control->m_patchSuperGroup); LogInfo(" Announcement Group: $%04X", m_control->m_announcementGroup); diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 013e2c13b..b97094ee3 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -311,6 +311,7 @@ namespace p25 bool m_sndcpSupport; bool m_ignoreAffiliationCheck; bool m_demandUnitRegForRefusedAff; + bool m_dfsiFDX; ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 2382c18a1..95290002a 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -540,7 +540,7 @@ bool Voice::process(uint8_t* data, uint32_t len) writeNetwork(buffer, DUID::HDU); - if (m_p25->m_duplex && !m_p25->m_isModemDFSI) { + if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -794,7 +794,7 @@ bool Voice::process(uint8_t* data, uint32_t len) writeNetwork(data + 2U, DUID::LDU1, frameType); - if (m_p25->m_duplex && !m_p25->m_isModemDFSI) { + if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -911,7 +911,7 @@ bool Voice::process(uint8_t* data, uint32_t len) writeNetwork(data + 2U, DUID::LDU2); - if (m_p25->m_duplex && !m_p25->m_isModemDFSI) { + if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { data[0U] = modem::TAG_DATA; data[1U] = 0x00U; From 1779a44c5b3c7ec57190eadaa87d9a16674e6100 Mon Sep 17 00:00:00 2001 From: Jamie <25770089+faultywarrior@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:05:03 -0500 Subject: [PATCH 016/133] Fix incorrect check of DFSI and FSC operating modes that resulted in a segfault (#92) Co-authored-by: faulty --- src/host/Host.Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 470e18fc7..91c87614c 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -750,7 +750,7 @@ bool Host::createModem() m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(Host::rmtPortModemHandler, this)); } - if (useFSCForUDP) { + if (m_isModemDFSI && useFSCForUDP) { modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDFSIRemotePort); udpPort->openFSC(); } From 5cf70cc33619fd701c6a40959c60f5f76af9ce59 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 6 Jun 2025 11:30:23 -0400 Subject: [PATCH 017/133] document p25CorrCount in depth more; set the default p25CorrCount to 2; --- configs/config.example.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index ffa69623f..f2be833ba 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -532,8 +532,12 @@ system: # Amount of time to wait before starting DMR transmissions after a signal is received. dmrRxDelay: 7 # Amount of packet correlations that should occur before P25 data is returned from the modem to the host. - # (Note: Changing this value will impact P25 protocol stability, and should not be altered.) - p25CorrCount: 8 + # (Note: Changing this value will impact P25 protocol stability, and should not be altered. This is set to + # a default value of 2 for fast synchronization of a P25 signal, if the air modem is having difficulty + # synchronizing to a P25 signal, this value can be increased to 3 or 4, or beyond. It should be noted that + # increasing this value will increase the time it takes to synchronize to a P25 signal, and may cause + # issues with P25 voice calls (especially encrypted voice calls).) + p25CorrCount: 2 # Size (in bytes) of the DMR transmit FIFO buffer. It is not recommended to adjust this unless absolutely # necessary, excessive sizes will cause delays in transmitted frames due to excessive buffering. # Calculation Formula: (DMR Frame Size (33 bytes) * x Frames) + Pad Bytes = FIFO Buffer Size Bytes From e269145542dba295cdb97c1be7737d04a0a06a7f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 6 Jun 2025 14:52:56 -0400 Subject: [PATCH 018/133] properly call PacketBuffers clear() before leaving a scope to ensure contained buffers are deleted (otherwise we'll leak memory); --- src/fne/network/FNENetwork.cpp | 6 ++++++ src/fne/network/PeerNetwork.cpp | 1 + 2 files changed, 7 insertions(+) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index c7c643e17..c6e1ac070 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1828,6 +1828,8 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE Thread::sleep(60U); // pace block transmission } } + + pkt.clear(); } return; @@ -2017,6 +2019,8 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP Thread::sleep(60U); // pace block transmission } } + + pkt.clear(); } return; @@ -2195,6 +2199,8 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) Thread::sleep(60U); // pace block transmission } } + + pkt.clear(); } return; diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index d78fb3aa4..fc26538c0 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -150,6 +150,7 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) } } + pkt.clear(); return true; } From 7b8c215c2fd9883d2fc4735fa8110f994dfbfc6b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 6 Jun 2025 22:17:06 -0400 Subject: [PATCH 019/133] skip the first 6 bits of the TIA-102 DFSI VHDR (this is very strange, and is probably not kosher); --- src/host/modem/ModemV24.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 3b5b9bbac..d19cfbb12 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1210,7 +1210,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) assert(vhdr != nullptr); - uint32_t offset = 0U; + uint32_t offset = 6U; // skip the first 6 bits (extremely strange bit offset for TIA because of status bits) for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { Utils::hex2Bin(raw[i], vhdr, offset); } @@ -1820,7 +1820,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // convert the binary bytes to hex bytes uint8_t raw[DFSI_VHDR_RAW_LEN]; - uint32_t offset = 0; + uint32_t offset = 6U; // skip the first 6 bits (extremely strange bit offset for TIA because of status bits) for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { raw[i] = Utils::bin2Hex(vhdr, offset); } From 73bcf73e4095bc380a0a1a8c74d76f77efdd4f5e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 7 Jun 2025 11:19:37 -0400 Subject: [PATCH 020/133] transparently pass voice frames with FID $20 (assumed to be Kenwood) when the PF flag is set (this allows Kenwoods flavor of encryption to pass); --- src/common/dmr/DMRDefines.h | 2 ++ src/host/dmr/packet/Voice.cpp | 28 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/common/dmr/DMRDefines.h b/src/common/dmr/DMRDefines.h index 30f84b2a0..16a9ca3ff 100644 --- a/src/common/dmr/DMRDefines.h +++ b/src/common/dmr/DMRDefines.h @@ -205,6 +205,8 @@ namespace dmr const uint8_t FID_ETSI = 0x00U; /** @brief Motorola */ const uint8_t FID_DMRA = 0x10U; + /** @brief Kenwood ?? */ + const uint8_t FID_KENWOOD = 0x20U; /** @brief DVM; Omaha Communication Systems, LLC ($9C) */ const uint8_t FID_DVM_OCS = 0x9CU; /** @} */ diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index bcd56c52c..4d5868ca6 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -278,11 +278,17 @@ bool Voice::process(uint8_t* data, uint32_t len) uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - errors = m_fec.regenerateDMR(data + 2U); + bool pf = m_slot->m_rfLC->getPF(); + if (fid == FID_ETSI || fid == FID_DMRA || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + errors = m_fec.regenerateDMR(data + 2U); + if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), - errors, float(errors) / 1.41F); + LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + pf, errors, float(errors) / 1.41F); } if (errors > m_slot->m_silenceThreshold) { @@ -340,11 +346,17 @@ bool Voice::process(uint8_t* data, uint32_t len) uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - errors = m_fec.regenerateDMR(data + 2U); + bool pf = m_slot->m_rfLC->getPF(); + if (fid == FID_ETSI || fid == FID_DMRA || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + errors = m_fec.regenerateDMR(data + 2U); + if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), - m_rfN, errors, float(errors) / 1.41F); + LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + m_rfN, pf, errors, float(errors) / 1.41F); } if (errors > m_slot->m_silenceThreshold) { From e09c15797075da9902bab742a503adc862b2a4dc Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 7 Jun 2025 11:31:47 -0400 Subject: [PATCH 021/133] relabel FID_DMRA to FID_MOT (this feature ID is assigned to Motorola); handle more conditions for FID $20 (Kenwood) on voice frames; --- src/common/dmr/DMRDefines.h | 11 +-- src/common/dmr/lc/csbk/CSBKFactory.cpp | 4 +- src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp | 2 +- src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp | 2 +- src/host/dmr/packet/ControlSignaling.cpp | 4 +- src/host/dmr/packet/Voice.cpp | 81 ++++++++++++++--------- 6 files changed, 62 insertions(+), 42 deletions(-) diff --git a/src/common/dmr/DMRDefines.h b/src/common/dmr/DMRDefines.h index 16a9ca3ff..2277faa29 100644 --- a/src/common/dmr/DMRDefines.h +++ b/src/common/dmr/DMRDefines.h @@ -200,12 +200,13 @@ namespace dmr }; }; - /** @name Feature IDs */ + /** @name Feature Set IDs */ /** @brief ETSI Standard Feature Set */ const uint8_t FID_ETSI = 0x00U; + /** @brief Motorola */ - const uint8_t FID_DMRA = 0x10U; - /** @brief Kenwood ?? */ + const uint8_t FID_MOT = 0x10U; + /** @brief Kenwood */ const uint8_t FID_KENWOOD = 0x20U; /** @brief DVM; Omaha Communication Systems, LLC ($9C) */ const uint8_t FID_DVM_OCS = 0x9CU; @@ -261,9 +262,9 @@ namespace dmr }; } - /** @brief FID_DMRA Extended Functions. */ + /** @brief FID_MOT Extended Functions. */ namespace ExtendedFunctions { - /** @brief FID_DMRA Extended Functions. */ + /** @brief FID_MOT Extended Functions. */ enum : uint16_t { CHECK = 0x0000U, //! Radio Check UNINHIBIT = 0x007EU, //! Radio Uninhibit diff --git a/src/common/dmr/lc/csbk/CSBKFactory.cpp b/src/common/dmr/lc/csbk/CSBKFactory.cpp index c16dc6203..bdb4dc47b 100644 --- a/src/common/dmr/lc/csbk/CSBKFactory.cpp +++ b/src/common/dmr/lc/csbk/CSBKFactory.cpp @@ -93,10 +93,10 @@ std::unique_ptr CSBKFactory::createCSBK(const uint8_t* data, DataType::E d return decode(new CSBK_UU_ANS_RSP(), data); case CSBKO::PRECCSBK: return decode(new CSBK_PRECCSBK(), data); - case CSBKO::RAND: // CSBKO::CALL_ALRT when FID == FID_DMRA + case CSBKO::RAND: // CSBKO::CALL_ALRT when FID == FID_MOT switch (FID) { - case FID_DMRA: + case FID_MOT: return decode(new CSBK_CALL_ALRT(), data); case FID_ETSI: default: diff --git a/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp b/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp index 98b9ab0d2..8872452af 100644 --- a/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp +++ b/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp @@ -26,7 +26,7 @@ using namespace dmr::lc::csbk; CSBK_CALL_ALRT::CSBK_CALL_ALRT() : CSBK() { m_CSBKO = CSBKO::RAND; - m_FID = FID_DMRA; + m_FID = FID_MOT; } /* Decode a control signalling block. */ diff --git a/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp b/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp index 745343191..0f20d561e 100644 --- a/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp +++ b/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp @@ -27,7 +27,7 @@ CSBK_EXT_FNCT::CSBK_EXT_FNCT() : CSBK(), m_extendedFunction(ExtendedFunctions::CHECK) { m_CSBKO = CSBKO::EXT_FNCT; - m_FID = FID_DMRA; + m_FID = FID_MOT; } /* Decode a control signalling block. */ diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 671384e38..90cc29912 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -188,7 +188,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) break; case CSBKO::RAND: { - if (csbk->getFID() == FID_DMRA) { + if (csbk->getFID() == FID_MOT) { if (m_verbose) { LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); @@ -469,7 +469,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) break; case CSBKO::RAND: { - if (csbk->getFID() == FID_DMRA) { + if (csbk->getFID() == FID_MOT) { if (m_verbose) { LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index 4d5868ca6..fd5896fdf 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -279,18 +279,13 @@ bool Voice::process(uint8_t* data, uint32_t len) uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); bool pf = m_slot->m_rfLC->getPF(); - if (fid == FID_ETSI || fid == FID_DMRA || fid == FID_KENWOOD) { + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { if (fid == FID_KENWOOD && pf) errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames // don't bother trying to regenerate or perform FEC else errors = m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), - pf, errors, float(errors) / 1.41F); - } - if (errors > m_slot->m_silenceThreshold) { insertNullAudio(data + 2U); m_fec.regenerateDMR(data + 2U); @@ -301,6 +296,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfErrs += errors; } + if (m_verbose) { + LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + fid, pf, errors, float(errors) / 1.41F); + } + m_slot->m_rfBits += 141U; m_slot->m_rfFrames++; @@ -347,18 +347,13 @@ bool Voice::process(uint8_t* data, uint32_t len) uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); bool pf = m_slot->m_rfLC->getPF(); - if (fid == FID_ETSI || fid == FID_DMRA || fid == FID_KENWOOD) { + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { if (fid == FID_KENWOOD && pf) errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames // don't bother trying to regenerate or perform FEC else errors = m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), - m_rfN, pf, errors, float(errors) / 1.41F); - } - if (errors > m_slot->m_silenceThreshold) { // get the LCSS from the EMB data::EMB emb; @@ -376,6 +371,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfErrs += errors; } + if (m_verbose) { + LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + m_rfN, fid, pf, errors, float(errors) / 1.41F); + } + m_slot->m_rfBits += 141U; m_slot->m_rfFrames++; @@ -603,12 +603,13 @@ bool Voice::process(uint8_t* data, uint32_t len) // send the original audio frame out uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - errors = m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, errs = %u/141 (%.1f%%)", - m_slot->m_slotNo, m_rfN, errors, float(errors) / 1.41F); - } + bool pf = m_slot->m_rfLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + errors = m_fec.regenerateDMR(data + 2U); if (errors > m_slot->m_silenceThreshold) { // get the LCSS from the EMB @@ -627,6 +628,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfErrs += errors; } + if (m_verbose) { + LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_rfN, fid, pf, errors, float(errors) / 1.41F); + } + m_slot->m_rfBits += 141U; m_slot->m_rfFrames++; @@ -947,12 +953,18 @@ void Voice::processNetwork(const data::NetData& dmrData) if (m_slot->m_netState == RS_NET_AUDIO) { uint8_t fid = m_slot->m_netLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, VOICE_SYNC audio, sequence no = %u, errs = %u/141 (%.1f%%)", - m_slot->m_slotNo, m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); - } + bool pf = m_slot->m_netLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + m_slot->m_netErrs = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); + } + + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, VOICE_SYNC audio, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_netN, fid, pf, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); } if (m_netN >= 5U) { @@ -997,13 +1009,20 @@ void Voice::processNetwork(const data::NetData& dmrData) return; uint8_t fid = m_slot->m_netLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), - m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); - } + bool pf = m_slot->m_netLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + m_slot->m_netErrs = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); } + + if (m_verbose) { + LogMessage(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), + m_netN, fid, pf, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); + } + m_slot->m_netBits += 141U; m_slot->m_netTGHang.start(); @@ -1296,7 +1315,7 @@ void Voice::insertSilence(uint32_t count) for (uint32_t i = 0U; i < count; i++) { // only use our silence frame if its AMBE audio data - if (fid == FID_ETSI || fid == FID_DMRA) { + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { if (i > 0U) { ::memcpy(data, SILENCE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); m_lastFrameValid = false; From cc5aa6dba24439bc2c0ac1c3ef5c37ee744d399e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 9 Jun 2025 12:07:52 -0400 Subject: [PATCH 022/133] remove filterHeaders (this is deprecated, HDUs aren't sent over the network); perform frame length validation for P25 network frames (unlike DMR and NXDN P25 frames are variable length, requring length validation to prevent buffer under or overflows); fix issue with addr variable not being freed in InfluxDB handler; --- configs/fne-config.example.yml | 4 +- src/common/network/BaseNetwork.cpp | 112 +++++++++++++++++++-- src/common/network/BaseNetwork.h | 10 +- src/fne/network/FNENetwork.cpp | 4 +- src/fne/network/FNENetwork.h | 1 - src/fne/network/callhandler/TagP25Data.cpp | 59 ++++------- src/fne/network/influxdb/InfluxDB.cpp | 30 ++++++ src/host/p25/Control.cpp | 6 ++ src/host/p25/packet/Voice.cpp | 10 +- 9 files changed, 175 insertions(+), 61 deletions(-) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index ddaeb551f..9a8c407cd 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -110,9 +110,7 @@ master: # (This is useful for networked FNEs that are have a mix of connections, and the originating traffic peer ID to non-Peer-Link FNEs should be masked.) maskOutboundPeerIDForNonPeerLink: false - # Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID). - filterHeaders: true - # Flag indicating that terminators will be filtered by destination ID (i.e. valid RID or valid TGID). + # Flag indicating that P25 terminators will be filtered by destination ID (i.e. valid RID or valid TGID). filterTerminators: true # Flag indicating the FNE will drop all inbound Unit-to-Unit calls. diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index 9a64ab588..babde1edd 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -513,7 +513,7 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength) /* Writes P25 LDU1 frame data to the network. */ -bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) +bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, P25DEF::FrameType::E frameType) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -655,6 +655,106 @@ bool BaseNetwork::hasP25Data() const return true; } +bool BaseNetwork::validateP25FrameLength(uint8_t& frameLength, uint32_t len, const P25DEF::DUID::E duid) +{ + using namespace p25::defines; + + // P25 network frame should never be less then 24 bytes + if (len < 24U) { + LogError(LOG_NET, "malformed P25 packet, len < 24, shouldn't happen"); + return false; + } + + // frame length should never be 0 + if (frameLength == 0U) { + LogError(LOG_NET, "DUID $%02X, sent with frame length of 0?", duid); + return false; + } + + // frame length should never be larger then the network packet length + if (frameLength > len) { + LogError(LOG_NET, "malformed P25 packet, frameLength > len (%u > %u), shouldn't happen", frameLength, len); + return false; + } + + // validate frame length, because P25 has variable network frame lengths we should be validating + // the actual frame length to ensure we don't have buffer overflow vulnerabilities + switch (duid) { + case DUID::HDU: + // HDU's aren't actually ever sent over the network, they are packaged with the first LDU1 for the + // initating superframe + return false; + case DUID::TDU: + // TDUs are sent with the P25 message header only + if (frameLength != 24U) { + LogError(LOG_NET, P25_TDU_STR ", malformed TDU, discard."); + break; + } + break; + + case DUID::LDU1: + // LDU1 with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_LDU1_STR ", malformed LDU1, discard."); + break; + } + break; + case DUID::VSELP1: + // VSELP1 frames aren't actually sent over the network right now + return false; + + case DUID::TSDU: + // oversized TSDU -- this shouldn't happen, truncate and only handle the size of the TSDU frame length + if (frameLength > P25_TSDU_FRAME_LENGTH_BYTES) + frameLength = P25_TSDU_FRAME_LENGTH_BYTES; + + // TSDU with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_TSDU_STR ", malformed TSDU, discard."); + break; + } + break; + + case DUID::LDU2: + // LDU2 with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_LDU2_STR ", malformed LDU2, discard."); + break; + } + break; + case DUID::VSELP2: + // VSELP2 frames aren't actually sent over the network right now + return false; + + case DUID::PDU: + // PDU with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_PDU_STR ", malformed PDU, discard."); + break; + } + break; + + + case DUID::TDULC: + // oversized TDULC -- this shouldn't happen, truncate and only handle the size of the TSDU frame length + if (frameLength > P25_TDULC_FRAME_LENGTH_BYTES) + frameLength = P25_TDULC_FRAME_LENGTH_BYTES; + + // TDULC with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_TSDU_STR ", malformed TDULC, discard."); + break; + } + break; + + default: + LogError(LOG_NET, "unsupported DUID $%02X", duid); + return false; + } + + return true; +} + /* Reads NXDN raw frame data from the NXDN ring buffer. */ UInt8Array BaseNetwork::readNXDN(bool& ret, uint32_t& frameLength) @@ -1056,12 +1156,9 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L createP25_MessageHdr(buffer, DUID::TSDU, control, lsd, FrameType::TERMINATOR); // pack raw P25 TSDU bytes - uint32_t count = MSG_HDR_SIZE; - ::memcpy(buffer + 24U, data, P25_TSDU_FRAME_LENGTH_BYTES); - count += P25_TSDU_FRAME_LENGTH_BYTES; - buffer[23U] = count; + buffer[23U] = P25_TSDU_FRAME_LENGTH_BYTES; if (m_debug) Utils::dump(1U, "Network Message, P25 TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD)); @@ -1085,12 +1182,9 @@ UInt8Array BaseNetwork::createP25_TDULCMessage(uint32_t& length, const p25::lc:: createP25_MessageHdr(buffer, DUID::TDULC, control, lsd, FrameType::TERMINATOR); // pack raw P25 TSDU bytes - uint32_t count = MSG_HDR_SIZE; - ::memcpy(buffer + 24U, data, P25_TDULC_FRAME_LENGTH_BYTES); - count += P25_TDULC_FRAME_LENGTH_BYTES; - buffer[23U] = count; + buffer[23U] = P25_TDULC_FRAME_LENGTH_BYTES; if (m_debug) Utils::dump(1U, "Network Message, P25 TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 37f83244a..e317f7fd1 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -371,7 +371,7 @@ namespace network * @returns bool True, if message was sent, otherwise false. */ virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - p25::defines::FrameType::E frameType); + P25DEF::FrameType::E frameType); /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. @@ -420,6 +420,14 @@ namespace network */ bool hasP25Data() const; + /** + * @brief Helper to validate a P25 network frame length. + * @param frameLength P25 encapsulated frame length. + * @param len Network packet length. + * @return bool True, if validated, otherwise false. + */ + bool validateP25FrameLength(uint8_t& frameLength, const uint32_t len, const P25DEF::DUID::E duid); + // Next Generation Digital Narrowband /** * @brief Reads NXDN raw frame data from the NXDN ring buffer. diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index c6e1ac070..6927ae3ab 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -98,8 +98,8 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_rejectUnknownRID(false), m_maskOutboundPeerID(false), m_maskOutboundPeerIDForNonPL(false), - m_filterHeaders(true), m_filterTerminators(true), + m_forceListUpdate(false), m_disallowU2U(false), m_dropU2UPeerTable(), m_enableInfluxDB(false), @@ -172,7 +172,6 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_parrotOnlyOriginating = conf["parrotOnlyToOrginiatingPeer"].as(false); m_restrictGrantToAffOnly = conf["restrictGrantToAffiliatedOnly"].as(false); - m_filterHeaders = conf["filterHeaders"].as(true); m_filterTerminators = conf["filterTerminators"].as(true); m_disablePacketData = conf["disablePacketData"].as(false); @@ -213,7 +212,6 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Mask Outbound Traffic Peer ID for Non-Peer Link: yes"); } LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); - LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); LogInfo(" Disallow Unit-to-Unit: %s", m_disallowU2U ? "yes" : "no"); LogInfo(" InfluxDB Reporting Enabled: %s", m_enableInfluxDB ? "yes" : "no"); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index f5796d7b2..74f60e1e4 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -603,7 +603,6 @@ namespace network bool m_maskOutboundPeerID; bool m_maskOutboundPeerIDForNonPL; - bool m_filterHeaders; bool m_filterTerminators; bool m_forceListUpdate; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 72d4ab8a0..208c2866d 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -69,6 +69,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId { hrc::hrc_t pktTime = hrc::now(); + // P25 network frame should never be less then 24 bytes + if (len < 24U) { + LogError(LOG_NET, "malformed P25 packet, len < 24, shouldn't happen"); + return false; + } + DECLARE_UINT8_ARRAY(buffer, len); ::memcpy(buffer, data, len); @@ -141,7 +147,10 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId lsd.setLSD1(lsd1); lsd.setLSD2(lsd2); - uint32_t frameLength = buffer[23U]; + uint8_t frameLength = buffer[23U]; + + if (!m_network->validateP25FrameLength(frameLength, len, duid)) + return false; // process a TSBK out into a class literal if possible std::unique_ptr tsbk; @@ -718,7 +727,7 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) { // are we receiving a TSDU? if (duid == DUID::TSDU) { - uint32_t frameLength = buffer[23U]; + uint32_t frameLength = P25_TSDU_FRAME_LENGTH_BYTES;//buffer[23U]; UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); ::memset(data.get(), 0x00U, frameLength); @@ -835,7 +844,7 @@ bool TagP25Data::processTSDUTo(uint8_t* buffer, uint32_t peerId, uint8_t duid) { // are we receiving a TSDU? if (duid == DUID::TSDU) { - uint32_t frameLength = buffer[23U]; + uint32_t frameLength = P25_TSDU_FRAME_LENGTH_BYTES;//buffer[23U]; UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); ::memset(data.get(), 0x00U, frameLength); @@ -948,6 +957,13 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, return false; if (!m_network->checkU2UDroppedPeer(peerId)) return true; + + // is this a U2U call? + lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); + if (!rid.radioDefault() && rid.radioEnabled()) { + return true; + } + return false; } @@ -970,43 +986,6 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, if (duid == DUID::TSDU || duid == DUID::PDU) return true; - if (duid == DUID::HDU) { - if (m_network->m_filterHeaders) { - if (control.getSrcId() != 0U && control.getDstId() != 0U) { - // is this a group call? - lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); - if (!tg.isInvalid()) { - return true; - } - - // is this peer excluded from the group? - std::vector exclusion = tg.config().exclusion(); - if (exclusion.size() > 0) { - auto it = std::find(exclusion.begin(), exclusion.end(), peerId); - if (it != exclusion.end()) { - return false; - } - } - - tg = m_network->m_tidLookup->findByRewrite(peerId, control.getDstId()); - if (!tg.isInvalid()) { - return true; - } - - // is this a U2U call? - lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); - if (!rid.radioDefault() && rid.radioEnabled()) { - return true; - } - - return false; - } - } - - // always permit a headers - return true; - } - if (duid == DUID::TDULC) { // always permit a terminator return true; diff --git a/src/fne/network/influxdb/InfluxDB.cpp b/src/fne/network/influxdb/InfluxDB.cpp index a070571cf..40f7fe604 100644 --- a/src/fne/network/influxdb/InfluxDB.cpp +++ b/src/fne/network/influxdb/InfluxDB.cpp @@ -76,6 +76,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); #endif // defined(_WIN32) closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } @@ -85,12 +87,16 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&sockOptVal, sizeof(int)) != 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sockOptVal, sizeof(int)) < 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // defined(_WIN32) @@ -101,6 +107,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (ioctlsocket(fd, FIONBIO, &flags) != 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #else @@ -108,12 +116,16 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (flags < 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // defined(_WIN32) @@ -140,6 +152,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (retryCnt > 5U) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, timed out while connecting"); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } @@ -153,6 +167,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); #endif // defined(_WIN32) closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } else if (ret > 0) { #if !defined(_WIN32) @@ -163,12 +179,16 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)(&valopt), &slen) < 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } if (valopt) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", valopt); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // !defined(_WIN32) @@ -176,6 +196,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin } else { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, timed out while connecting"); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } } while (true); @@ -188,6 +210,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (ioctlsocket(fd, FIONBIO, &flags) != 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #else @@ -195,12 +219,16 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (flags < 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d", errno); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // defined(_WIN32) @@ -259,5 +287,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #endif // close socket closesocket(fd); + if (addr != nullptr) + free(addr); return ret; } diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index a803a227f..7741baf67 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1392,6 +1392,12 @@ void Control::processNetwork() // process raw P25 data bytes UInt8Array data; uint8_t frameLength = buffer[23U]; + + if (!m_network->validateP25FrameLength(frameLength, length, duid)) { + m_network->resetP25(); + return; + } + if (duid == DUID::PDU) { frameLength = length; data = std::unique_ptr(new uint8_t[length]); diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 95290002a..a1f6a98a3 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -538,8 +538,6 @@ bool Voice::process(uint8_t* data, uint32_t len) // add status bits P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); - writeNetwork(buffer, DUID::HDU); - if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1008,8 +1006,6 @@ bool Voice::process(uint8_t* data, uint32_t len) // add status bits P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); - writeNetwork(buffer, DUID::HDU); - if (m_p25->m_duplex) { buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1567,9 +1563,15 @@ void Voice::writeNetwork(const uint8_t *data, defines::DUID::E duid, defines::Fr case DUID::LDU1: m_p25->m_network->writeP25LDU1(m_rfLC, m_rfLSD, data, frameType); break; + case DUID::VSELP1: + // ignore VSELP1 + break; case DUID::LDU2: m_p25->m_network->writeP25LDU2(m_rfLC, m_rfLSD, data); break; + case DUID::VSELP2: + // ignore VSELP2 + break; case DUID::TDU: case DUID::TDULC: m_p25->m_network->writeP25TDU(m_rfLC, m_rfLSD); From efd7394521925208bb7b0acf3629cf6fb0f1d84e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 9 Jun 2025 23:18:41 -0400 Subject: [PATCH 023/133] [EXPERIMENTAL] add very initial support for dvmpatch to talk to a MMDVM P25 Gateway in P25 mode; --- configs/patch-config.example.yml | 8 + src/patch/CMakeLists.txt | 2 + src/patch/HostPatch.cpp | 457 ++++++++++++++++++++++++++++- src/patch/HostPatch.h | 45 +++ src/patch/mmdvm/P25Network.cpp | 482 +++++++++++++++++++++++++++++++ src/patch/mmdvm/P25Network.h | 121 ++++++++ src/patch/network/PeerNetwork.h | 2 +- 7 files changed, 1105 insertions(+), 12 deletions(-) create mode 100644 src/patch/mmdvm/P25Network.cpp create mode 100644 src/patch/mmdvm/P25Network.h diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index d2997368b..174cf3408 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -68,6 +68,11 @@ network: # Flag indicating whether or not the patch is two-way. twoWay: false + # Hostname/IP address of MMDVM gateway to connect to. + mmdvmGatewayAddress: 127.0.0.1 + # Port number to connect to. + mmdvmGatewayPort: 42020 + system: # Textual Name identity: PATCH @@ -75,6 +80,9 @@ system: # Digital mode (1 - DMR, 2 - P25). digiMode: 1 + # Flag indicating whether or not the patch is from/to a MMDVM P25 reflector. + mmdvmP25Reflector: false + # Flag indicating whether or not trace logging is enabled. trace: false # Flag indicating whether or not debug logging is enabled. diff --git a/src/patch/CMakeLists.txt b/src/patch/CMakeLists.txt index d04d50046..d6f6f46a5 100644 --- a/src/patch/CMakeLists.txt +++ b/src/patch/CMakeLists.txt @@ -8,6 +8,8 @@ # * # */ file(GLOB patch_SRC + "src/patch/mmdvm/*.h" + "src/patch/mmdvm/*.cpp" "src/patch/network/*.h" "src/patch/network/*.cpp" "src/patch/*.h" diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index cd040b0d4..4f6025a5b 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -18,6 +18,7 @@ #include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/LC.h" #include "common/p25/lc/LC.h" +#include "common/p25/Sync.h" #include "common/p25/P25Utils.h" #include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" @@ -64,6 +65,16 @@ HostPatch::HostPatch(const std::string& confFile) : m_srcSlot(1U), m_dstTGId(0U), m_dstSlot(1U), + m_twoWayPatch(false), + m_mmdvmP25Reflector(false), + m_mmdvmP25Net(nullptr), + m_netState(RS_NET_IDLE), + m_netLC(), + m_gotNetLDU1(false), + m_netLDU1(nullptr), + m_gotNetLDU2(false), + m_netLDU2(nullptr), + m_p25Audio(), m_identity(), m_digiMode(1U), m_dmrEmbeddedData(), @@ -75,12 +86,22 @@ HostPatch::HostPatch(const std::string& confFile) : m_trace(false), m_debug(false) { - /* stub */ + m_netLDU1 = new uint8_t[9U * 25U]; + m_netLDU2 = new uint8_t[9U * 25U]; + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + resetWithNullAudio(m_netLDU1, false); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + resetWithNullAudio(m_netLDU2, false); } /* Finalizes a instance of the HostPatch class. */ -HostPatch::~HostPatch() = default; +HostPatch::~HostPatch() +{ + delete[] m_netLDU1; + delete[] m_netLDU2; +} /* Executes the main FNE processing loop. */ @@ -166,6 +187,13 @@ int HostPatch::run() if (!ret) return EXIT_FAILURE; + // initialize MMDVM P25 reflector networking + if (m_mmdvmP25Reflector) { + ret = createMMDVMP25Network(); + if (!ret) + return EXIT_FAILURE; + } + /* ** Initialize Threads */ @@ -173,6 +201,11 @@ int HostPatch::run() if (!Thread::runAsThread(this, threadNetworkProcess)) return EXIT_FAILURE; + if (m_mmdvmP25Reflector) { + if (!Thread::runAsThread(this, threadMMDVMProcess)) + return EXIT_FAILURE; + } + ::LogInfoEx(LOG_HOST, "Patch is up and running"); m_running = true; @@ -196,6 +229,11 @@ int HostPatch::run() m_network->clock(ms); } + if (m_mmdvmP25Reflector) { + std::lock_guard lock(HostPatch::m_networkMutex); + m_mmdvmP25Net->clock(ms); + } + if (ms < 2U) Thread::sleep(1U); } @@ -229,12 +267,20 @@ bool HostPatch::readParams() m_grantDemand = systemConf["grantDemand"].as(false); + m_mmdvmP25Reflector = systemConf["mmdvmP25Reflector"].as(false); + + if (m_mmdvmP25Reflector && m_digiMode != TX_MODE_P25) { + LogError(LOG_HOST, "Patch does not currently support MMDVM patching in any mode other then P25."); + return false; + } + m_trace = systemConf["trace"].as(false); m_debug = systemConf["debug"].as(false); LogInfo("General Parameters"); LogInfo(" Digital Mode: %s", m_digiMode == TX_MODE_DMR ? "DMR" : "P25"); LogInfo(" Grant Demands: %s", m_grantDemand ? "yes" : "no"); + LogInfo(" MMDVM P25 Reflector Patch: %s", m_mmdvmP25Reflector ? "yes" : "no"); if (m_debug) { LogInfo(" Debug: yes"); @@ -401,6 +447,38 @@ bool HostPatch::createNetwork() return true; } +/* Initializes MMDVM network connectivity. */ + +bool HostPatch::createMMDVMP25Network() +{ + yaml::Node networkConf = m_conf["network"]; + + std::string address = networkConf["mmdvmGatewayAddress"].as(); + uint16_t port = (uint16_t)networkConf["mmdvmGatewayPort"].as(42020U); + bool debug = networkConf["debug"].as(false); + + LogInfo("MMDVM Network Parameters"); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + + if (debug) { + LogInfo(" Debug: yes"); + } + + // initialize networking + m_mmdvmP25Net = new mmdvm::P25Network(address, port, 0U, debug); + + bool ret = m_mmdvmP25Net->open(); + if (!ret) { + delete m_mmdvmP25Net; + m_mmdvmP25Net = nullptr; + LogError(LOG_HOST, "failed to initialize MMDVM networking!"); + return false; + } + + return true; +} + /* Helper to process DMR network traffic. */ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) @@ -761,13 +839,16 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (dstId != m_srcTGId && dstId != m_dstTGId) return; - uint32_t actualDstId = m_dstTGId; - if (m_twoWayPatch) { - if (dstId == m_dstTGId) - actualDstId = m_srcTGId; - } else { - if (dstId == m_dstTGId) - return; + uint32_t actualDstId = m_srcTGId; + if (!m_mmdvmP25Reflector) { + actualDstId = m_dstTGId; + if (m_twoWayPatch) { + if (dstId == m_dstTGId) + actualDstId = m_srcTGId; + } else { + if (dstId == m_dstTGId) + return; + } } if (m_network->getP25StreamId() != m_rxStreamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { @@ -902,7 +983,14 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } } - m_network->writeP25LDU1(control, lsd, netLDU, frameType); + if (m_mmdvmP25Reflector) { + ::memcpy(m_netLDU1, netLDU, count); + m_gotNetLDU1 = true; + + writeNet_LDU1(false); + } else { + m_network->writeP25LDU1(control, lsd, netLDU, frameType); + } } break; case DUID::LDU2: @@ -957,7 +1045,14 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setSrcId(srcId); control.setDstId(actualDstId); - m_network->writeP25LDU2(control, lsd, netLDU); + if (m_mmdvmP25Reflector) { + ::memcpy(m_netLDU2, netLDU, count); + m_gotNetLDU2 = true; + + writeNet_LDU2(false); + } else { + m_network->writeP25LDU2(control, lsd, netLDU); + } } break; @@ -975,6 +1070,188 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } } +/* Helper to check for an unflushed LDU1 packet. */ + +void HostPatch::checkNet_LDU1() +{ + if (m_netState == RS_NET_IDLE) + return; + + // check for an unflushed LDU1 + if ((m_netLDU1[10U] != 0x00U || m_netLDU1[26U] != 0x00U || m_netLDU1[55U] != 0x00U || + m_netLDU1[80U] != 0x00U || m_netLDU1[105U] != 0x00U || m_netLDU1[130U] != 0x00U || + m_netLDU1[155U] != 0x00U || m_netLDU1[180U] != 0x00U || m_netLDU1[204U] != 0x00U) && + m_gotNetLDU1) + writeNet_LDU1(false); +} + +/* Helper to write a network P25 LDU1 packet. */ + +void HostPatch::writeNet_LDU1(bool toFNE) +{ + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + if (toFNE) { + if (m_netState == RS_NET_IDLE) { + m_callInProgress = true; + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_rxStartTime = now; + + uint8_t lco = m_netLDU1[51U]; + uint8_t mfId = m_netLDU1[52U]; + uint32_t dstId = GET_UINT24(m_netLDU1, 76U); + uint32_t srcId = GET_UINT24(m_netLDU1, 101U); + + LogMessage(LOG_HOST, "P25, call start, srcId = %u, dstId = %u", srcId, dstId); + + lc::LC lc = lc::LC(); + m_netLC = lc; + m_netLC.setLCO(lco); + m_netLC.setMFId(mfId); + m_netLC.setDstId(dstId); + m_netLC.setSrcId(srcId); + + if (m_grantDemand) { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(p25::defines::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = 0x80U; + m_network->writeP25TDU(lc, lsd, controlByte); + } + } + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU1[201U]); + lsd.setLSD2(m_netLDU1[202U]); + + LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); + + m_network->writeP25LDU1(m_netLC, lsd, m_netLDU1, FrameType::DATA_UNIT); + + m_netState = RS_NET_AUDIO; + resetWithNullAudio(m_netLDU1, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + m_gotNetLDU1 = false; + } + else { + uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // network bursts have no NID + + m_netLC.encodeLDU1(buffer + 2U); + + // add the Audio + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 10U, 0U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 26U, 1U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 55U, 2U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 80U, 3U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 105U, 4U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 130U, 5U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 155U, 6U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 180U, 7U); + m_p25Audio.encode(buffer + 2U, m_netLDU1 + 204U, 8U); + + // add the Low Speed Data + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU1[201U]); + lsd.setLSD2(m_netLDU1[202U]); + lsd.encode(buffer + 2U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); + + m_mmdvmP25Net->writeLDU1(buffer, m_netLC, lsd, false); + + resetWithNullAudio(m_netLDU1, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + m_gotNetLDU1 = false; + } +} + +/* Helper to check for an unflushed LDU2 packet. */ + +void HostPatch::checkNet_LDU2() +{ + if (m_netState == RS_NET_IDLE) + return; + + // check for an unflushed LDU2 + if ((m_netLDU2[10U] != 0x00U || m_netLDU2[26U] != 0x00U || m_netLDU2[55U] != 0x00U || + m_netLDU2[80U] != 0x00U || m_netLDU2[105U] != 0x00U || m_netLDU2[130U] != 0x00U || + m_netLDU2[155U] != 0x00U || m_netLDU2[180U] != 0x00U || m_netLDU2[204U] != 0x00U) && + m_gotNetLDU2) { + writeNet_LDU2(false); + } +} + +/* Helper to write a network P25 LDU2 packet. */ + +void HostPatch::writeNet_LDU2(bool toFNE) +{ + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + if (toFNE) { + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU2[201U]); + lsd.setLSD2(m_netLDU2[202U]); + + LogMessage(LOG_NET, P25_LDU2_STR " audio"); + + m_network->writeP25LDU2(m_netLC, lsd, m_netLDU2); + + resetWithNullAudio(m_netLDU2, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + m_gotNetLDU2 = false; + } + else { + uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // network bursts have no NID + + // generate LDU2 data + m_netLC.encodeLDU2(buffer + 2U); + + // add the Audio + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 10U, 0U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 26U, 1U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 55U, 2U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 80U, 3U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 105U, 4U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 130U, 5U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 155U, 6U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 180U, 7U); + m_p25Audio.encode(buffer + 2U, m_netLDU2 + 204U, 8U); + + // add the Low Speed Data + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU2[201U]); + lsd.setLSD2(m_netLDU2[202U]); + lsd.encode(buffer + 2U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); + + m_mmdvmP25Net->writeLDU2(buffer, m_netLC, lsd, false); + + resetWithNullAudio(m_netLDU2, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + m_gotNetLDU2 = false; + } +} + /* Entry point to network processing thread. */ void* HostPatch::threadNetworkProcess(void* arg) @@ -1038,6 +1315,164 @@ void* HostPatch::threadNetworkProcess(void* arg) return nullptr; } +/* Entry point to MMDVM network processing thread. */ + +void* HostPatch::threadMMDVMProcess(void* arg) +{ + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + thread_t* th = (thread_t*)arg; + if (th != nullptr) { +#if defined(_WIN32) + ::CloseHandle(th->thread); +#else + ::pthread_detach(th->thread); +#endif // defined(_WIN32) + + std::string threadName("patch:mmdvm-net-process"); + HostPatch* patch = static_cast(th->obj); + if (patch == nullptr) { + g_killed = true; + LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); + } + + if (g_killed) { + delete th; + return nullptr; + } + + LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); +#ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); +#endif // _GNU_SOURCE + + while (!g_killed) { + if (!patch->m_running) { + Thread::sleep(1U); + continue; + } + + uint32_t length = 0U; + bool netReadRet = false; + if (patch->m_digiMode == TX_MODE_P25) { + std::lock_guard lock(HostPatch::m_networkMutex); + + DECLARE_UINT8_ARRAY(buffer, 100U); + uint32_t len = patch->m_mmdvmP25Net->read(buffer, 100U); + if (len != 0U) { + switch (buffer[0U]) { + // LDU1 + case DFSIFrameType::LDU1_VOICE1: + ::memcpy(patch->m_netLDU1 + 0U, buffer, 22U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE2: + ::memcpy(patch->m_netLDU1 + 25U, buffer, 14U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE3: + ::memcpy(patch->m_netLDU1 + 50U, buffer, 17U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE4: + ::memcpy(patch->m_netLDU1 + 75U, buffer, 17U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE5: + ::memcpy(patch->m_netLDU1 + 100U, buffer, 17U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE6: + ::memcpy(patch->m_netLDU1 + 125U, buffer, 17U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE7: + ::memcpy(patch->m_netLDU1 + 150U, buffer, 17U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE8: + ::memcpy(patch->m_netLDU1 + 175U, buffer, 17U); + patch->checkNet_LDU2(); + break; + case DFSIFrameType::LDU1_VOICE9: + ::memcpy(patch->m_netLDU1 + 200U, buffer, 16U); + patch->checkNet_LDU2(); + + if (patch->m_netState != RS_NET_IDLE) { + patch->m_gotNetLDU1 = true; + patch->writeNet_LDU1(true); + } + break; + + // LDU2 + case DFSIFrameType::LDU2_VOICE10: + ::memcpy(patch->m_netLDU2 + 0U, buffer, 22U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE11: + ::memcpy(patch->m_netLDU2 + 25U, buffer, 14U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE12: + ::memcpy(patch->m_netLDU2 + 50U, buffer, 17U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE13: + ::memcpy(patch->m_netLDU2 + 75U, buffer, 17U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE14: + ::memcpy(patch->m_netLDU2 + 100U, buffer, 17U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE15: + ::memcpy(patch->m_netLDU2 + 125U, buffer, 17U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE16: + ::memcpy(patch->m_netLDU2 + 150U, buffer, 17U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE17: + ::memcpy(patch->m_netLDU2 + 175U, buffer, 17U); + patch->checkNet_LDU1(); + break; + case DFSIFrameType::LDU2_VOICE18: + ::memcpy(patch->m_netLDU2 + 200U, buffer, 16U); + if (patch->m_netState == RS_NET_IDLE) { + patch->writeNet_LDU1(true); + } else { + patch->checkNet_LDU1(); + } + + patch->writeNet_LDU2(true); + break; + + case 0x80U: + { + patch->m_netState = RS_NET_IDLE; + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + patch->m_network->writeP25TDU(patch->m_netLC, lsd, 0U); + } + break; + default: + break; + } + } + } + + Thread::sleep(1U); + } + + LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; +} + /* Helper to reset IMBE buffer with null frames. */ void HostPatch::resetWithNullAudio(uint8_t* data, bool encrypted) diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h index 2e91a899a..d22ffd2e0 100644 --- a/src/patch/HostPatch.h +++ b/src/patch/HostPatch.h @@ -20,10 +20,13 @@ #include "common/dmr/data/EmbeddedData.h" #include "common/dmr/lc/LC.h" #include "common/dmr/lc/PrivacyLC.h" +#include "common/p25/lc/LC.h" +#include "common/p25/Audio.h" #include "common/network/udp/Socket.h" #include "common/yaml/Yaml.h" #include "common/Timer.h" #include "network/PeerNetwork.h" +#include "mmdvm/P25Network.h" #include #include @@ -73,6 +76,18 @@ class HOST_SW_API HostPatch { uint8_t m_dstSlot; bool m_twoWayPatch; + bool m_mmdvmP25Reflector; + mmdvm::P25Network* m_mmdvmP25Net; + + RPT_NET_STATE m_netState; + p25::lc::LC m_netLC; + bool m_gotNetLDU1; + uint8_t* m_netLDU1; + bool m_gotNetLDU2; + uint8_t* m_netLDU2; + + p25::Audio m_p25Audio; + std::string m_identity; uint8_t m_digiMode; @@ -101,6 +116,11 @@ class HOST_SW_API HostPatch { * @returns bool True, if network connectivity was initialized, otherwise false. */ bool createNetwork(); + /** + * @brief Initializes MMDVM network connectivity. + * @returns bool True, if network connectivity was initialized, otherwise false. + */ + bool createMMDVMP25Network(); /** * @brief Helper to process DMR network traffic. @@ -116,12 +136,37 @@ class HOST_SW_API HostPatch { */ void processP25Network(uint8_t* buffer, uint32_t length); + /** + * @brief Helper to check for an unflushed LDU1 packet. + */ + void checkNet_LDU1(); + /** + * @brief Helper to write a network P25 LDU1 packet. + * @param toFNE Flag indicating whether or not the packet is being written to the DVM FNE. + */ + void writeNet_LDU1(bool toFNE); + /** + * @brief Helper to check for an unflushed LDU2 packet. + */ + void checkNet_LDU2(); + /** + * @brief Helper to write a network P25 LDU2 packet. + * @param toFNE Flag indicating whether or not the packet is being written to the DVM FNE. + */ + void writeNet_LDU2(bool toFNE); + /** * @brief Entry point to network processing thread. * @param arg Instance of the thread_t structure. * @returns void* (Ignore) */ static void* threadNetworkProcess(void* arg); + /** + * @brief Entry point to MMDVM network processing thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadMMDVMProcess(void* arg); /** * @brief Helper to reset IMBE buffer with null frames. diff --git a/src/patch/mmdvm/P25Network.cpp b/src/patch/mmdvm/P25Network.cpp new file mode 100644 index 000000000..3c68b6e2d --- /dev/null +++ b/src/patch/mmdvm/P25Network.cpp @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - TG Patch + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2009-2014,2016,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "patch/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "mmdvm/P25Network.h" + +using namespace p25::defines; +using namespace network::udp; +using namespace mmdvm; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/* +** bryanb: we purposely do handling this way, instead of using the DFSI classes, to ensure we maintain compat +** with upstream MMDVM, extra data handled from our DFSI could confuse the P25 gateway +*/ + +const uint8_t REC62[] = { + 0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC63[] = { + 0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC64[] = { + 0x64U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC65[] = { + 0x65U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC66[] = { + 0x66U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC67[] = { + 0x67U, 0xF0U, 0x9DU, 0x6AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC68[] = { + 0x68U, 0x19U, 0xD4U, 0x26U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC69[] = { + 0x69U, 0xE0U, 0xEBU, 0x7BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6A[] = { + 0x6AU, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC6B[] = { + 0x6BU, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC6C[] = { + 0x6CU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6D[] = { + 0x6DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6E[] = { + 0x6EU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6F[] = { + 0x6FU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC70[] = { + 0x70U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC71[] = { + 0x71U, 0xACU, 0xB8U, 0xA4U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC72[] = { + 0x72U, 0x9BU, 0xDCU, 0x75U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC73[] = { + 0x73U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC80[] = { + 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint32_t BUFFER_LENGTH = 100U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the P25Network class. */ + +P25Network::P25Network(const std::string& gatewayAddress, uint16_t gatewayPort, uint16_t localPort, bool debug) : + m_socket(localPort), + m_addr(), + m_addrLen(0U), + m_debug(debug), + m_buffer(1000U, "MMDVM P25 Network"), + m_audio() +{ + if (Socket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) + m_addrLen = 0U; +} + +/* Finalizes a instance of the P25Network class. */ + +P25Network::~P25Network() = default; + +/* Reads P25 raw frame data from the P25 ring buffer. */ + +uint32_t P25Network::read(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + + if (m_buffer.isEmpty()) + return 0U; + + uint8_t c = 0U; + m_buffer.get(&c, 1U); + + if (c == 0U) { + return 0U; + } + + if (c <= length) { + return 0U; + } + + m_buffer.get(data, c); + return c; +} + +/* Writes P25 LDU1 frame data to the network. */ + +bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end) +{ + assert(ldu1 != nullptr); + + uint8_t buffer[22U]; + + // The '62' record + ::memcpy(buffer, REC62, 22U); + m_audio.decode(ldu1, buffer + 10U, 0U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 22U); + + bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '63' record + ::memcpy(buffer, REC63, 14U); + m_audio.decode(ldu1, buffer + 1U, 1U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 14U); + + ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '64' record + ::memcpy(buffer, REC64, 17U); + buffer[1U] = control.getLCO(); + buffer[2U] = control.getMFId(); + m_audio.decode(ldu1, buffer + 5U, 2U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '65' record + ::memcpy(buffer, REC65, 17U); + uint32_t id = control.getDstId(); + buffer[1U] = (id >> 16) & 0xFFU; + buffer[2U] = (id >> 8) & 0xFFU; + buffer[3U] = (id >> 0) & 0xFFU; + m_audio.decode(ldu1, buffer + 5U, 3U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '66' record + ::memcpy(buffer, REC66, 17U); + id = control.getSrcId(); + buffer[1U] = (id >> 16) & 0xFFU; + buffer[2U] = (id >> 8) & 0xFFU; + buffer[3U] = (id >> 0) & 0xFFU; + m_audio.decode(ldu1, buffer + 5U, 4U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '67' record + ::memcpy(buffer, REC67, 17U); + m_audio.decode(ldu1, buffer + 5U, 5U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '68' record + ::memcpy(buffer, REC68, 17U); + m_audio.decode(ldu1, buffer + 5U, 6U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '69' record + ::memcpy(buffer, REC69, 17U); + m_audio.decode(ldu1, buffer + 5U, 7U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6A' record + ::memcpy(buffer, REC6A, 16U); + buffer[1U] = lsd.getLSD1(); + buffer[2U] = lsd.getLSD2(); + m_audio.decode(ldu1, buffer + 4U, 8U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 16U); + + ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); + if (!ret) + return false; + + if (end) { + if (m_debug) + Utils::dump(1U, "MMDVM Network END Sent", REC80, 17U); + + ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); + if (!ret) + return false; + } + + return true; +} + +/* Writes P25 LDU2 frame data to the network. */ + +bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end) +{ + assert(ldu2 != nullptr); + + uint8_t buffer[22U]; + + // The '6B' record + ::memcpy(buffer, REC6B, 22U); + m_audio.decode(ldu2, buffer + 10U, 0U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 22U); + + bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6C' record + ::memcpy(buffer, REC6C, 14U); + m_audio.decode(ldu2, buffer + 1U, 1U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 14U); + + ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); + if (!ret) + return false; + + uint8_t mi[MI_LENGTH_BYTES]; + control.getMI(mi); + + // The '6D' record + ::memcpy(buffer, REC6D, 17U); + buffer[1U] = mi[0U]; + buffer[2U] = mi[1U]; + buffer[3U] = mi[2U]; + m_audio.decode(ldu2, buffer + 5U, 2U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6E' record + ::memcpy(buffer, REC6E, 17U); + buffer[1U] = mi[3U]; + buffer[2U] = mi[4U]; + buffer[3U] = mi[5U]; + m_audio.decode(ldu2, buffer + 5U, 3U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6F' record + ::memcpy(buffer, REC6F, 17U); + buffer[1U] = mi[6U]; + buffer[2U] = mi[7U]; + buffer[3U] = mi[8U]; + m_audio.decode(ldu2, buffer + 5U, 4U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '70' record + ::memcpy(buffer, REC70, 17U); + buffer[1U] = control.getAlgId(); + uint32_t id = control.getKId(); + buffer[2U] = (id >> 8) & 0xFFU; + buffer[3U] = (id >> 0) & 0xFFU; + m_audio.decode(ldu2, buffer + 5U, 5U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '71' record + ::memcpy(buffer, REC71, 17U); + m_audio.decode(ldu2, buffer + 5U, 6U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '72' record + ::memcpy(buffer, REC72, 17U); + m_audio.decode(ldu2, buffer + 5U, 7U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '73' record + ::memcpy(buffer, REC73, 16U); + buffer[1U] = lsd.getLSD1(); + buffer[2U] = lsd.getLSD2(); + m_audio.decode(ldu2, buffer + 4U, 8U); + + if (m_debug) + Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 16U); + + ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); + if (!ret) + return false; + + if (end) { + if (m_debug) + Utils::dump(1U, "MMDVM Network END Sent", REC80, 17U); + + ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); + if (!ret) + return false; + } + + return true; +} + +/* Updates the timer by the passed number of milliseconds. */ + +void P25Network::clock(uint32_t ms) +{ + uint8_t buffer[BUFFER_LENGTH]; + + sockaddr_storage address; + uint32_t addrLen; + int length = m_socket.read(buffer, BUFFER_LENGTH, address, addrLen); + if (length <= 0) + return; + + if (!Socket::match(m_addr, address)) { + LogMessage(LOG_NET, "MMDVM, packet received from an invalid source"); + return; + } + + if (m_debug) + Utils::dump(1U, "MMDVM Network Data Received", buffer, length); + + uint8_t c = length; + m_buffer.addData(&c, 1U); + + m_buffer.addData(buffer, length); +} + +/* Helper to determine if we are connected to a MMDVM gateway. */ + +bool P25Network::isConnected() const +{ + return (m_addrLen != 0); +} + +/* Opens connection to the network. */ + +bool P25Network::open() +{ + if (m_addrLen == 0U) { + LogError(LOG_NET, "MMDVM, Unable to resolve the address of the P25 Gateway"); + return false; + } + + LogMessage(LOG_NET, "MMDVM, Opening P25 network connection"); + + return m_socket.open(m_addr); +} + +/* Closes connection to the network. */ + +void P25Network::close() +{ + m_socket.close(); + + LogMessage(LOG_NET, "MMDVM, Closing P25 network connection"); +} diff --git a/src/patch/mmdvm/P25Network.h b/src/patch/mmdvm/P25Network.h new file mode 100644 index 000000000..f64c154b6 --- /dev/null +++ b/src/patch/mmdvm/P25Network.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - TG Patch + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2009-2014,2016,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup patch_mmdvm MMDVM Networking + * @brief Implementation for the patch MMDVM networking. + * @ingroup patch + * + * @file PeerNetwork.h + * @ingroup patch_mmdvm + * @file PeerNetwork.cpp + * @ingroup patch_mmdvm + */ +#if !defined(__P25_NETWORK_H__) +#define __P25_NETWORK_H__ + +#include "Defines.h" +#include "common/p25/Audio.h" +#include "common/p25/data/LowSpeedData.h" +#include "common/p25/lc/LC.h" +#include "common/network/udp/Socket.h" +#include "common/RingBuffer.h" + +#include +#include + +namespace mmdvm +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the MMDVM networking logic. + // --------------------------------------------------------------------------- + + class P25Network { + public: + /** + * @brief Initializes a new instance of the P25Network class. + * @param gatewayAddress Network Hostname/IP address to connect to. + * @param gatewayPort Network port number. + * @param localPort + * @param debug Flag indicating whether network debug is enabled. + */ + P25Network(const std::string& gatewayAddress, uint16_t gatewayPort, uint16_t localPort, bool debug); + /** + * @brief Finalizes a instance of the P25Network class. + */ + ~P25Network(); + + /** + * @brief Reads P25 raw frame data from the P25 ring buffer. + * @param[out] data Buffer to write received frame data to. + * @param[out] length Length in bytes of received frame. + * @returns + */ + uint32_t read(uint8_t* data, uint32_t length); + + /** + * @brief Writes P25 LDU1 frame data to the network. + * @param[in] ldu1 Buffer containing P25 LDU1 data to send. + * @param[in] control Instance of p25::lc::LC containing link control data. + * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. + * @param[in] end + * @returns bool True, if message was sent, otherwise false. + */ + bool writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end); + + /** + * @brief Writes P25 LDU2 frame data to the network. + * @param[in] ldu1 Buffer containing P25 LDU2 data to send. + * @param[in] control Instance of p25::lc::LC containing link control data. + * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. + * @param[in] end + * @returns bool True, if message was sent, otherwise false. + */ + bool writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end); + + /** + * @brief Updates the timer by the passed number of milliseconds. + * @param ms Number of milliseconds. + */ + void clock(uint32_t ms); + + /** + * @brief Helper to determine if we are connected to a MMDVM gateway. + * @return bool True, if connected, otherwise false. + */ + bool isConnected() const; + + /** + * @brief Opens connection to the network. + * @returns bool True, if networking has started, otherwise false. + */ + bool open(); + + /** + * @brief Closes connection to the network. + */ + void close(); + + private: + network::udp::Socket m_socket; + + sockaddr_storage m_addr; + uint32_t m_addrLen; + + bool m_debug; + + RingBuffer m_buffer; + + p25::Audio m_audio; + }; +} // namespace mmdvm + +#endif // __P25_NETWORK_H__ diff --git a/src/patch/network/PeerNetwork.h b/src/patch/network/PeerNetwork.h index 081856bc2..b1e7b460f 100644 --- a/src/patch/network/PeerNetwork.h +++ b/src/patch/network/PeerNetwork.h @@ -9,7 +9,7 @@ */ /** * @defgroup patch_network Networking - * @brief Implementation for the bridge networking. + * @brief Implementation for the patch networking. * @ingroup patch * * @file PeerNetwork.h From 3c1ebab9a5bbe52312ae675cc461063a9ae68ccd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 10 Jun 2025 13:13:47 -0400 Subject: [PATCH 024/133] fixup dvmpatch support for talking to MMDVM P25 Gateway; --- configs/patch-config.example.yml | 2 + src/patch/HostPatch.cpp | 89 +++++++++++--------------------- src/patch/HostPatch.h | 3 -- src/patch/mmdvm/P25Network.cpp | 77 ++++++++++++++------------- src/patch/mmdvm/P25Network.h | 3 -- 5 files changed, 70 insertions(+), 104 deletions(-) diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index 174cf3408..b0fedfda8 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -72,6 +72,8 @@ network: mmdvmGatewayAddress: 127.0.0.1 # Port number to connect to. mmdvmGatewayPort: 42020 + # Local port number for gateway to connect to. + localGatewayPort: 32010 system: # Textual Name diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 4f6025a5b..787730fd2 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -74,7 +74,6 @@ HostPatch::HostPatch(const std::string& confFile) : m_netLDU1(nullptr), m_gotNetLDU2(false), m_netLDU2(nullptr), - m_p25Audio(), m_identity(), m_digiMode(1U), m_dmrEmbeddedData(), @@ -455,18 +454,20 @@ bool HostPatch::createMMDVMP25Network() std::string address = networkConf["mmdvmGatewayAddress"].as(); uint16_t port = (uint16_t)networkConf["mmdvmGatewayPort"].as(42020U); + uint16_t localPort = (uint16_t)networkConf["localGatewayPort"].as(32010U); bool debug = networkConf["debug"].as(false); LogInfo("MMDVM Network Parameters"); LogInfo(" Address: %s", address.c_str()); LogInfo(" Port: %u", port); + LogInfo(" Local Port: %u", localPort); if (debug) { LogInfo(" Debug: yes"); } // initialize networking - m_mmdvmP25Net = new mmdvm::P25Network(address, port, 0U, debug); + m_mmdvmP25Net = new mmdvm::P25Network(address, port, localPort, debug); bool ret = m_mmdvmP25Net->open(); if (!ret) { @@ -984,8 +985,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } if (m_mmdvmP25Reflector) { - ::memcpy(m_netLDU1, netLDU, count); + ::memcpy(m_netLDU1, netLDU, 9U * 25U); m_gotNetLDU1 = true; + m_netLC = control; writeNet_LDU1(false); } else { @@ -1046,8 +1048,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setDstId(actualDstId); if (m_mmdvmP25Reflector) { - ::memcpy(m_netLDU2, netLDU, count); + ::memcpy(m_netLDU2, netLDU, 9U * 25U); m_gotNetLDU2 = true; + m_netLC = control; writeNet_LDU2(false); } else { @@ -1133,46 +1136,27 @@ void HostPatch::writeNet_LDU1(bool toFNE) LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); + if (m_debug) + Utils::dump(1U, "MMDVM -> DVM LDU1", m_netLDU1, 9U * 25U); + m_network->writeP25LDU1(m_netLC, lsd, m_netLDU1, FrameType::DATA_UNIT); m_netState = RS_NET_AUDIO; - resetWithNullAudio(m_netLDU1, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + resetWithNullAudio(m_netLDU1, false); m_gotNetLDU1 = false; } else { - uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); - - // generate Sync - Sync::addP25Sync(buffer + 2U); - - // network bursts have no NID - - m_netLC.encodeLDU1(buffer + 2U); - - // add the Audio - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 10U, 0U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 26U, 1U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 55U, 2U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 80U, 3U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 105U, 4U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 130U, 5U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 155U, 6U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 180U, 7U); - m_p25Audio.encode(buffer + 2U, m_netLDU1 + 204U, 8U); + if (m_debug) + Utils::dump(1U, "DVM -> MMDVM LDU1", m_netLDU1, 9U * 25U); // add the Low Speed Data p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); lsd.setLSD1(m_netLDU1[201U]); lsd.setLSD2(m_netLDU1[202U]); - lsd.encode(buffer + 2U); - - // add status bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); - m_mmdvmP25Net->writeLDU1(buffer, m_netLC, lsd, false); + m_mmdvmP25Net->writeLDU1(m_netLDU1, m_netLC, lsd, false); - resetWithNullAudio(m_netLDU1, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + resetWithNullAudio(m_netLDU1, false); m_gotNetLDU1 = false; } } @@ -1208,46 +1192,26 @@ void HostPatch::writeNet_LDU2(bool toFNE) LogMessage(LOG_NET, P25_LDU2_STR " audio"); + if (m_debug) + Utils::dump(1U, "MMDVM -> DVM LDU2", m_netLDU2, 9U * 25U); + m_network->writeP25LDU2(m_netLC, lsd, m_netLDU2); - resetWithNullAudio(m_netLDU2, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + resetWithNullAudio(m_netLDU2, false); m_gotNetLDU2 = false; } else { - uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); - - // generate Sync - Sync::addP25Sync(buffer + 2U); - - // network bursts have no NID - - // generate LDU2 data - m_netLC.encodeLDU2(buffer + 2U); - - // add the Audio - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 10U, 0U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 26U, 1U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 55U, 2U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 80U, 3U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 105U, 4U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 130U, 5U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 155U, 6U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 180U, 7U); - m_p25Audio.encode(buffer + 2U, m_netLDU2 + 204U, 8U); + if (m_debug) + Utils::dump(1U, "DVM -> MMDVM LDU2", m_netLDU2, 9U * 25U); // add the Low Speed Data p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); lsd.setLSD1(m_netLDU2[201U]); lsd.setLSD2(m_netLDU2[202U]); - lsd.encode(buffer + 2U); - - // add status bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); - m_mmdvmP25Net->writeLDU2(buffer, m_netLC, lsd, false); + m_mmdvmP25Net->writeLDU2(m_netLDU2, m_netLC, lsd, false); - resetWithNullAudio(m_netLDU2, m_netLC.getAlgId() != P25DEF::ALGO_UNENCRYPT); + resetWithNullAudio(m_netLDU2, false); m_gotNetLDU2 = false; } } @@ -1457,7 +1421,14 @@ void* HostPatch::threadMMDVMProcess(void* arg) patch->m_network->writeP25TDU(patch->m_netLC, lsd, 0U); } break; + + case 0xF0U: + case 0xF1U: + // these are MMDVM control bytes -- we ignore these + break; + default: + LogError(LOG_NET, "unknown opcode from MMDVM gateway $%02X", buffer[0U]); break; } } diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h index d22ffd2e0..dd0ad07d8 100644 --- a/src/patch/HostPatch.h +++ b/src/patch/HostPatch.h @@ -21,7 +21,6 @@ #include "common/dmr/lc/LC.h" #include "common/dmr/lc/PrivacyLC.h" #include "common/p25/lc/LC.h" -#include "common/p25/Audio.h" #include "common/network/udp/Socket.h" #include "common/yaml/Yaml.h" #include "common/Timer.h" @@ -86,8 +85,6 @@ class HOST_SW_API HostPatch { bool m_gotNetLDU2; uint8_t* m_netLDU2; - p25::Audio m_p25Audio; - std::string m_identity; uint8_t m_digiMode; diff --git a/src/patch/mmdvm/P25Network.cpp b/src/patch/mmdvm/P25Network.cpp index 3c68b6e2d..352f5cda2 100644 --- a/src/patch/mmdvm/P25Network.cpp +++ b/src/patch/mmdvm/P25Network.cpp @@ -122,8 +122,7 @@ P25Network::P25Network(const std::string& gatewayAddress, uint16_t gatewayPort, m_addr(), m_addrLen(0U), m_debug(debug), - m_buffer(1000U, "MMDVM P25 Network"), - m_audio() + m_buffer(1000U, "MMDVM P25 Network") { if (Socket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) m_addrLen = 0U; @@ -149,7 +148,7 @@ uint32_t P25Network::read(uint8_t* data, uint32_t length) return 0U; } - if (c <= length) { + if (c > length) { return 0U; } @@ -167,10 +166,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons // The '62' record ::memcpy(buffer, REC62, 22U); - m_audio.decode(ldu1, buffer + 10U, 0U); + ::memcpy(buffer + 10U, ldu1 + 10U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 22U); + Utils::dump(1U, "MMDVM Network $62 LDU1 Sent", buffer, 22U); bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); if (!ret) @@ -178,10 +177,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons // The '63' record ::memcpy(buffer, REC63, 14U); - m_audio.decode(ldu1, buffer + 1U, 1U); + ::memcpy(buffer + 1U, ldu1 + 26U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 14U); + Utils::dump(1U, "MMDVM Network $63 LDU1 Sent", buffer, 14U); ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); if (!ret) @@ -191,10 +190,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer, REC64, 17U); buffer[1U] = control.getLCO(); buffer[2U] = control.getMFId(); - m_audio.decode(ldu1, buffer + 5U, 2U); + ::memcpy(buffer + 5U, ldu1 + 55U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $64 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -206,10 +205,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons buffer[1U] = (id >> 16) & 0xFFU; buffer[2U] = (id >> 8) & 0xFFU; buffer[3U] = (id >> 0) & 0xFFU; - m_audio.decode(ldu1, buffer + 5U, 3U); + ::memcpy(buffer + 5U, ldu1 + 80U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $65 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -221,10 +220,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons buffer[1U] = (id >> 16) & 0xFFU; buffer[2U] = (id >> 8) & 0xFFU; buffer[3U] = (id >> 0) & 0xFFU; - m_audio.decode(ldu1, buffer + 5U, 4U); + ::memcpy(buffer + 5U, ldu1 + 105U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $66 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -232,10 +231,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons // The '67' record ::memcpy(buffer, REC67, 17U); - m_audio.decode(ldu1, buffer + 5U, 5U); + ::memcpy(buffer + 5U, ldu1 + 130U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $67 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -243,10 +242,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons // The '68' record ::memcpy(buffer, REC68, 17U); - m_audio.decode(ldu1, buffer + 5U, 6U); + ::memcpy(buffer + 5U, ldu1 + 155U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $68 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -254,10 +253,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons // The '69' record ::memcpy(buffer, REC69, 17U); - m_audio.decode(ldu1, buffer + 5U, 7U); + ::memcpy(buffer + 5U, ldu1 + 180U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $69 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -267,10 +266,10 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer, REC6A, 16U); buffer[1U] = lsd.getLSD1(); buffer[2U] = lsd.getLSD2(); - m_audio.decode(ldu1, buffer + 4U, 8U); + ::memcpy(buffer + 5U, ldu1 + 204U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU1 Sent", buffer, 16U); + Utils::dump(1U, "MMDVM Network $6A LDU1 Sent", buffer, 16U); ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); if (!ret) @@ -298,10 +297,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons // The '6B' record ::memcpy(buffer, REC6B, 22U); - m_audio.decode(ldu2, buffer + 10U, 0U); + ::memcpy(buffer + 10U, ldu2 + 10U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 22U); + Utils::dump(1U, "MMDVM Network $6B LDU2 Sent", buffer, 22U); bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); if (!ret) @@ -309,10 +308,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons // The '6C' record ::memcpy(buffer, REC6C, 14U); - m_audio.decode(ldu2, buffer + 1U, 1U); + ::memcpy(buffer + 1U, ldu2 + 26U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 14U); + Utils::dump(1U, "MMDVM Network $6C LDU2 Sent", buffer, 14U); ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); if (!ret) @@ -326,10 +325,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons buffer[1U] = mi[0U]; buffer[2U] = mi[1U]; buffer[3U] = mi[2U]; - m_audio.decode(ldu2, buffer + 5U, 2U); + ::memcpy(buffer + 5U, ldu2 + 55U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $6D LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -340,10 +339,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons buffer[1U] = mi[3U]; buffer[2U] = mi[4U]; buffer[3U] = mi[5U]; - m_audio.decode(ldu2, buffer + 5U, 3U); + ::memcpy(buffer + 5U, ldu2 + 80U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $6E LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -354,10 +353,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons buffer[1U] = mi[6U]; buffer[2U] = mi[7U]; buffer[3U] = mi[8U]; - m_audio.decode(ldu2, buffer + 5U, 4U); + ::memcpy(buffer + 5U, ldu2 + 105U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $6F LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -369,10 +368,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons uint32_t id = control.getKId(); buffer[2U] = (id >> 8) & 0xFFU; buffer[3U] = (id >> 0) & 0xFFU; - m_audio.decode(ldu2, buffer + 5U, 5U); + ::memcpy(buffer + 5U, ldu2 + 130U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $70 LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -380,10 +379,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons // The '71' record ::memcpy(buffer, REC71, 17U); - m_audio.decode(ldu2, buffer + 5U, 6U); + ::memcpy(buffer + 5U, ldu2 + 155U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $71 LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -391,10 +390,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons // The '72' record ::memcpy(buffer, REC72, 17U); - m_audio.decode(ldu2, buffer + 5U, 7U); + ::memcpy(buffer + 5U, ldu2 + 180U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 17U); + Utils::dump(1U, "MMDVM Network $72 LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -404,10 +403,10 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer, REC73, 16U); buffer[1U] = lsd.getLSD1(); buffer[2U] = lsd.getLSD2(); - m_audio.decode(ldu2, buffer + 4U, 8U); + ::memcpy(buffer + 5U, ldu2 + 204U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network LDU2 Sent", buffer, 16U); + Utils::dump(1U, "MMDVM Network $73 LDU2 Sent", buffer, 16U); ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); if (!ret) diff --git a/src/patch/mmdvm/P25Network.h b/src/patch/mmdvm/P25Network.h index f64c154b6..1b1855a4d 100644 --- a/src/patch/mmdvm/P25Network.h +++ b/src/patch/mmdvm/P25Network.h @@ -22,7 +22,6 @@ #define __P25_NETWORK_H__ #include "Defines.h" -#include "common/p25/Audio.h" #include "common/p25/data/LowSpeedData.h" #include "common/p25/lc/LC.h" #include "common/network/udp/Socket.h" @@ -113,8 +112,6 @@ namespace mmdvm bool m_debug; RingBuffer m_buffer; - - p25::Audio m_audio; }; } // namespace mmdvm From 1d83f8e1cc49c4b04e9cb1f1ac4eabcf9c26945a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 10 Jun 2025 13:16:24 -0400 Subject: [PATCH 025/133] fix up handling of MMDVM call termination; --- src/patch/HostPatch.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 787730fd2..6418d8d0b 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -1417,8 +1417,27 @@ void* HostPatch::threadMMDVMProcess(void* arg) case 0x80U: { patch->m_netState = RS_NET_IDLE; + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - patch->m_network->writeP25TDU(patch->m_netLC, lsd, 0U); + + LogMessage(LOG_HOST, P25_TDU_STR); + + uint8_t controlByte = 0x00U; + patch->m_network->writeP25TDU(patch->m_netLC, lsd, controlByte); + + if (patch->m_rxStartTime > 0U) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t diff = now - patch->m_rxStartTime; + + LogMessage(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", patch->m_netLC.getSrcId(), patch->m_netLC.getDstId(), diff / 1000U); + } + + patch->m_rxStartTime = 0U; + patch->m_rxStreamId = 0U; + + patch->m_callInProgress = false; + patch->m_rxStartTime = 0U; + patch->m_rxStreamId = 0U; } break; From b8fd50d0f8d0ef299808d18cc66d2ffd05b79736 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 10 Jun 2025 13:25:52 -0400 Subject: [PATCH 026/133] more work on better signalling end of call for MMDVM P25 gateway patches; better log traffic from MMDVM; --- src/patch/HostPatch.cpp | 19 ++++++++++++------- src/patch/mmdvm/P25Network.cpp | 14 ++++++++++++++ src/patch/mmdvm/P25Network.h | 5 +++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 6418d8d0b..b0e6abef4 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -887,8 +887,13 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) LogMessage(LOG_HOST, P25_TDU_STR); - uint8_t controlByte = 0x00U; - m_network->writeP25TDU(lc, lsd, controlByte); + if (m_mmdvmP25Reflector) { + m_mmdvmP25Net->writeTDU(); + } + else { + uint8_t controlByte = 0x00U; + m_network->writeP25TDU(lc, lsd, controlByte); + } if (m_rxStartTime > 0U) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -1108,7 +1113,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) uint32_t dstId = GET_UINT24(m_netLDU1, 76U); uint32_t srcId = GET_UINT24(m_netLDU1, 101U); - LogMessage(LOG_HOST, "P25, call start, srcId = %u, dstId = %u", srcId, dstId); + LogMessage(LOG_HOST, "MMDVM P25, call start, srcId = %u, dstId = %u", srcId, dstId); lc::LC lc = lc::LC(); m_netLC = lc; @@ -1134,7 +1139,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) lsd.setLSD1(m_netLDU1[201U]); lsd.setLSD2(m_netLDU1[202U]); - LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); + LogMessage(LOG_NET, "MMDVM " P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); if (m_debug) Utils::dump(1U, "MMDVM -> DVM LDU1", m_netLDU1, 9U * 25U); @@ -1190,7 +1195,7 @@ void HostPatch::writeNet_LDU2(bool toFNE) lsd.setLSD1(m_netLDU2[201U]); lsd.setLSD2(m_netLDU2[202U]); - LogMessage(LOG_NET, P25_LDU2_STR " audio"); + LogMessage(LOG_NET, "MMDVM " P25_LDU2_STR " audio"); if (m_debug) Utils::dump(1U, "MMDVM -> DVM LDU2", m_netLDU2, 9U * 25U); @@ -1420,7 +1425,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - LogMessage(LOG_HOST, P25_TDU_STR); + LogMessage(LOG_HOST, "MMDVM " P25_TDU_STR); uint8_t controlByte = 0x00U; patch->m_network->writeP25TDU(patch->m_netLC, lsd, controlByte); @@ -1429,7 +1434,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - patch->m_rxStartTime; - LogMessage(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", patch->m_netLC.getSrcId(), patch->m_netLC.getDstId(), diff / 1000U); + LogMessage(LOG_HOST, "MMDVM P25, call end, srcId = %u, dstId = %u, dur = %us", patch->m_netLC.getSrcId(), patch->m_netLC.getDstId(), diff / 1000U); } patch->m_rxStartTime = 0U; diff --git a/src/patch/mmdvm/P25Network.cpp b/src/patch/mmdvm/P25Network.cpp index 352f5cda2..897cc3a7d 100644 --- a/src/patch/mmdvm/P25Network.cpp +++ b/src/patch/mmdvm/P25Network.cpp @@ -424,6 +424,20 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons return true; } +/* Writes a TDU frame to the network. */ + +bool P25Network::writeTDU() +{ + if (m_debug) + Utils::dump(1U, "MMDVM Network END Sent", REC80, 17U); + + bool ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + return true; +} + /* Updates the timer by the passed number of milliseconds. */ void P25Network::clock(uint32_t ms) diff --git a/src/patch/mmdvm/P25Network.h b/src/patch/mmdvm/P25Network.h index 1b1855a4d..ca3a5bb2a 100644 --- a/src/patch/mmdvm/P25Network.h +++ b/src/patch/mmdvm/P25Network.h @@ -80,6 +80,11 @@ namespace mmdvm */ bool writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end); + /** + * @brief Writes a TDU frame to the network. + */ + bool writeTDU(); + /** * @brief Updates the timer by the passed number of milliseconds. * @param ms Number of milliseconds. From ec53aca47bb5385acfc5fb248b96a5387256b5d7 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 10 Jun 2025 15:16:29 -0400 Subject: [PATCH 027/133] correct some incorrect timing; --- src/patch/HostPatch.cpp | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index b0e6abef4..0c9bd5bfd 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -1317,12 +1317,20 @@ void* HostPatch::threadMMDVMProcess(void* arg) ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE + StopWatch stopWatch; + stopWatch.start(); + while (!g_killed) { if (!patch->m_running) { Thread::sleep(1U); continue; } + uint32_t ms = stopWatch.elapsed(); + + ms = stopWatch.elapsed(); + stopWatch.start(); + uint32_t length = 0U; bool netReadRet = false; if (patch->m_digiMode == TX_MODE_P25) { @@ -1335,35 +1343,27 @@ void* HostPatch::threadMMDVMProcess(void* arg) // LDU1 case DFSIFrameType::LDU1_VOICE1: ::memcpy(patch->m_netLDU1 + 0U, buffer, 22U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE2: ::memcpy(patch->m_netLDU1 + 25U, buffer, 14U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE3: ::memcpy(patch->m_netLDU1 + 50U, buffer, 17U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE4: ::memcpy(patch->m_netLDU1 + 75U, buffer, 17U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE5: ::memcpy(patch->m_netLDU1 + 100U, buffer, 17U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE6: ::memcpy(patch->m_netLDU1 + 125U, buffer, 17U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE7: ::memcpy(patch->m_netLDU1 + 150U, buffer, 17U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE8: ::memcpy(patch->m_netLDU1 + 175U, buffer, 17U); - patch->checkNet_LDU2(); break; case DFSIFrameType::LDU1_VOICE9: ::memcpy(patch->m_netLDU1 + 200U, buffer, 16U); @@ -1378,35 +1378,27 @@ void* HostPatch::threadMMDVMProcess(void* arg) // LDU2 case DFSIFrameType::LDU2_VOICE10: ::memcpy(patch->m_netLDU2 + 0U, buffer, 22U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE11: ::memcpy(patch->m_netLDU2 + 25U, buffer, 14U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE12: ::memcpy(patch->m_netLDU2 + 50U, buffer, 17U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE13: ::memcpy(patch->m_netLDU2 + 75U, buffer, 17U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE14: ::memcpy(patch->m_netLDU2 + 100U, buffer, 17U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE15: ::memcpy(patch->m_netLDU2 + 125U, buffer, 17U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE16: ::memcpy(patch->m_netLDU2 + 150U, buffer, 17U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE17: ::memcpy(patch->m_netLDU2 + 175U, buffer, 17U); - patch->checkNet_LDU1(); break; case DFSIFrameType::LDU2_VOICE18: ::memcpy(patch->m_netLDU2 + 200U, buffer, 16U); @@ -1458,7 +1450,8 @@ void* HostPatch::threadMMDVMProcess(void* arg) } } - Thread::sleep(1U); + if (ms < 5U) + Thread::sleep(5U); } LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); From a51617883d46f5223e58f476df108b28ebdb628a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 11:23:30 -0400 Subject: [PATCH 028/133] add support for properly authorizing a peer to send Inhibit/Uninhibit commands; add some NXDN constants for remote control; --- configs/peer_list.example.dat | 12 ++-- src/common/lookups/PeerListLookup.cpp | 31 ++++++-- src/common/lookups/PeerListLookup.h | 31 ++++---- src/common/nxdn/NXDNDefines.h | 13 ++++ src/fne/network/RESTAPI.cpp | 5 +- src/fne/network/callhandler/TagDMRData.cpp | 71 ++++++++++++++++++- src/fne/network/callhandler/TagDMRData.h | 3 +- src/fne/network/callhandler/TagP25Data.cpp | 20 ++++++ .../callhandler/packetdata/DMRPacketData.cpp | 2 +- src/peered/PeerEditWnd.h | 23 +++++- 10 files changed, 175 insertions(+), 36 deletions(-) diff --git a/configs/peer_list.example.dat b/configs/peer_list.example.dat index bfed0ea97..60cf3299b 100644 --- a/configs/peer_list.example.dat +++ b/configs/peer_list.example.dat @@ -1,9 +1,9 @@ # # This file sets the valid peer IDs allowed on a FNE. # -# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled)," -#1234,,0,,1, -#5678,MYSECUREPASSWORD,0,,0, -#9876,MYSECUREPASSWORD,1,,0, -#5432,MYSECUREPASSWORD,,Peer Alias 1,0, -#1012,MYSECUREPASSWORD,1,Peer Alias 2,1, +# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled)" +#1234,,0,,1,0, +#5678,MYSECUREPASSWORD,0,,0,0, +#9876,MYSECUREPASSWORD,1,,0,0, +#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0, +#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0, diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index d96d291c0..4eb39df47 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -69,17 +69,15 @@ void PeerListLookup::clear() /* Adds a new entry to the list. */ -void PeerListLookup::addEntry(uint32_t id, const std::string& alias, const std::string& password, bool peerLink, bool canRequestKeys) +void PeerListLookup::addEntry(uint32_t id, PeerId entry) { - PeerId entry = PeerId(id, alias, password, peerLink, canRequestKeys, false); - __LOCK_TABLE(); try { PeerId _entry = m_table.at(id); // if either the alias or the enabled flag doesn't match, update the entry if (_entry.peerId() == id) { - _entry = PeerId(id, alias, password, peerLink, canRequestKeys, false); + _entry = entry; m_table[id] = _entry; } } catch (...) { @@ -117,7 +115,7 @@ PeerId PeerListLookup::find(uint32_t id) try { entry = m_table.at(id); } catch (...) { - entry = PeerId(0U, "", "", false, false, true); + entry = PeerId(0U, "", "", true); } return entry; @@ -243,20 +241,31 @@ bool PeerListLookup::load() if (parsed.size() >= 5) canRequestKeys = ::atoi(parsed[4].c_str()) == 1; + // parse can issue inhibit flag + bool canIssueInhibit = false; + if (parsed.size() >= 6) + canIssueInhibit = ::atoi(parsed[5].c_str()) == 1; + // parse optional password std::string password = ""; if (parsed.size() >= 2) password = parsed[1].c_str(); // load into table - m_table[id] = PeerId(id, alias, password, peerLink, canRequestKeys, false); + PeerId entry = PeerId(id, alias, password, false); + entry.peerLink(peerLink); + entry.canRequestKeys(canRequestKeys); + entry.canIssueInhibit(canIssueInhibit); + + m_table[id] = entry; // log depending on what was loaded LogMessage(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s", id, (!alias.empty() ? (" (" + alias + ")").c_str() : ""), (!password.empty() ? "using unique peer password" : "using master password"), (peerLink) ? ", Peer-Link Enabled" : "", - (canRequestKeys) ? ", Can Request Keys" : ""); + (canRequestKeys) ? ", Can Request Keys" : "", + (canIssueInhibit) ? ", Can Issue Inhibit" : ""); } } @@ -334,6 +343,14 @@ bool PeerListLookup::save() line += "0,"; } + // add canIssueInhibit flag + bool canIssueInhibit = entry.second.canIssueInhibit(); + if (canIssueInhibit) { + line += "1,"; + } else { + line += "0,"; + } + line += "\n"; file << line; lines++; diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index d4ae371ec..5b7dd15b8 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -51,6 +51,7 @@ namespace lookups m_peerPassword(), m_peerLink(false), m_canRequestKeys(false), + m_canIssueInhibit(false), m_peerDefault(false) { /* stub */ @@ -61,16 +62,15 @@ namespace lookups * @param peerAlias Peer alias * @param peerPassword Per Peer Password. * @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration. - * @param peerLink lag indicating if the peer participates in peer link and should be sent configuration. - * @param canRequestKeys Flag indicating if the peer can request encryption keys. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. */ - PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool canRequestKeys, bool peerDefault) : + PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerDefault) : m_peerId(peerId), m_peerAlias(peerAlias), m_peerPassword(peerPassword), - m_peerLink(peerLink), - m_canRequestKeys(canRequestKeys), + m_peerLink(false), + m_canRequestKeys(false), + m_canIssueInhibit(false), m_peerDefault(peerDefault) { /* stub */ @@ -88,6 +88,7 @@ namespace lookups m_peerPassword = data.m_peerPassword; m_peerLink = data.m_peerLink; m_canRequestKeys = data.m_canRequestKeys; + m_canIssueInhibit = data.m_canIssueInhibit; m_peerDefault = data.m_peerDefault; } @@ -100,17 +101,13 @@ namespace lookups * @param peerAlias Peer Alias * @param peerPassword Per Peer Password. * @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration. - * @param peerLink lag indicating if the peer participates in peer link and should be sent configuration. - * @param canRequestKeys Flag indicating if the peer can request encryption keys. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. */ - void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool canRequestKeys, bool peerDefault) + void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerDefault) { m_peerId = peerId; m_peerAlias = peerAlias; m_peerPassword = peerPassword; - m_peerLink = peerLink; - m_canRequestKeys = canRequestKeys; m_peerDefault = peerDefault; } @@ -135,6 +132,10 @@ namespace lookups * @brief Flag indicating if the peer can request encryption keys. */ DECLARE_PROPERTY_PLAIN(bool, canRequestKeys); + /** + * @brief Flag indicating if the peer can issue inhibit/uninhibit packets. + */ + DECLARE_PROPERTY_PLAIN(bool, canIssueInhibit); /** * @brief Flag indicating if the peer is default. */ @@ -167,15 +168,13 @@ namespace lookups /** * @brief Adds a new entry to the list. - * @param peerId Unique peer ID to add. - * @param password Per Peer Password. - * @param peerLink Flag indicating this peer will participate in peer link and should be sent configuration. - * @param canRequestKeys Flag indicating if the peer can request encryption keys. + * @param id Unique peer ID to add. + * @param entry Peer ID entry to add. */ - void addEntry(uint32_t id, const std::string& alias = "", const std::string& password = "", bool peerLink = false, bool canRequestKeys = false); + void addEntry(uint32_t id, PeerId entry); /** * @brief Removes an existing entry from the list. - * @param peerId Unique peer ID to remove. + * @param id Unique peer ID to remove. */ void eraseEntry(uint32_t id); /** diff --git a/src/common/nxdn/NXDNDefines.h b/src/common/nxdn/NXDNDefines.h index 077c28e5d..a9e4c3632 100644 --- a/src/common/nxdn/NXDNDefines.h +++ b/src/common/nxdn/NXDNDefines.h @@ -284,6 +284,17 @@ namespace nxdn }; } + /** @brief Remote Control Commands */ + namespace RemoteControlCommand { + /** @brief Remote Control Commands */ + enum : uint8_t { + STUN = 0x00U, //! Stun + REVIVE = 0x01U, //! Revive + KILL = 0x02U, //! Kill + REMOTE_MONITOR = 0x04U //! Remote Monitor + }; + } + /** @brief Channel Access - Step */ namespace ChAccessStep { /** @brief Channel Access - Step */ @@ -340,6 +351,8 @@ namespace nxdn SRV_INFO = 0x19U, //! SRV_INFO - Service Information CCH_INFO = 0x1AU, //! CCH_INFO - Control Channel Information ADJ_SITE_INFO = 0x1BU, //! ADJ_SITE_INFO - Adjacent Site Information + REM_CON_REQ = 0x34U, //! REM_CON_REQ - Remote Control Request + REM_CON_RESP = 0x35U, //! REM_CON_RESP - Remote Control Response // Traffic Channel Message Types RTCH_VCALL = 0x01U, //! VCALL - Voice Call diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 85a44615f..4e52b9151 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -1295,7 +1295,10 @@ void RESTAPI::restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply, peerPassword = req["peerPassword"].get(); } - m_peerListLookup->addEntry(peerId, peerAlias, peerPassword, peerLink); + PeerId entry = PeerId(peerId, peerAlias, peerPassword, false); + entry.peerLink(peerLink); + + m_peerListLookup->addEntry(peerId, entry); } /* REST API endpoint; implements put peer delete request. */ diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 5898c9044..330d8686f 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -120,12 +120,18 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint8_t frame[DMR_FRAME_LENGTH_BYTES]; dmrData.getData(frame); + // process a CSBK out into a class literal if possible + std::unique_ptr csbk; + if (dataType == DataType::CSBK) { + csbk = lc::csbk::CSBKFactory::createCSBK(frame, dataType); + } + // perform TGID route rewrites if configured routeRewrite(buffer, peerId, dmrData, dataType, dstId, slotNo, false); dstId = GET_UINT24(buffer, 8U); // is the stream valid? - if (validate(peerId, dmrData, streamId)) { + if (validate(peerId, dmrData, csbk.get(), streamId)) { // is this peer ignored? if (!isPeerPermitted(peerId, dmrData, streamId, external)) { return false; @@ -766,7 +772,7 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t /* Helper to validate the DMR call stream. */ -bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamId) +bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, uint32_t streamId) { // is the source ID a blacklisted ID? bool rejectUnknownBadCall = false; @@ -804,6 +810,67 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI if (data.getDataType() == DataType::TERMINATOR_WITH_LC) return true; + // always validate a CSBK if the source is valid + if (data.getDataType() == DataType::CSBK) { + if (rejectUnknownBadCall) + return false; + + if (csbk != nullptr) { + // handle standard DMR reference opcodes + switch (csbk->getCSBKO()) { + case CSBKO::PV_GRANT: + { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(data.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + } + break; + case CSBKO::TV_GRANT: + { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(csbk->getDstId()); + + // check TGID validity + if (tg.isInvalid()) { + return false; + } + + if (!tg.config().active()) { + return false; + } + } + break; + case CSBKO::EXT_FNCT: + { + if (csbk->getFID() == FID_MOT) { + const lc::csbk::CSBK_EXT_FNCT* iosp = static_cast(csbk); + if (iosp != nullptr) { + lookups::PeerId pid = m_network->m_peerListLookup->find(peerId); + uint32_t func = iosp->getExtendedFunction(); + switch (func) { + case ExtendedFunctions::INHIBIT: + case ExtendedFunctions::UNINHIBIT: + { + if (!pid.peerDefault() && !pid.canIssueInhibit()) { + LogWarning(LOG_NET, "DMR, PEER %u attempted inhibit/unhibit, not authorized", peerId); + return false; + } + } + break; + } + } + } + } + break; + } + } + + return true; + } + // is this a private call? if (data.getFLCO() == FLCO::PRIVATE) { // is the destination ID a blacklisted ID? diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index 44270cff8..d827877bb 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -247,10 +247,11 @@ namespace network * @brief Helper to validate the DMR call stream. * @param peerId Peer ID. * @param dmrData Instance of data::NetData DMR data container class. + * @param[in] csbk Instance of dmr::lc::CSBK. * @param streamId Stream ID. * @returns bool True, if valid, otherwise false. */ - bool validate(uint32_t peerId, dmr::data::NetData& data, uint32_t streamId); + bool validate(uint32_t peerId, dmr::data::NetData& data, dmr::lc::CSBK* csbk, uint32_t streamId); /** * @brief Helper to write a grant packet. diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 208c2866d..7085bfa63 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1256,6 +1256,26 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } } break; + case TSBKO::IOSP_EXT_FNCT: + { + const lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast(tsbk); + if (iosp != nullptr) { + lookups::PeerId pid = m_network->m_peerListLookup->find(peerId); + uint32_t func = iosp->getExtendedFunction(); + switch (func) { + case ExtendedFunctions::INHIBIT: + case ExtendedFunctions::UNINHIBIT: + { + if (!pid.peerDefault() && !pid.canIssueInhibit()) { + LogWarning(LOG_NET, "P25, PEER %u attempted inhibit/unhibit, not authorized", peerId); + return false; + } + } + break; + } + } + } + break; } // handle validating DVM call termination packets diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp index 969f508c9..dafab7915 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp @@ -92,7 +92,7 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee dmrData.getData(frame); // is the stream valid? - if (m_tag->validate(peerId, dmrData, streamId)) { + if (m_tag->validate(peerId, dmrData, nullptr, streamId)) { // is this peer ignored? if (!m_tag->isPeerPermitted(peerId, dmrData, streamId)) { return false; diff --git a/src/peered/PeerEditWnd.h b/src/peered/PeerEditWnd.h index bfac5039e..601c55a2d 100644 --- a/src/peered/PeerEditWnd.h +++ b/src/peered/PeerEditWnd.h @@ -110,6 +110,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { FButtonGroup m_configGroup{"Configuration", this}; FCheckBox m_peerLinkEnabled{"Peer Link", &m_configGroup}; FCheckBox m_canReqKeysEnabled{"Request Keys", &m_configGroup}; + FCheckBox m_canInhibitEnabled{"Issue Inhibit", &m_configGroup}; /** * @brief Initializes the window layout. @@ -233,6 +234,12 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { m_canReqKeysEnabled.addCallback("toggled", [&]() { m_rule.canRequestKeys(m_canReqKeysEnabled.isChecked()); }); + + m_canInhibitEnabled.setGeometry(FPoint(2, 3), FSize(10, 1)); + m_canInhibitEnabled.setChecked(m_rule.canIssueInhibit()); + m_canInhibitEnabled.addCallback("toggled", [&]() { + m_rule.canIssueInhibit(m_canInhibitEnabled.isChecked()); + }); } CloseWndBase::initControls(); @@ -314,7 +321,13 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { if (it != peers.end()) { LogMessage(LOG_HOST, "Updating peer %s (%u) to %s (%u)", it->peerAlias().c_str(), it->peerId(), m_rule.peerAlias().c_str(), m_rule.peerId()); g_pidLookups->eraseEntry(m_origPeerId); - g_pidLookups->addEntry(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), m_rule.peerLink(), m_rule.canRequestKeys()); + + lookups::PeerId entry = lookups::PeerId(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), false); + entry.peerLink(m_rule.peerLink()); + entry.canRequestKeys(m_rule.canRequestKeys()); + entry.canIssueInhibit(m_rule.canIssueInhibit()); + + g_pidLookups->addEntry(m_rule.peerId(), entry); logRuleInfo(); } @@ -345,7 +358,13 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { } else { LogMessage(LOG_HOST, "Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId()); } - g_pidLookups->addEntry(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), m_rule.peerLink(), m_rule.canRequestKeys()); + + lookups::PeerId entry = lookups::PeerId(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), false); + entry.peerLink(m_rule.peerLink()); + entry.canRequestKeys(m_rule.canRequestKeys()); + entry.canIssueInhibit(m_rule.canIssueInhibit()); + + g_pidLookups->addEntry(m_rule.peerId(), entry); logRuleInfo(); From ac0646e73f1077d59c1c7d90d2b2b4550a7b6683 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 11:27:16 -0400 Subject: [PATCH 029/133] add columns to main peered peer list to represent if a peer is allowed to inhibit; --- src/peered/PeerListWnd.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/peered/PeerListWnd.h b/src/peered/PeerListWnd.h index 609fce203..7fa6fd14f 100644 --- a/src/peered/PeerListWnd.h +++ b/src/peered/PeerListWnd.h @@ -119,11 +119,12 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { bool masterPassword = (entry.peerPassword().size() == 0U); // build list view entry - const std::array columns = { + const std::array columns = { oss.str(), (masterPassword) ? "X" : "", (entry.peerLink()) ? "X" : "", (entry.canRequestKeys()) ? "X" : "", + (entry.canIssueInhibit()) ? "X" : "", entry.peerAlias() }; @@ -210,14 +211,16 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { m_listView.addColumn("Peer ID", 10); m_listView.addColumn("Master Password", 16); m_listView.addColumn("Peer Link", 12); - m_listView.addColumn("Can Request Keys", 12); + m_listView.addColumn("Request Keys", 12); + m_listView.addColumn("Can Inhibit", 12); m_listView.addColumn("Alias", 40); // set right alignment for peer ID m_listView.setColumnAlignment(2, finalcut::Align::Center); m_listView.setColumnAlignment(3, finalcut::Align::Center); m_listView.setColumnAlignment(4, finalcut::Align::Center); - m_listView.setColumnAlignment(5, finalcut::Align::Left); + m_listView.setColumnAlignment(5, finalcut::Align::Center); + m_listView.setColumnAlignment(6, finalcut::Align::Left); // set type of sorting m_listView.setColumnSortType(1, finalcut::SortType::Name); From 109697a4f569725e34538f3a740b9612c69c52e1 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 15:55:12 -0400 Subject: [PATCH 030/133] fixup informational logging; --- src/peered/PeerEditWnd.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/peered/PeerEditWnd.h b/src/peered/PeerEditWnd.h index 601c55a2d..69c469c92 100644 --- a/src/peered/PeerEditWnd.h +++ b/src/peered/PeerEditWnd.h @@ -254,8 +254,9 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { uint32_t peerId = m_rule.peerId(); bool peerLink = m_rule.peerLink(); bool canRequestKeys = m_rule.canRequestKeys(); + bool canIssueInhibit = m_rule.canIssueInhibit(); - ::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u PEER LINK: %u CAN REQUEST KEYS: %u", peerAlias.c_str(), peerId, peerLink, canRequestKeys); + ::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u PEER LINK: %u CAN REQUEST KEYS: %u CAN ISSUE INHIBIT: %u", peerAlias.c_str(), peerId, peerLink, canRequestKeys, canIssueInhibit); } /* From 65ebed3614539a73f3c8214bbf6417e853fd044b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 17:05:34 -0400 Subject: [PATCH 031/133] add auto generation of a peer password; slightly increase the dialog size of the peer edit window; --- src/peered/PeerEditWnd.h | 58 ++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/peered/PeerEditWnd.h b/src/peered/PeerEditWnd.h index 69c469c92..1dabf2873 100644 --- a/src/peered/PeerEditWnd.h +++ b/src/peered/PeerEditWnd.h @@ -22,6 +22,41 @@ #include using namespace finalcut; +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/* Helper to generate a mostly random password. */ + +std::string randPasswordGen(int len) +{ + srand((unsigned int)(time(NULL))); + + const char numbers[] = "0123456789"; + const char letter[] = "abcdefghijklmnoqprstuvwyzx"; + const char LETTER[] = "ABCDEFGHIJKLMNOQPRSTUYWVZX"; + + std::string password; + int randomizer = rand() % 6; + + for (int i = 0; i < len; i++) { + if (randomizer == 1 || randomizer == 4) { + password.append(1U, numbers[rand() % 10]); + randomizer = rand() % 4; + } + else if (randomizer == 2 || randomizer == 5) { + password.append(1U, LETTER[rand() % 26]); + randomizer = rand() % 4; + } + else { + password.append(1U, letter[rand() % 26]); + randomizer = rand() % 4; + } + } + + return password; +} + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -80,12 +115,19 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { */ explicit PeerEditWnd(lookups::PeerId rule, FWidget* widget = nullptr) : CloseWndBase{widget} { + m_new = false; m_rule = rule; - if (m_rule.peerDefault()) { + + if (m_rule.peerDefault() || (m_rule.peerId() == 0U)) { m_new = true; } else { m_origPeerId = m_rule.peerId(); } + + if (m_new) { + std::string password = randPasswordGen(20); + m_rule.peerPassword(password); + } } private: @@ -118,7 +160,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { void initLayout() override { FDialog::setText("Peer ID"); - FDialog::setSize(FSize{60, 18}); + FDialog::setSize(FSize{65, 18}); m_enableSetButton = false; CloseWndBase::initLayout(); @@ -139,7 +181,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { m_peerAlias.setShadow(false); m_peerAlias.addCallback("changed", [&]() { m_rule.peerAlias(m_peerAlias.getText().toString()); }); - m_saveCopy.setGeometry(FPoint(36, 2), FSize(18, 1)); + m_saveCopy.setGeometry(FPoint(41, 2), FSize(18, 1)); m_saveCopy.addCallback("toggled", [&]() { if (m_saveCopy.isChecked()) { m_incOnSave.setEnable(); @@ -150,14 +192,14 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { redraw(); }); - m_incOnSave.setGeometry(FPoint(36, 3), FSize(18, 1)); + m_incOnSave.setGeometry(FPoint(41, 3), FSize(18, 1)); m_incOnSave.setDisable(); // talkgroup source { - m_sourceGroup.setGeometry(FPoint(2, 5), FSize(30, 5)); + m_sourceGroup.setGeometry(FPoint(2, 5), FSize(35, 5)); m_peerIdLabel.setGeometry(FPoint(2, 1), FSize(10, 1)); - m_peerId.setGeometry(FPoint(11, 1), FSize(17, 1)); + m_peerId.setGeometry(FPoint(11, 1), FSize(22, 1)); m_peerId.setAlignment(finalcut::Align::Right); if (!m_rule.peerDefault()) { m_peerId.setText(std::to_string(m_rule.peerId())); @@ -211,7 +253,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { }); m_peerPasswordLabel.setGeometry(FPoint(2, 2), FSize(10, 1)); - m_peerPassword.setGeometry(FPoint(11, 2), FSize(17, 1)); + m_peerPassword.setGeometry(FPoint(11, 2), FSize(22, 1)); if (!m_rule.peerDefault()) { m_peerPassword.setText(m_rule.peerPassword()); } @@ -221,7 +263,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { // configuration { - m_configGroup.setGeometry(FPoint(34, 5), FSize(23, 5)); + m_configGroup.setGeometry(FPoint(39, 5), FSize(23, 5)); m_peerLinkEnabled.setGeometry(FPoint(2, 1), FSize(10, 1)); m_peerLinkEnabled.setChecked(m_rule.peerLink()); From cfc7bb5e1bfa7eb2069b0767e5834f84e6e8357f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 21:45:37 -0400 Subject: [PATCH 032/133] fix TIA VHDR incorrect length; correct gatekeep accidentally setting TIA StartOfStream to 4 bytes instead of 3, because you know the TIA spec is incomprehensible with its bit alignment; --- src/common/p25/dfsi/DFSIDefines.h | 2 ++ src/common/p25/dfsi/frames/StartOfStream.h | 2 +- src/host/modem/ModemV24.cpp | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index bf78aff63..9fa6b7c1c 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -43,6 +43,8 @@ namespace p25 const uint32_t DFSI_VHDR_RAW_LEN = 36U; const uint32_t DFSI_VHDR_LEN = 27U; + const uint32_t DFSI_TIA_VHDR_LEN = 22U; + const uint32_t DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES = 22U; const uint32_t DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES = 14U; const uint32_t DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES = 17U; diff --git a/src/common/p25/dfsi/frames/StartOfStream.h b/src/common/p25/dfsi/frames/StartOfStream.h index 426803750..014b08c22 100644 --- a/src/common/p25/dfsi/frames/StartOfStream.h +++ b/src/common/p25/dfsi/frames/StartOfStream.h @@ -45,7 +45,7 @@ namespace p25 */ class HOST_SW_API StartOfStream { public: - static const uint8_t LENGTH = 4U; + static const uint8_t LENGTH = 3U; /** * @brief Initializes a copy instance of the StartOfStream class. diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index d19cfbb12..8d38b38c2 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1849,7 +1849,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // prepare VHDR1 buffer[3U] = DFSIFrameType::MOT_VHDR_1; ::memcpy(buffer + 4U, raw, 18U); - length += 19U; // 18 Golay + Block Type Marker + length += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker start.encode(buffer + length); length += StartOfStream::LENGTH; @@ -1878,7 +1878,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // prepare VHDR2 buffer[3U] = DFSIFrameType::MOT_VHDR_2; ::memcpy(buffer + 4U, raw + 18U, 18U); - length += 19U; // 18 Golay + Block Type Marker + length += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker start.encode(buffer + length); length += StartOfStream::LENGTH; From 68af6fbe7eeafc6ed7391b35910011ee2defc3af Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 22:30:16 -0400 Subject: [PATCH 033/133] add some permanent log trace for DFSI over UDP tracing; correct startOfStreamTIA incorrectly sending a LDU1 NID; correct some offsets; --- src/host/modem/ModemV24.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 8d38b38c2..5ce5bce58 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1184,7 +1184,10 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memset(m_rxCall->VHDR1, 0x00U, 18U); ::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U); - dataOffs += 19U; // 18 Golay + Block Type Marker + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA() VoiceHeader1", m_rxCall->VHDR1, 18U); + + dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker } break; case BlockType::VOICE_HEADER_P2: @@ -1193,7 +1196,10 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memset(m_rxCall->VHDR2, 0x00U, 18U); ::memcpy(m_rxCall->VHDR2, dfsiData + dataOffs + 1U, 18U); - dataOffs += 19U; // 18 Golay + Block Type Marker + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA() VoiceHeader1", m_rxCall->VHDR1, 18U); + + dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; @@ -1210,17 +1216,23 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) assert(vhdr != nullptr); - uint32_t offset = 6U; // skip the first 6 bits (extremely strange bit offset for TIA because of status bits) + uint32_t offset = 0U; for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { Utils::hex2Bin(raw[i], vhdr, offset); } + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA() VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); + // try to decode the RS data try { bool ret = m_rs.decode362017(vhdr); if (!ret) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); } else { + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA() VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); + // late entry? if (!m_rxCallInProgress) { m_rxCallInProgress = true; @@ -1792,7 +1804,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // generate start of stream StartOfStream start = StartOfStream(); - start.setNID(generateNID()); + start.setNID(generateNID(DUID::HDU)); start.encode(buffer + 2U); length += StartOfStream::LENGTH; @@ -1820,7 +1832,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // convert the binary bytes to hex bytes uint8_t raw[DFSI_VHDR_RAW_LEN]; - uint32_t offset = 6U; // skip the first 6 bits (extremely strange bit offset for TIA because of status bits) + uint32_t offset = 0U; for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { raw[i] = Utils::bin2Hex(vhdr, offset); } From faca19f53aceeee76d8f975280747fe0faed8841 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 23:03:08 -0400 Subject: [PATCH 034/133] make sure we send out heartbeats *before* the heartbeat time; --- src/host/Host.Config.cpp | 2 +- src/host/modem/port/specialized/V24UDPPort.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 91c87614c..49443172a 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -656,7 +656,7 @@ bool Host::createModem() if (useFSCForUDP) { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, g_remoteLocalPort, true, fscInitiator, debug); ((modem::port::specialized::V24UDPPort*)modemPort)->setHeartbeatInterval(fscHeartbeat); - } else { + } else { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, 0U, false, false, debug); } m_udpDFSIRemotePort = modemPort; diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 2c2dfa4d6..e06f04f22 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -518,11 +518,16 @@ void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) network->m_heartbeatInterval = connMessage->getHostHeartbeatPeriod(); if (network->m_heartbeatInterval > 30U) network->m_heartbeatInterval = 30U; + if (network->m_heartbeatInterval < 5U) + network->m_heartbeatInterval = 5U; + + // HACK: make sure the HB is always one second shorter then the requested value + network->m_heartbeatInterval--; uint16_t remoteCtrlPort = Socket::port(req->address); network->m_remoteCtrlAddr = req->address; network->m_remoteCtrlAddrLen = req->addrLen; - + LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u, hostHBInterval = %u", remoteCtrlPort, network->m_localPort, vcBasePort, connMessage->getHostHeartbeatPeriod()); // setup local RTP VC port (where we receive traffic) From 91afeef4aeece6f7b6ebbc5f512b3809c80ab5ce Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 11 Jun 2025 23:50:30 -0400 Subject: [PATCH 035/133] send Start of Stream with voice data; --- src/host/modem/ModemV24.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 5ce5bce58..44f898a1d 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -2548,7 +2548,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) // generate control octet ControlOctet ctrl = ControlOctet(); - ctrl.setBlockHeaderCnt(1U); + ctrl.setBlockHeaderCnt(2U); ctrl.encode(buffer); bufferSize += ControlOctet::LENGTH; @@ -2557,12 +2557,21 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) hdr.setBlockType(BlockType::FULL_RATE_VOICE); hdr.encode(buffer + 1U); bufferSize += BlockHeader::LENGTH; + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + bufferSize += BlockHeader::LENGTH; voice.setSuperframeCnt(m_superFrameCnt); voice.setBusy(1U); // Inbound Channel is Busy voice.encode(buffer + bufferSize); bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type + // generate start of stream + StartOfStream start = StartOfStream(); + start.setNID(generateNID(duid)); + start.encode(buffer + bufferSize); + bufferSize += StartOfStream::LENGTH; + if (buffer != nullptr) { if (m_trace) { Utils::dump("ModemV24::convertFromAirTIA() Encoded V.24 Voice Frame Data", buffer, bufferSize); From 23610accdaa3d83e25cf3acd867aeb69754c9dad Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 16 Jun 2025 13:05:09 -0400 Subject: [PATCH 036/133] free memory in error case; remove unused variables; --- src/fne/network/DiagNetwork.cpp | 1 + src/fne/network/FNENetwork.cpp | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 42834eed9..3dcaecf1c 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -435,6 +435,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) if (!v.is()) { LogError(LOG_NET, "PEER %u error parsing active peer list, data was not valid", peerId); pkt.buffer->clear(); + delete pkt.buffer; pkt.streamId = 0U; if (decompressed != nullptr) { delete[] decompressed; diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 6927ae3ab..ede136b41 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1975,8 +1975,6 @@ void FNENetwork::writeBlacklistRIDs(uint32_t peerId, uint32_t streamId) void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalPeer) { - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - if (!m_tidLookup->sendTalkgroups()) { return; } @@ -2160,8 +2158,6 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) { - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - // sending PEER_LINK style RID list to external peers FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { From 5a01c674633b8e205b84645ad3c38a3dadc8454a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 17 Jun 2025 21:22:13 -0400 Subject: [PATCH 037/133] correct some buffer allocations; ensure the AES class is deleted after use; --- src/fne/network/callhandler/TagP25Data.cpp | 42 ++++++++++------------ src/host/p25/packet/ControlSignaling.cpp | 3 +- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 7085bfa63..7ead5451f 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -155,11 +155,10 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // process a TSBK out into a class literal if possible std::unique_ptr tsbk; if (duid == DUID::TSDU) { - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + tsbk = lc::tsbk::TSBKFactory::createTSBK(data); } // is the stream valid? @@ -648,11 +647,10 @@ void TagP25Data::routeRewrite(uint8_t* buffer, uint32_t peerId, uint8_t duid, ui // are we receiving a TSDU? if (duid == DUID::TSDU) { - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { // handle standard P25 reference opcodes switch (tsbk->getLCO()) { @@ -729,11 +727,10 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) if (duid == DUID::TSDU) { uint32_t frameLength = P25_TSDU_FRAME_LENGTH_BYTES;//buffer[23U]; - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { // report tsbk event to InfluxDB if (m_network->m_enableInfluxDB && m_network->m_influxLogRawData) { @@ -814,11 +811,10 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) if (duid == DUID::TDULC) { uint32_t frameLength = buffer[23U]; - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get()); + std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data); if (tdulc != nullptr) { // handle standard P25 reference opcodes switch (tdulc->getLCO()) { @@ -846,11 +842,10 @@ bool TagP25Data::processTSDUTo(uint8_t* buffer, uint32_t peerId, uint8_t duid) if (duid == DUID::TSDU) { uint32_t frameLength = P25_TSDU_FRAME_LENGTH_BYTES;//buffer[23U]; - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { //uint32_t srcId = tsbk->getSrcId(); uint32_t dstId = tsbk->getDstId(); @@ -913,11 +908,10 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint if (duid == DUID::TSDU) { uint32_t frameLength = buffer[23U]; - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { // handle standard P25 reference opcodes switch (tsbk->getLCO()) { diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 699e314f0..5769adee3 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -649,7 +649,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetAuthRes(RES1); - // get challenge for our SU + // get challenge for our SU ulong64_t challenge = 0U; try { challenge = m_llaDemandTable.at(srcId); @@ -681,6 +681,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_siteData.sysId()); From 63c31ee46db853aa42d45fb9b23fca7071b47cdb Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 18 Jun 2025 15:56:06 -0400 Subject: [PATCH 038/133] implement support for legacy radio affiliation (like P25) for DMR and NXDN (this fixes an issue where conventional DMR and NXDN systems would be unable to transmit onto affiliation only TGs); correct bad CSBK decoding in the FNE (forgot to check for dataSync); --- configs/config.example.yml | 10 +++++++++- src/fne/network/callhandler/TagDMRData.cpp | 2 +- src/host/dmr/Control.cpp | 5 +++++ src/host/dmr/Slot.cpp | 1 + src/host/dmr/Slot.h | 6 ++++++ src/host/dmr/packet/Voice.cpp | 19 +++++++++++++++++++ src/host/nxdn/Control.cpp | 3 +++ src/host/nxdn/Control.h | 1 + src/host/nxdn/packet/Voice.cpp | 19 +++++++++++++++++++ 9 files changed, 64 insertions(+), 2 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index f2be833ba..0538343c1 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -155,6 +155,10 @@ protocols: ignoreAffiliationCheck: false # Flag indicating the host should send a network grant demand for conventional traffic. convNetGrantDemand: false + # Flag indicating the fallback legacy group registration for radios that do not support group affiliation. + # (Useful for alerting the FNE to affiliations to TGIDs for radios that do not properly support group + # affiliation.) + legacyGroupReg: false # Flag indicating whether or not received RF embedded LC data only should be transmitted. embeddedLCOnly: false # Flag indicating whether talker alias data should be dumped to the log. @@ -245,7 +249,7 @@ protocols: # Flag indicating that the host will attempt to automatically inhibit unauthorized RIDs (those not in the # RID ACL list). inhibitUnauthorized: false - # Flag indicating the fallback legacy group grant for radios that do not support group affilition to + # Flag indicating the fallback legacy group grant for radios that do not support group affiliation to # have group grants transmitted. (Useful for alerting the FNE to affiliations to TGIDs for radios that # do not properly support group affiliation.) legacyGroupGrnt: true @@ -319,6 +323,10 @@ protocols: # Flag indicating whether or not a TGID will be tested for affiliations before being granted. ignoreAffiliationCheck: false + # Flag indicating the fallback legacy group registration for radios that do not support group affiliation. + # (Useful for alerting the FNE to affiliations to TGIDs for radios that do not properly support group + # affiliation.) + legacyGroupReg: false # Flag indicating the host should verify group affiliation. verifyAff: false # Flag indicating the host should verify unit registration. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 330d8686f..935ff97d8 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -122,7 +122,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // process a CSBK out into a class literal if possible std::unique_ptr csbk; - if (dataType == DataType::CSBK) { + if (dataSync && (dataType == DataType::CSBK)) { csbk = lc::csbk::CSBKFactory::createCSBK(frame, dataType); } diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index e3bf7e8d2..7d7b8af36 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -186,6 +186,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_slot1->m_ignoreAffiliationCheck = ignoreAffiliationCheck; m_slot2->m_ignoreAffiliationCheck = ignoreAffiliationCheck; + bool legacyGroupReg = dmrProtocol["legacyGroupReg"].as(false); + m_slot1->setLegacyGroupReg(legacyGroupReg); + m_slot2->setLegacyGroupReg(legacyGroupReg); + // set the In-Call Control function callback if (m_network != nullptr) { m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo) { processInCallCtrl(command, dstId, slotNo); }); @@ -219,6 +223,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa } LogInfo(" Ignore Affiliation Check: %s", ignoreAffiliationCheck ? "yes" : "no"); + LogInfo(" Legacy Group Registration: %s", legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", notifyCC ? "yes" : "no"); LogInfo(" Silence Threshold: %u (%.1f%%)", silenceThreshold, float(silenceThreshold) / 1.41F); LogInfo(" Frame Loss Threshold: %u", frameLossThreshold); diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 44730d50e..44e1a4853 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -154,6 +154,7 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_ignoreAffiliationCheck(false), m_disableNetworkGrant(false), m_convNetGrantDemand(false), + m_legacyGroupReg(false), m_tsccPayloadDstId(0U), m_tsccPayloadSrcId(0U), m_tsccPayloadGroup(false), diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index c8c6e3f4e..3c6fb3d7c 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -256,6 +256,11 @@ namespace dmr * @param notifyCC Flag indicating whether the voice channels will notify the TSCC of traffic channel changes. */ void setNotifyCC(bool notifyCC) { m_notifyCC = notifyCC; } + /** + * @brief Sets a flag indicating whether the legacy group registration is enabled. + * @param legacyGroupReg Flag indicating whether the legacy group registration is enabled. + */ + void setLegacyGroupReg(bool legacyGroupReg) { m_legacyGroupReg = legacyGroupReg; } /** * @brief Sets a flag indicating whether the control message debug is enabled. @@ -411,6 +416,7 @@ namespace dmr bool m_ignoreAffiliationCheck; bool m_disableNetworkGrant; bool m_convNetGrantDemand; + bool m_legacyGroupReg; uint32_t m_tsccPayloadDstId; uint32_t m_tsccPayloadSrcId; diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index fd5896fdf..69f2eab5b 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -145,6 +145,25 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfState = RS_RF_REJECTED; return false; } + + // are we auto-registering legacy radios to groups? + if (m_slot->m_legacyGroupReg) { + if (!m_slot->m_affiliations->isGroupAff(srcId, dstId)) { + // update dynamic unit registration table + if (!m_slot->m_affiliations->isUnitReg(srcId)) { + m_slot->m_affiliations->unitReg(srcId); + } + + if (m_slot->m_network != nullptr) + m_slot->m_network->announceUnitRegistration(srcId); + + // update dynamic affiliation table + m_slot->m_affiliations->groupAff(srcId, dstId); + + if (m_slot->m_network != nullptr) + m_slot->m_network->announceGroupAffiliation(srcId, dstId); + } + } } m_slot->m_data->m_lastRejectId = 0U; diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index a8a0da208..9a94207b3 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -71,6 +71,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_enableControl(false), m_dedicatedControl(false), m_ignoreAffiliationCheck(false), + m_legacyGroupReg(false), m_rfLastLICH(), m_rfLC(), m_netLC(), @@ -217,6 +218,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_ccDebug = control["debug"].as(false); m_ignoreAffiliationCheck = nxdnProtocol["ignoreAffiliationCheck"].as(false); + m_legacyGroupReg = nxdnProtocol["legacyGroupReg"].as(false); yaml::Node rfssConfig = systemConf["config"]; yaml::Node controlCh = rfssConfig["controlCh"]; @@ -327,6 +329,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw } LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); + LogInfo(" Legacy Group Registration: %s", m_legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", m_notifyCC ? "yes" : "no"); LogInfo(" Verify Affiliation: %s", m_control->m_verifyAff ? "yes" : "no"); LogInfo(" Verify Registration: %s", m_control->m_verifyReg ? "yes" : "no"); diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index 883ed2c09..4dd8cf815 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -278,6 +278,7 @@ namespace nxdn bool m_enableControl; bool m_dedicatedControl; bool m_ignoreAffiliationCheck; + bool m_legacyGroupReg; channel::LICH m_rfLastLICH; lc::RTCH m_rfLC; diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 7ae868249..834d5a7d9 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -262,6 +262,25 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u return false; } + // are we auto-registering legacy radios to groups? + if (m_nxdn->m_legacyGroupReg && group) { + if (!m_nxdn->m_affiliations->isGroupAff(srcId, dstId)) { + // update dynamic unit registration table + if (!m_nxdn->m_affiliations->isUnitReg(srcId)) { + m_nxdn->m_affiliations->unitReg(srcId); + } + + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->announceUnitRegistration(srcId); + + // update dynamic affiliation table + m_nxdn->m_affiliations->groupAff(srcId, dstId); + + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->announceGroupAffiliation(srcId, dstId); + } + } + m_nxdn->m_rfTGHang.start(); m_nxdn->m_netTGHang.stop(); m_nxdn->m_rfLastDstId = lc.getDstId(); From 96ac995c6d47740b005a65793866e67923987c6d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 19 Jun 2025 14:34:45 -0400 Subject: [PATCH 039/133] code cleanup to correct compiler warnings; --- src/bridge/HostBridge.cpp | 7 ++++--- src/common/network/BaseNetwork.cpp | 2 -- src/host/setup/HostSetup.cpp | 3 +-- src/patch/HostPatch.cpp | 5 +---- src/sysview/HostWS.cpp | 2 +- src/sysview/SysViewMain.cpp | 4 ++-- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index a6607420f..7bc5dd46e 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -139,7 +139,7 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit ::LogMessage(LOG_HOST, "Local Traffic, MDC Detect, unitId = $%04X", unitID); // HACK: nasty bullshit to convert MDC unitID to decimal - char* pCharRes = new (char); + char* pCharRes = new char[16]; // enough space for "0xFFFFFFFF" ::sprintf(pCharRes, "0x%X", unitID); uint32_t res = 0U; @@ -151,6 +151,7 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit res = (uint32_t)std::stoi(pCharRes, 0, 16); } + delete[] pCharRes; bridge->m_srcIdOverride = res; ::LogMessage(LOG_HOST, "Local Traffic, MDC Detect, converted srcId = %u", bridge->m_srcIdOverride); } @@ -3025,7 +3026,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) if (req != nullptr) { if (bridge->m_udpInterFrameDelay > 0U) { - int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (req->pktRxTime > now) { Thread::sleep(1U); @@ -3083,7 +3084,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) } } else { - for (uint32_t pcmIdx = 0; pcmIdx < req->pcmLength; pcmIdx += 2) { + for (int pcmIdx = 0; pcmIdx < req->pcmLength; pcmIdx += 2) { samples[smpIdx] = (short)((req->pcm[pcmIdx + 1] << 8) + req->pcm[pcmIdx + 0]); smpIdx++; } diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index babde1edd..660021a22 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -562,9 +562,7 @@ bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSp if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - bool resetSeq = false; if (m_p25StreamId == 0U) { - resetSeq = true; m_p25StreamId = createStreamId(); } diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 60b77799c..3a02939f5 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -1297,8 +1297,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; ::memset(rfPDU, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); - uint32_t rfPDUBits = 0U; - rfPDUBits = Utils::getBits(pduBuffer, rfPDU, 0U, bits); + Utils::getBits(pduBuffer, rfPDU, 0U, bits); ::memset(pduBuffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); Utils::getBitRange(rfPDU, pduBuffer, P25_PREAMBLE_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS); diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 0c9bd5bfd..081c2200f 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -535,7 +535,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) UInt8Array data = std::unique_ptr(new uint8_t[DMR_FRAME_LENGTH_BYTES]); ::memset(data.get(), 0x00U, DMR_FRAME_LENGTH_BYTES); DataType::E dataType = DataType::VOICE_SYNC; - uint8_t n = 0U; + if (dataSync) { dataType = (DataType::E)(buffer[15U] & 0x0FU); ::memcpy(data.get(), buffer + 20U, DMR_FRAME_LENGTH_BYTES); @@ -544,7 +544,6 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) ::memcpy(data.get(), buffer + 20U, DMR_FRAME_LENGTH_BYTES); } else { - n = buffer[15U] & 0x0FU; dataType = DataType::VOICE; ::memcpy(data.get(), buffer + 20U, DMR_FRAME_LENGTH_BYTES); } @@ -1331,8 +1330,6 @@ void* HostPatch::threadMMDVMProcess(void* arg) ms = stopWatch.elapsed(); stopWatch.start(); - uint32_t length = 0U; - bool netReadRet = false; if (patch->m_digiMode == TX_MODE_P25) { std::lock_guard lock(HostPatch::m_networkMutex); diff --git a/src/sysview/HostWS.cpp b/src/sysview/HostWS.cpp index d8ea75911..7efcaeeed 100644 --- a/src/sysview/HostWS.cpp +++ b/src/sysview/HostWS.cpp @@ -452,7 +452,7 @@ void HostWS::send(json::object obj) m_wsServer.send(*it, json, websocketpp::frame::opcode::text); } } - catch (websocketpp::exception) { /* stub */ } + catch (websocketpp::exception const&) { /* stub */ } } // --------------------------------------------------------------------------- diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index d7923c180..ecee7b562 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -500,8 +500,8 @@ void* threadNetworkPump(void* arg) uint32_t srcId = GET_UINT24(p25Buffer, 5U); uint32_t dstId = GET_UINT24(p25Buffer, 8U); - uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0); - uint32_t netId = GET_UINT24(p25Buffer, 16U); + //uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0); + //uint32_t netId = GET_UINT24(p25Buffer, 16U); // log call status if (duid != P25DEF::DUID::TSDU && duid != P25DEF::DUID::PDU) { From 8e66766daf50f6bd24d28b6ac8b2a1d61a488b7a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 19 Jun 2025 16:06:02 -0400 Subject: [PATCH 040/133] add string file meta data info to Win32 EXEs; add icons to Win32 EXEs; --- src/CMakeLists.txt | 9 +++++++++ src/bridge/CMakeLists.txt | 10 +++++++++- src/bridge/win32/project.ico | Bin 0 -> 25277 bytes src/bridge/win32/resource.h | 12 ++++++++++++ src/bridge/win32/resource.rc | Bin 0 -> 5140 bytes src/fne/CMakeLists.txt | 10 +++++++++- src/fne/win32/fne.ico | Bin 0 -> 21413 bytes src/fne/win32/resource.h | 12 ++++++++++++ src/fne/win32/resource.rc | Bin 0 -> 5194 bytes src/host/CMakeLists.txt | 10 +++++++++- src/host/win32/host.ico | Bin 0 -> 20989 bytes src/host/win32/resource.h | 12 ++++++++++++ src/host/win32/resource.rc | Bin 0 -> 5140 bytes src/patch/CMakeLists.txt | 8 ++++++++ src/patch/win32/project.ico | Bin 0 -> 25277 bytes src/patch/win32/resource.h | 12 ++++++++++++ src/patch/win32/resource.rc | Bin 0 -> 5142 bytes src/remote/CMakeLists.txt | 9 ++++++++- src/remote/win32/project.ico | Bin 0 -> 25277 bytes src/remote/win32/resource.h | 12 ++++++++++++ src/remote/win32/resource.rc | Bin 0 -> 5200 bytes 21 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 src/bridge/win32/project.ico create mode 100644 src/bridge/win32/resource.h create mode 100644 src/bridge/win32/resource.rc create mode 100644 src/fne/win32/fne.ico create mode 100644 src/fne/win32/resource.h create mode 100644 src/fne/win32/resource.rc create mode 100644 src/host/win32/host.ico create mode 100644 src/host/win32/resource.h create mode 100644 src/host/win32/resource.rc create mode 100644 src/patch/win32/project.ico create mode 100644 src/patch/win32/resource.h create mode 100644 src/patch/win32/resource.rc create mode 100644 src/remote/win32/project.ico create mode 100644 src/remote/win32/resource.h create mode 100644 src/remote/win32/resource.rc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b110299d..d64beb349 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,7 @@ if (ENABLE_SETUP_TUI) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util) else () if (COMPILE_WIN32) + target_sources(dvmhost PRIVATE ${dvmhost_RC}) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) else () target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) @@ -76,6 +77,9 @@ include(CPack) # include(src/fne/CMakeLists.txt) add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC}) +if (COMPILE_WIN32) + target_sources(dvmfne PRIVATE ${dvmfne_RC}) +endif (COMPILE_WIN32) target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne) @@ -128,6 +132,9 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) # include(src/remote/CMakeLists.txt) add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC}) +if (COMPILE_WIN32) + target_sources(dvmcmd PRIVATE ${dvmcmd_RC}) +endif (COMPILE_WIN32) target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) @@ -137,6 +144,7 @@ target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) include(src/bridge/CMakeLists.txt) add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC}) if (COMPILE_WIN32) + target_sources(dvmbridge PRIVATE ${bridge_RC}) target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) else () if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") @@ -153,6 +161,7 @@ target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} src src/brid include(src/patch/CMakeLists.txt) add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC}) if (COMPILE_WIN32) + target_sources(dvmpatch PRIVATE ${patch_RC}) target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) else () target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) diff --git a/src/bridge/CMakeLists.txt b/src/bridge/CMakeLists.txt index 647081a2a..43e372525 100644 --- a/src/bridge/CMakeLists.txt +++ b/src/bridge/CMakeLists.txt @@ -4,7 +4,7 @@ # * GPLv2 Open Source. Use is subject to license terms. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * -# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL +# * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL # * # */ file(GLOB bridge_SRC @@ -16,6 +16,14 @@ file(GLOB bridge_SRC "src/bridge/network/*.h" "src/bridge/network/*.cpp" + "src/bridge/win32/*.h" "src/bridge/*.h" "src/bridge/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB bridge_RC + "src/bridge/win32/*.rc" +) diff --git a/src/bridge/win32/project.ico b/src/bridge/win32/project.ico new file mode 100644 index 0000000000000000000000000000000000000000..7557168b5740a7aa088f5368a1d4c7d4ec1c0ca5 GIT binary patch literal 25277 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 literal 0 HcmV?d00001 diff --git a/src/bridge/win32/resource.h b/src/bridge/win32/resource.h new file mode 100644 index 000000000..7ebe3a27a --- /dev/null +++ b/src/bridge/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/bridge/win32/resource.rc b/src/bridge/win32/resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..964be84fbe4cb6acaaa7c8b99f5d9c13a998968f GIT binary patch literal 5140 zcmdUzTTfd@5Xa}aQoqC2c_C3ThEP@Q8w5kF8ruTnrcxwCu(`CpAWYIA^|QDA{b#*C zUSq4o<$>tvc=zni%+7sh{LkMTwqe1Jtz~=m&epAN*LH3_8`_xPb*ov|zwTJS;O*J1 z{S3wd7RvodFT718h7a*dcS#AF#g8y9@Pe zRxVi+rAQ&3^@+2BHSNav5ziz02)wv0vvcAc2k^&yjoJHPzq5DF$$@voZ+~)fluoOX zX8?40=Da;l3D)4Z&A5Y{ZL8W5^QQkE@pZ_&!x-!hvzGnFP7STRb)1M>W3LLI39rp= z&A;~<>yG`F-yL)$`UMY#?4mzH=6l!7rCqr+YplM6?iRY=vLBd9bN6U?=yTaA+U*b4 zZg{?Dy#cJOR-ek6&e`ylWVcrn`)Lf0As&yN-&{9UIIp?Hi)8!N_ufK7yV6z1EkMmSVP{aYTfi!eiC#{?gB_v)*7`UUA@dPb&$s z4W6PXb*St{w{d0Q)S8Hj*$d~`VVvLuK2FLr~;Kkx8MWwH@p z@@-|e8hQx!$!-3T)j-7e`BHA|@+C{&PGDmuj2T|}Q_wL-#O!zm#cStAxisf1`py7& zd4}?A+#mBTPDEAwNET7;^C%bJPj-t;D~@aU!VdYR#i(4_#1F!f=fC#n@k(gm7CzDq zP@c$=N3=4=#l&Nk>04P&5?2j!R?~j*T$JqfwRn|ot#?F)q zb{Cujry=>1o)>4<_uNNO!&T1Qo5rJV?p4lMM(_2?^lt4vz8QHeWLb933-{=*dr5+h zG5Hu@nT_JF#b-K5tV|IzK=Sw`Nr(72^}6SXneOd|$8LihtUFv4QL!Vx(|YuqRP*F8 z!M4fkmFXK{TTp4@KPUKYdyeX2j6{9k?}h`oJ0zZuiQEA7&-R))k9AKT^YI4R<)ni- zJ_|~b!sZmeM^;mHoXa&2Uzvd)l4F!ag6|Ra`VDu>kXxm}IDq4l*9Ij#ri0yES>|7S_G0Qu1FD(E+-=g# z&GbGjPI+8MquNXSs5YAIY*yhYkITf#Hq~%t`hE#r**JDTL%*lzHQA+_7|=l#{a6{D ze0E(&FHRcF$J|mNB{r; literal 0 HcmV?d00001 diff --git a/src/fne/CMakeLists.txt b/src/fne/CMakeLists.txt index ae3ef35ed..61788027b 100644 --- a/src/fne/CMakeLists.txt +++ b/src/fne/CMakeLists.txt @@ -4,7 +4,7 @@ # * GPLv2 Open Source. Use is subject to license terms. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * -# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL +# * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL # * # */ file(GLOB dvmfne_SRC @@ -17,6 +17,14 @@ file(GLOB dvmfne_SRC "src/fne/network/*.h" "src/fne/network/*.cpp" "src/fne/xml/*.h" + "src/fne/win32/*.h" "src/fne/*.h" "src/fne/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB dvmfne_RC + "src/fne/win32/*.rc" +) diff --git a/src/fne/win32/fne.ico b/src/fne/win32/fne.ico new file mode 100644 index 0000000000000000000000000000000000000000..3a46b8772b59f056bb26f8ebdbbced9f81c0e73e GIT binary patch literal 21413 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 literal 0 HcmV?d00001 diff --git a/src/host/CMakeLists.txt b/src/host/CMakeLists.txt index a06febbac..aba8f13eb 100644 --- a/src/host/CMakeLists.txt +++ b/src/host/CMakeLists.txt @@ -4,7 +4,7 @@ # * GPLv2 Open Source. Use is subject to license terms. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * -# * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL +# * Copyright (C) 2022,2024,2025 Bryan Biedenkapp, N2PLL # * Copyright (C) 2022 Natalie Moore # * # */ @@ -50,4 +50,12 @@ file(GLOB dvmhost_SRC "src/host/modem/port/specialized/*.cpp" "src/host/network/*.h" "src/host/network/*.cpp" + "src/host/win32/*.h" +) + +# +# Windows Resource Scripts +# +file(GLOB dvmhost_RC + "src/host/win32/*.rc" ) diff --git a/src/host/win32/host.ico b/src/host/win32/host.ico new file mode 100644 index 0000000000000000000000000000000000000000..874b99977cfb9aee483517604a488369db866332 GIT binary patch literal 20989 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 literal 0 HcmV?d00001 diff --git a/src/host/win32/resource.h b/src/host/win32/resource.h new file mode 100644 index 000000000..056c0418b --- /dev/null +++ b/src/host/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/host/win32/resource.rc b/src/host/win32/resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..d1eb8f1b813539f0e4bdad32c9e165b6bfd72f43 GIT binary patch literal 5140 zcmdUzT~Av_5QgWvQvbu&xgk*jL#V3u4uSzI# z8e1jihUn;c_w3Hh&iBsv-+xwZ)q)*b)3)uCEnCg5?995>w;{jFR<(|QjaWbD?b@yV z3dSxNHTKtS#mbBWFs|*vzPIn7_|C_`7@&J)7rY}o0U}r%*my>;edeOyV||%-@_UcH zAz0#Wz}X|ahr%t;IwR7KTzr9xU=6!*P9yvskdkACogwO(H|i51AI!umoCi`t8byZj#bv}CuUphKcKn3 z&t<`A(LY(c;rWsEI^={|HySJ3%0PDC`jMZ>LIfuX3|^Wjnt#oTkJL9J!+}oG4d`+-k_mA_P)mUl0H}o zt#q;OfR*Gam#of%zIO@{X9ZnHNI~mW6ur(TaVHyhk#&T$F%Fl|VVAf%VfC7IdCjy= zhAv&w$vF@|xc-z0`e1i#35)HJIgX!L=@|O*DAE0!C(OlcLF0f3IfloI+x@MdTVuV> zy1Zi7?VeT=WD7h+p?vrdxGm-pN2gcdY?D<*X|hz($LqFM;xZ5cXZUD;>}5#~@n7r) za-%ZrC2QBf-@9)scUI9uurF@&J5~b`f6bS2V}~zU@|HLdY|Ml)_bVy{9dks?jwe7| zIXB9sIbYFtdceywlxO4qkZ(~JRh=POMD?0Sx%lC6x5%{OxQZ`ql3$vP%9RcLAk2CG zZ+{xEgqGsXvhpZJ9?QxV=a0Us9N)~ck@%^Tts3^xvrw|sx8hH_uRakElHvoAp)6Z@ z;$3U#dNyQm*e!5&ordI38lIg~KXVgB4OcjGdmM?nvllsI9=-Pq)4R3LcxB|VkY(OE zZ`_wV?j;F2M&omQWio!h6`yG%v2sL={p9gUk~Z;i>=n-eGu_yAkJ>ufSGTunp<+jV zr}gMJsoKe5f^Cq|%i}k~HlfnMe~$3m))dv*7>W9h-wL~Mw@*AD61f5Df7yHDJXSn; z%*VT9i<5Sz_$(+z3TtEhF8NFqb1v63e0c)CPmWQh$uS!67in+6qn?dNoPge9ZP=ow zY@*eihhjh8ggI0?d3NOL-fW;(X{H1RsuGmPFF5lG$-11}hu6VWl+31NngfeeRPwV-Jo?UKNz^m^OBABGF)6_0K+g zHua`9)g^vZ6{QYm5pMFBOk8YG1DD6|*U*)HW6#s~+j&}x z9cqZ)#Ld|yex&2*y-9QVm|Lj^{Dsq&z4E>#@&9xuP+^{pJDN57*?Z2#dK=XpiaPbA zPkllssfQZD+mIet**W&DmsH57XjK(WkJYG)o6MtR&rPqdRb41sevNJ#?o~Q1YCrj$ zz9xP7hGfa2 zK92sZJ0|8l^(SlIlb5rIDYz|WiYUFWvpNu0iMuqa_bP4-RdpxCi*Ahsp;52;di@)s uu1;N_Y!j=`**A%L0aX{N7f{{Wa-3xEd{nl7X&%lkF~8bdyq|vEmi-U2S$A>( literal 0 HcmV?d00001 diff --git a/src/patch/CMakeLists.txt b/src/patch/CMakeLists.txt index d6f6f46a5..de1c681b5 100644 --- a/src/patch/CMakeLists.txt +++ b/src/patch/CMakeLists.txt @@ -12,6 +12,14 @@ file(GLOB patch_SRC "src/patch/mmdvm/*.cpp" "src/patch/network/*.h" "src/patch/network/*.cpp" + "src/patch/win32/*.h" "src/patch/*.h" "src/patch/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB patch_RC + "src/patch/win32/*.rc" +) diff --git a/src/patch/win32/project.ico b/src/patch/win32/project.ico new file mode 100644 index 0000000000000000000000000000000000000000..7557168b5740a7aa088f5368a1d4c7d4ec1c0ca5 GIT binary patch literal 25277 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 literal 0 HcmV?d00001 diff --git a/src/patch/win32/resource.h b/src/patch/win32/resource.h new file mode 100644 index 000000000..114be5674 --- /dev/null +++ b/src/patch/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - TG Patch + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/patch/win32/resource.rc b/src/patch/win32/resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..bb9ede682f1e48698bdbee3fcd3db8cc7fa8c043 GIT binary patch literal 5142 zcmdUzTW?xN5Xa}aQoqBNyf{&fvEx3pZ#c#_Dh6a9(^QINIe;%M2Fo~UEcLUu{rz`X z4u^~CkUTg#I_#d^nc2DT4FCSKWm^{P)Y`UhZ*9{Wc4J-Z+t9}RZd%=X@#}&0OWwZS z+b>`og3(}q$I4b=9Dy;gNBiEsgW{_=2F3v08@u9tunQo9b%6CVf*ms#{ae;Ic{At+ zC|>~Gwd)xwe%?pU3)Zr`$dmXU*#|fh$7OcTBG&=>W4^}hy|+Ku>q0N^j`$r+PIn2l zDw%G9F3+5|&ndy0{AP?*WX-H*C(K*%_k^!w<{idhZFiI(xhup^he122%pmBb(Cg<)t5l*p#L5FiP;|ekLYd~=dzXC z?oZb4cz$HP39KyFn97>Y*@`R4cCRKj)F_LH(<*#R+M;f1eAlDI%Vhg4?j3=pU14{k z{AW?8vS5bQL_zwNR!^CoF_Yc`uTyueXY94$#kG|4=)6mk7iegRy$9G{(g!P{l|I&; z^^|K?S3&<3DL76UT|1<&{4L?K#4;qw?=f-a*I{L*Gru5954Va4-*`}24uG;j|e z=>{lIWGP0pGREb^W0mRKg`6a=n&hmO{Tg#ove&obRl2p_5*3o-4e_B|TU+q4Z8W|d zGDO%ta1J94$)EJRJhQ&zK5`A$ICFm*kGi?nIb#*Q*K5YNwRqkGXy z612zUQ+#DMioX?~=^(K(g=c`|@kx>n@o^sOo)c!ex0^9`o8(~K;i`!89r>Ns-EUIO zQ-le&OJ1)`Ux#f&rG@{T;kVfw)#Vt8`a!%K4&m;Ycs?a^1Jpm+YvSDNo?^`V4YJ2c zhjV=9l$^r$6u(baQ-xfVYaYHb13x6kDAN=%TJYzzci~ab?gS^GH?I&gD$6!nE%K1> z`+Zo1N+&OmT-}~6^eWAi;6Sy4viTKf-XK|@lZWs+nv0U<)KXLwsY;TH{TwBF&u=l_ za-3zECGDoMgB_K-q=OQCkEqx0xLbzYDow^)I4;H7poGVCuzS0Z`In!)oO;rLYNj!F zn>2Gby$|zKp4Qc<_7Xp;jpn>bcB-qrKk>`_s;uDv5G zqm@rl+wR9nhsCjIuLkf}k-98b>|>Gyi#vgev-?FIj;aquojR69pAda@QV(!6rl(bQ z_CEDG_KoFO*W9w2bTC^>$ez_$WnUwcvgPM!y%oJmr@7XX&*^K{m($6qA6`fM9tk~o zRTn&O-=}uxU(1h++^p}-d2NwAZ*irVk2>h~{)_Tk(W^74$-lv>Stp3A#9bQIi{*ERGTa6Kxaj^!5E}KW&)2^r>hjbD$~Iny nF27UM8>r4my@4v&EaD`47f0p&ua%LD7Fk{Yt>0%qZwvnihthWs literal 0 HcmV?d00001 diff --git a/src/remote/CMakeLists.txt b/src/remote/CMakeLists.txt index bfd1753de..4ebdd5899 100644 --- a/src/remote/CMakeLists.txt +++ b/src/remote/CMakeLists.txt @@ -4,10 +4,17 @@ # * GPLv2 Open Source. Use is subject to license terms. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * -# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL +# * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL # * # */ file(GLOB dvmcmd_SRC "src/remote/*.h" "src/remote/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB dvmcmd_RC + "src/remote/win32/*.rc" +) diff --git a/src/remote/win32/project.ico b/src/remote/win32/project.ico new file mode 100644 index 0000000000000000000000000000000000000000..7557168b5740a7aa088f5368a1d4c7d4ec1c0ca5 GIT binary patch literal 25277 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 literal 0 HcmV?d00001 diff --git a/src/remote/win32/resource.h b/src/remote/win32/resource.h new file mode 100644 index 000000000..6dec41ff5 --- /dev/null +++ b/src/remote/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Remote Command Client + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/remote/win32/resource.rc b/src/remote/win32/resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..1f7e53e245938d633f37c79c127fb1508cea933d GIT binary patch literal 5200 zcmdUzTTfd@5XbkqQoqC2c_C3ThEP@Q8w5kF8r#CgO{GYPU~_4GK{iQ))X(1b_n-0l zu*Pl)$pg{R@$T83nVtL2_@BQw!bXUp9a>>8yb0@}9rHhcrc7dA%52;Hl2!Fv-3mvcArH zWb-kweZF3?JLEZqcF(>C#=(evo#XVKlMiqbLo-~1IbzlZ_dUEymNMVRCQk%^%GZ>= zcj0&T&Y%@}$NY+VA-h&3?GWhlo%8lMC58sS9mXBB&;<0V~=DMlEdCeqV zCfl#J_Zk}7m9Dxb|FP**7VKa(QINi+)i$$ZX3|^ab?UD54tq^_cP$k>I`5L?8FG)X z?*Q9N`WQ-RrH^&*ddelMtDyfh3XW4o*A6LYy^5r#`6TXS<37^gAh*Zj8anI~*Qcxw zSeMr<>SSutC7qlD@tx^U*p;w5g{k=ST(yp zw{z>PH&~Zf9GKnHN`mZwrzn&Uw}IPb?l?NV3THd4DoT^3l0KfcwGx++2spz>M{_Sr za)|$8HwO9V4SuOiHs(vdt?X7q4>5c&o4;o@67d7Rldyeqz zcnZZU<3_nO=gWOZ`C6W#JnQ>YzQu{CiXX`$sskS7;@jD7k!i(o4PV$HzqA;YE1UR1 zT=D$h{vuup4NTx8-2mm8EO|sLW1P=CR++w)OCLZMB~dLqrvWib6_+if70{v%=(u5$TeK$%)NO$>gImT8LQ~Md^EjDc#Cg3 zkA*C&&Ut1Y-7_yq&>oWy@s+z#{I&Q@7m1ZAJOd<;Pm*+rj}xnVj+p7*ZdmL#$ice9 zRS^|C@;j}&-=vx+hlydEyk42V4%>oC6aP8JZ#xTAmt!RA`*t@Rz}+G7+$M4()IWxo z#JSf!dCdC_vdc*a3w#!ooWkZDzfV?E?VQWC2w%Aauj*ErCdX*PpVQukM?Kp!oQU4M zLhMjkw$N(ML$UAoVGfl}ULLu+J)7uNnkm77Y6WHU3(mYkvOXt|;B~wZCCjO$sK}{G zl1li=Bznhh9&ZKCvdofp)7Zt1%3aby3BJ`aTywXKxK$dALpUy3ZBW8vy4byyW&Y)7 zFQ=X~pqgpQ-6qXk&+o(Hl!x^@s=dUIY9p_1y`N~IVdcXy&tQda{+qu%N)un()W((h z`zdnE;@%65?ABh?YmeGuh(3n+aT(owG@ZLQCrqZ~f@u(W{9&?$G=p=PkH}E#4`&G8~j`q^}$gen;QSWc7BfYd-lthTh^xe18r7i{H;S^{ zDgVRhR!I;V^{V^VeO`bZsv>e`OsBzxzhZi6qMr*qq^t`8sI Jhd*sw{|nMoe@*}Z literal 0 HcmV?d00001 From 1d3a796359d0955f4411ac5bb70a4a491921c778 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 20 Jun 2025 14:48:10 -0400 Subject: [PATCH 041/133] migrate and condense analog audio helper and G.711 codec routines into a common class called AnalogAudio for easy reuse; stub network protocol entries to eventually handle analog audio; --- src/bridge/HostBridge.cpp | 413 ++++++++---------------------- src/bridge/HostBridge.h | 26 -- src/bridge/SampleTimeConversion.h | 54 ---- src/common/AnalogAudio.cpp | 187 ++++++++++++++ src/common/AnalogAudio.h | 99 +++++++ src/common/RC4Crypto.cpp | 2 +- src/common/VariableLengthArray.h | 21 +- src/common/network/BaseNetwork.h | 1 + src/common/network/FrameQueue.h | 2 + src/common/network/RTPFNEHeader.h | 1 + 10 files changed, 421 insertions(+), 385 deletions(-) delete mode 100644 src/bridge/SampleTimeConversion.h create mode 100644 src/common/AnalogAudio.cpp create mode 100644 src/common/AnalogAudio.h diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 7bc5dd46e..3b704a431 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -22,6 +22,7 @@ #include "common/p25/P25Utils.h" #include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" +#include "common/AnalogAudio.h" #include "common/Clock.h" #include "common/StopWatch.h" #include "common/Thread.h" @@ -30,7 +31,6 @@ #include "bridge/ActivityLog.h" #include "HostBridge.h" #include "BridgeMain.h" -#include "SampleTimeConversion.h" using namespace network; using namespace network::frame; @@ -59,20 +59,6 @@ const int NUMBER_OF_BUFFERS = 32; #define LOCAL_CALL "Local Traffic" #define UDP_CALL "UDP Traffic" -#define SIGN_BIT (0x80) // sign bit for a A-law byte -#define QUANT_MASK (0xf) // quantization field mask -#define NSEGS (8) // number of A-law segments -#define SEG_SHIFT (4) // left shift for segment number -#define SEG_MASK (0x70) // segment field mask - -static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF }; -static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF }; - -#define BIAS (0x84) // bias for linear code -#define CLIP 8159 - -const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; - #define TEK_AES "aes" #define TEK_ARC4 "arc4" @@ -102,23 +88,23 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 std::lock_guard lock(HostBridge::m_audioMutex); int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; const uint8_t* pcm = (const uint8_t*)input; for (uint32_t pcmIdx = 0; pcmIdx < pcmBytes; pcmIdx += 2) { samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); smpIdx++; } - bridge->m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH); + bridge->m_inputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); } // playback output audio - if (bridge->m_outputAudio.dataSize() >= MBE_SAMPLES_LENGTH) { - short samples[MBE_SAMPLES_LENGTH]; - bridge->m_outputAudio.get(samples, MBE_SAMPLES_LENGTH); + if (bridge->m_outputAudio.dataSize() >= AUDIO_SAMPLES_LENGTH) { + short samples[AUDIO_SAMPLES_LENGTH]; + bridge->m_outputAudio.get(samples, AUDIO_SAMPLES_LENGTH); uint8_t* pcm = (uint8_t*)output; int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; @@ -157,128 +143,6 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit } } -/* */ - -static short search(short val, short* table, short size) -{ - for (short i = 0; i < size; i++) { - if (val <= *table++) - return (i); - } - - return (size); -} - -/* Helper to convert PCM into G.711 aLaw. */ - -uint8_t encodeALaw(short pcm) -{ - short mask; - uint8_t aval; - - pcm = pcm >> 3; - - if (pcm >= 0) { - mask = 0xD5U; // sign (7th) bit = 1 - } else { - mask = 0x55U; // sign bit = 0 - pcm = -pcm - 1; - } - - // convert the scaled magnitude to segment number - short seg = search(pcm, seg_aend, 8); - - /* - ** combine the sign, segment, quantization bits - */ - if (seg >= 8) // out of range, return maximum value - return (uint8_t)(0x7F ^ mask); - else { - aval = (uint8_t) seg << SEG_SHIFT; - if (seg < 2) - aval |= (pcm >> 1) & QUANT_MASK; - else - aval |= (pcm >> seg) & QUANT_MASK; - - return (aval ^ mask); - } -} - -/* Helper to convert G.711 aLaw into PCM. */ - -short decodeALaw(uint8_t alaw) -{ - alaw ^= 0x55U; - - short t = (alaw & QUANT_MASK) << 4; - short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT; - switch (seg) { - case 0: - t += 8; - break; - case 1: - t += 0x108U; - break; - default: - t += 0x108U; - t <<= seg - 1; - } - - return ((alaw & SIGN_BIT) ? t : -t); -} - -/* Helper to convert PCM into G.711 MuLaw. */ - -uint8_t encodeMuLaw(short pcm) -{ - short mask; - - // get the sign and the magnitude of the value - pcm = pcm >> 2; - if (pcm < 0) { - pcm = -pcm; - mask = 0x7FU; - } else { - mask = 0xFFU; - } - - // clip the magnitude - if (pcm > CLIP) - pcm = CLIP; - pcm += (BIAS >> 2); - - // convert the scaled magnitude to segment number - short seg = search(pcm, seg_uend, 8); - - /* - ** combine the sign, segment, quantization bits; - ** and complement the code word - */ - if (seg >= 8) // out of range, return maximum value. - return (uint8_t)(0x7F ^ mask); - else { - uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF); - return (ulaw ^ mask); - } -} - -/* Helper to convert G.711 MuLaw into PCM. */ - -short decodeMuLaw(uint8_t ulaw) -{ - // complement to obtain normal u-law value - ulaw = ~ulaw; - - /* - ** extract and bias the quantization bits; then - ** shift up by the segment number and subtract out the bias - */ - short t = ((ulaw & QUANT_MASK) << 3) + BIAS; - t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT; - - return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); -} - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -339,8 +203,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_maCaptureDevices(nullptr), m_maDeviceConfig(), m_maDevice(), - m_inputAudio(MBE_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Input Audio Buffer"), - m_outputAudio(MBE_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Output Audio Buffer"), + m_inputAudio(AUDIO_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Input Audio Buffer"), + m_outputAudio(AUDIO_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Output Audio Buffer"), m_udpPackets(), m_decoder(nullptr), m_encoder(nullptr), @@ -553,7 +417,7 @@ int HostBridge::run() m_maDeviceConfig.playback.channels = 1; m_maDeviceConfig.playback.shareMode = ma_share_mode_shared; - m_maDeviceConfig.periodSizeInFrames = MBE_SAMPLES_LENGTH; + m_maDeviceConfig.periodSizeInFrames = AUDIO_SAMPLES_LENGTH; m_maDeviceConfig.dataCallback = audioCallback; m_maDeviceConfig.pUserData = this; @@ -824,7 +688,7 @@ int HostBridge::ambeDecode(const uint8_t* codeword, uint32_t codewordLength, sho assert(codeword != nullptr); assert(samples != nullptr); - //samples = new short[MBE_SAMPLES_LENGTH]; + //samples = new short[AUDIO_SAMPLES_LENGTH]; UInt8Array cw = std::make_unique(codewordLength); ::memcpy(cw.get(), codeword, codewordLength); @@ -856,17 +720,17 @@ int HostBridge::ambeDecode(const uint8_t* codeword, uint32_t codewordLength, sho std::unique_ptr codewordBits = std::make_unique(m_frameLengthInBits * 2); unpackBytesToBits(codewordBits.get(), cw.get(), m_frameLengthInBytes, m_frameLengthInBits); - std::unique_ptr n0 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - ambe_voice_dec(n0.get(), MBE_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 0, m_decoderState); + std::unique_ptr n0 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + ambe_voice_dec(n0.get(), AUDIO_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 0, m_decoderState); - std::unique_ptr n1 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - ambe_voice_dec(n1.get(), MBE_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 1, m_decoderState); + std::unique_ptr n1 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + ambe_voice_dec(n1.get(), AUDIO_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 1, m_decoderState); // combine sample segments into contiguous samples - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) samples[i] = n0[i]; - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) - samples[i + (MBE_SAMPLES_LENGTH / 2)] = n1[i]; + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) + samples[i + (AUDIO_SAMPLES_LENGTH / 2)] = n1[i]; return 0; // this always just returns no errors? } @@ -930,29 +794,29 @@ void HostBridge::ambeEncode(const short* samples, uint32_t sampleLength, uint8_t //codeword = new byte[this.frameLengthInBytes]; - if (sampleLength > MBE_SAMPLES_LENGTH) { - ::LogError(LOG_HOST, "Samples length is > %u", MBE_SAMPLES_LENGTH); + if (sampleLength > AUDIO_SAMPLES_LENGTH) { + ::LogError(LOG_HOST, "Samples length is > %u", AUDIO_SAMPLES_LENGTH); return; } - if (sampleLength < MBE_SAMPLES_LENGTH) { - ::LogError(LOG_HOST, "Samples length is < %u", MBE_SAMPLES_LENGTH); + if (sampleLength < AUDIO_SAMPLES_LENGTH) { + ::LogError(LOG_HOST, "Samples length is < %u", AUDIO_SAMPLES_LENGTH); return; } std::unique_ptr codewordBits = std::make_unique(m_frameLengthInBits * 2); // split samples into 2 segments - std::unique_ptr n0 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) + std::unique_ptr n0 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) n0[i] = samples[i]; - ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n0.get(), MBE_SAMPLES_LENGTH / 2, m_ecMode, 0, 8192, m_encoderState); + ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n0.get(), AUDIO_SAMPLES_LENGTH / 2, m_ecMode, 0, 8192, m_encoderState); - std::unique_ptr n1 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) - n1[i] = samples[i + (MBE_SAMPLES_LENGTH / 2)]; + std::unique_ptr n1 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) + n1[i] = samples[i + (AUDIO_SAMPLES_LENGTH / 2)]; - ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n1.get(), MBE_SAMPLES_LENGTH / 2, m_ecMode, 1, 8192, m_encoderState); + ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n1.get(), AUDIO_SAMPLES_LENGTH / 2, m_ecMode, 1, 8192, m_encoderState); // is this to be a DMR codeword? if (m_txMode == TX_MODE_DMR) { @@ -1328,7 +1192,7 @@ void HostBridge::processUDPAudio() } if (m_udpRTPFrames || m_udpUsrp) - pcmLength = MBE_SAMPLES_LENGTH * 2U; + pcmLength = AUDIO_SAMPLES_LENGTH * 2U; DECLARE_UINT8_ARRAY(pcm, pcmLength); @@ -1342,7 +1206,7 @@ void HostBridge::processUDPAudio() return; } - ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, MBE_SAMPLES_LENGTH * 2U); + ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, AUDIO_SAMPLES_LENGTH * 2U); } else { if (m_udpNoIncludeLength) { @@ -1628,7 +1492,7 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst for (uint32_t i = 0; i < RAW_AMBE_LENGTH_BYTES; i++) ambePartial[i] = ambe[i + (n * 9)]; - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; int errs = 0; #if defined(_WIN32) if (m_useExternalVocoder) { @@ -1645,69 +1509,55 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst LogMessage(LOG_HOST, DMR_DT_VOICE ", Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", dmrN, n, srcId, dstId, errs); // post-process: apply gain to decoded audio frames - if (m_rxAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_rxAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); if (m_localAudio) { - m_outputAudio.addData(samples, MBE_SAMPLES_LENGTH); + m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); } if (m_udpAudio) { int pcmIdx = 0; - uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; if (m_udpUseULaw) { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[smpIdx] = encodeMuLaw(samples[smpIdx]); + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); } if (m_trace) - Utils::dump(1U, "HostBridge()::decodeDMRAudioFrame() Encoded uLaw Audio", pcm, MBE_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::decodeDMRAudioFrame() Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } } - uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U; + uint32_t length = (AUDIO_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; if (!m_udpUsrp) { if (!m_udpMetadata) { - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) if (m_udpUseULaw) { - length = (MBE_SAMPLES_LENGTH)+4U; + length = (AUDIO_SAMPLES_LENGTH)+4U; if (m_udpNoIncludeLength) { - length = MBE_SAMPLES_LENGTH; - ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + length = AUDIO_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, AUDIO_SAMPLES_LENGTH); } else { - SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); + SET_UINT32(AUDIO_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH); } // are we sending RTP audio frames? if (m_udpRTPFrames) { - uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); + uint8_t* rtpFrame = generateRTPHeaders(AUDIO_SAMPLES_LENGTH, m_rtpSeqNo); if (rtpFrame != nullptr) { length += RTP_HEADER_LENGTH_BYTES; uint8_t* newAudioData = new uint8_t[length]; ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); - ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, AUDIO_SAMPLES_LENGTH); delete[] audioData; audioData = newAudioData; @@ -1717,26 +1567,26 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst } } else { - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); } } else { - length = (MBE_SAMPLES_LENGTH * 2U) + 12U; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + length = (AUDIO_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); // embed destination and source IDs - SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); - SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); + SET_UINT32(dstId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 4U)); + SET_UINT32(srcId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 8U)); } } else { uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; - length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + length = (AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) m_usrpSeqNo++; usrpHeader[15U] = 1; // set PTT state to true @@ -1744,7 +1594,7 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst ::memcpy(usrpHeader, "USRP", 4); ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload - ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, MBE_SAMPLES_LENGTH * 2U); + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, AUDIO_SAMPLES_LENGTH * 2U); } sockaddr_storage addr; @@ -1882,35 +1732,21 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ } int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; - for (uint32_t pcmIdx = 0; pcmIdx < (MBE_SAMPLES_LENGTH * 2U); pcmIdx += 2) { + short samples[AUDIO_SAMPLES_LENGTH]; + for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) { samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); smpIdx++; } // pre-process: apply gain to PCM audio frames - if (m_txAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_txAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain); // encode PCM samples into AMBE codewords uint8_t ambe[RAW_AMBE_LENGTH_BYTES]; ::memset(ambe, 0x00U, RAW_AMBE_LENGTH_BYTES); #if defined(_WIN32) if (m_useExternalVocoder) { - ambeEncode(samples, MBE_SAMPLES_LENGTH, ambe); + ambeEncode(samples, AUDIO_SAMPLES_LENGTH, ambe); } else { #endif // defined(_WIN32) @@ -2307,7 +2143,7 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI } } - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; int errs = 0; #if defined(_WIN32) if (m_useExternalVocoder) { @@ -2324,70 +2160,56 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI LogDebug(LOG_HOST, "P25, LDU (Logical Link Data Unit), Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", p25N, n, srcId, dstId, errs); // post-process: apply gain to decoded audio frames - if (m_rxAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_rxAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); if (m_localAudio) { - m_outputAudio.addData(samples, MBE_SAMPLES_LENGTH); + m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); } if (m_udpAudio) { int pcmIdx = 0; - uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; if (m_udpUseULaw) { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[smpIdx] = encodeMuLaw(samples[smpIdx]); + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); } if (m_trace) - Utils::dump(1U, "HostBridge()::decodeP25AudioFrame() Encoded uLaw Audio", pcm, MBE_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::decodeP25AudioFrame() Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } } - uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U; + uint32_t length = (AUDIO_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; if (!m_udpUsrp) { if (!m_udpMetadata) { - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) if (m_udpUseULaw) { - length = (MBE_SAMPLES_LENGTH)+4U; + length = (AUDIO_SAMPLES_LENGTH)+4U; if (m_udpNoIncludeLength) { - length = MBE_SAMPLES_LENGTH; - ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + length = AUDIO_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, AUDIO_SAMPLES_LENGTH); } else { - SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); + SET_UINT32(AUDIO_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH); } // are we sending RTP audio frames? if (m_udpRTPFrames) { - uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); + uint8_t* rtpFrame = generateRTPHeaders(AUDIO_SAMPLES_LENGTH, m_rtpSeqNo); if (rtpFrame != nullptr) { length += RTP_HEADER_LENGTH_BYTES; uint8_t* newAudioData = new uint8_t[length]; ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); - ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, AUDIO_SAMPLES_LENGTH); delete[] audioData; audioData = newAudioData; @@ -2397,26 +2219,26 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI } } else { - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); } } else { - length = (MBE_SAMPLES_LENGTH * 2U) + 12U; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + length = (AUDIO_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); // embed destination and source IDs - SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); - SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); + SET_UINT32(dstId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 4U)); + SET_UINT32(srcId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 8U)); } } else { uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; - length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + length = (AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) m_usrpSeqNo++; usrpHeader[15U] = 1; // set PTT state to true @@ -2424,7 +2246,7 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI ::memcpy(usrpHeader, "USRP", 4); ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload - ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, MBE_SAMPLES_LENGTH * 2U); + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, AUDIO_SAMPLES_LENGTH * 2U); } sockaddr_storage addr; @@ -2455,35 +2277,21 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ ::memset(m_netLDU2, 0x00U, 9U * 25U); int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; - for (uint32_t pcmIdx = 0; pcmIdx < (MBE_SAMPLES_LENGTH * 2U); pcmIdx += 2) { + short samples[AUDIO_SAMPLES_LENGTH]; + for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) { samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); smpIdx++; } // pre-process: apply gain to PCM audio frames - if (m_txAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_txAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain); // encode PCM samples into IMBE codewords uint8_t imbe[RAW_IMBE_LENGTH_BYTES]; ::memset(imbe, 0x00U, RAW_IMBE_LENGTH_BYTES); #if defined(_WIN32) if (m_useExternalVocoder) { - ambeEncode(samples, MBE_SAMPLES_LENGTH, imbe); + ambeEncode(samples, AUDIO_SAMPLES_LENGTH, imbe); } else { #endif // defined(_WIN32) @@ -2636,7 +2444,7 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ /* Helper to send USRP end of transmission */ -void HostBridge::sendUsrpEot() +void HostBridge::sendUsrpEot() { sockaddr_storage addr; uint32_t addrLen; @@ -2657,7 +2465,7 @@ void HostBridge::generatePreambleTone() { std::lock_guard lock(m_audioMutex); - uint64_t frameCount = SampleTimeConvert::ToSamples(SAMPLE_RATE, 1, m_preambleLength); + uint64_t frameCount = AnalogAudio::toSamples(SAMPLE_RATE, 1, m_preambleLength); if (frameCount > m_outputAudio.freeSpace()) { ::LogError(LOG_HOST, "failed to generate preamble tone"); return; @@ -2671,8 +2479,7 @@ void HostBridge::generatePreambleTone() ma_waveform_read_pcm_frames(&m_maSineWaveform, sine, frameCount, NULL); int smpIdx = 0; - std::unique_ptr __UNIQUE_sineSamples = std::make_unique(frameCount); - short* sineSamples = __UNIQUE_sineSamples.get(); + DECLARE_SHORT_ARRAY(sineSamples, frameCount); const uint8_t* pcm = (const uint8_t*)sine; for (uint32_t pcmIdx = 0; pcmIdx < pcmBytes; pcmIdx += 2) { sineSamples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); @@ -2872,13 +2679,13 @@ void* HostBridge::threadAudioProcess(void* arg) { std::lock_guard lock(m_audioMutex); - if (bridge->m_inputAudio.dataSize() >= MBE_SAMPLES_LENGTH) { - short samples[MBE_SAMPLES_LENGTH]; - bridge->m_inputAudio.get(samples, MBE_SAMPLES_LENGTH); + if (bridge->m_inputAudio.dataSize() >= AUDIO_SAMPLES_LENGTH) { + short samples[AUDIO_SAMPLES_LENGTH]; + bridge->m_inputAudio.get(samples, AUDIO_SAMPLES_LENGTH); // process MDC, if necessary if (bridge->m_overrideSrcIdFromMDC) - mdc_decoder_process_samples(bridge->m_mdcDecoder, samples, MBE_SAMPLES_LENGTH); + mdc_decoder_process_samples(bridge->m_mdcDecoder, samples, AUDIO_SAMPLES_LENGTH); float sampleLevel = bridge->m_voxSampleLevel / 1000; @@ -2896,7 +2703,7 @@ void* HostBridge::threadAudioProcess(void* arg) // perform maximum sample detection float maxSample = 0.0f; - for (int i = 0; i < MBE_SAMPLES_LENGTH; i++) { + for (int i = 0; i < AUDIO_SAMPLES_LENGTH; i++) { float sampleValue = fabs((float)samples[i]); maxSample = fmax(maxSample, sampleValue); } @@ -2951,11 +2758,11 @@ void* HostBridge::threadAudioProcess(void* arg) } if (bridge->m_audioDetect && !bridge->m_callInProgress) { - ma_uint32 pcmBytes = MBE_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); + ma_uint32 pcmBytes = AUDIO_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); DECLARE_UINT8_ARRAY(pcm, pcmBytes); int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; @@ -3066,18 +2873,18 @@ void* HostBridge::threadUDPAudioProcess(void* arg) std::lock_guard lock(m_audioMutex); int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; if (bridge->m_udpUseULaw) { if (bridge->m_trace) - Utils::dump(1U, "HostBridge()::threadUDPAudioProcess() uLaw Audio", req->pcm, MBE_SAMPLES_LENGTH * 2U); + Utils::dump(1U, "HostBridge()::threadUDPAudioProcess() uLaw Audio", req->pcm, AUDIO_SAMPLES_LENGTH * 2U); - for (uint32_t pcmIdx = 0; pcmIdx < MBE_SAMPLES_LENGTH; pcmIdx++) { - samples[smpIdx] = decodeMuLaw(req->pcm[pcmIdx]); + for (uint32_t pcmIdx = 0; pcmIdx < AUDIO_SAMPLES_LENGTH; pcmIdx++) { + samples[smpIdx] = AnalogAudio::decodeMuLaw(req->pcm[pcmIdx]); smpIdx++; } int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { req->pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); req->pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 398935e8c..b29343fa6 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -47,7 +47,6 @@ // Constants // --------------------------------------------------------------------------- -#define MBE_SAMPLES_LENGTH 160 #define NO_BIT_STEAL 0 #define ECMODE_NOISE_SUPPRESS 0x40 @@ -93,31 +92,6 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unitID, mdc_u8_t extra0, mdc_u8_t extra1, mdc_u8_t extra2, mdc_u8_t extra3, void* context); -/** - * @brief Helper to convert PCM into G.711 aLaw. - * @param pcm PCM value. - * @return uint8_t aLaw value. - */ -uint8_t encodeALaw(short pcm); -/** - * @brief Helper to convert G.711 aLaw into PCM. - * @param alaw aLaw value. - * @return short PCM value. - */ -short decodeALaw(uint8_t alaw); -/** - * @brief Helper to convert PCM into G.711 MuLaw. - * @param pcm PCM value. - * @return uint8_t MuLaw value. - */ -uint8_t encodeMuLaw(short pcm); -/** - * @brief Helper to convert G.711 MuLaw into PCM. - * @param ulaw MuLaw value. - * @return short PCM value. - */ -short decodeMuLaw(uint8_t ulaw); - // --------------------------------------------------------------------------- // Structure Declaration // --------------------------------------------------------------------------- diff --git a/src/bridge/SampleTimeConversion.h b/src/bridge/SampleTimeConversion.h deleted file mode 100644 index 662547996..000000000 --- a/src/bridge/SampleTimeConversion.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Bridge - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ - /** - * @file SampleTimeConversion.h - * @ingroup bridge - */ -#if !defined(__SAMPLE_TIME_CONVERSION_H__) -#define __SAMPLE_TIME_CONVERSION_H__ - -#include "Defines.h" - -// --------------------------------------------------------------------------- -// Class Declaration -// --------------------------------------------------------------------------- - -/** -* @brief -* @ingroup bridge -*/ -class HOST_SW_API SampleTimeConvert { -public: - /** - * @brief (ms) to sample count conversion - * @param sampleRate Sample rate. - * @param channels Number of audio channels. - * @param ms Number of milliseconds. - * @returns int Number of samples. - */ - static int ToSamples(uint32_t sampleRate, uint8_t channels, int ms) - { - return (int)(((long)ms) * sampleRate * channels / 1000); - } - - /** - * @brief Sample count to (ms) conversion - * @param sampleRate Sample rate. - * @param channels Number of audio channels. - * @param samples Number of samples. - * @returns int Number of milliseconds. - */ - static int ToMS(uint32_t sampleRate, uint8_t channels, int samples) - { - return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); - } -}; - -#endif // __SAMPLE_TIME_CONVERSION_H__ diff --git a/src/common/AnalogAudio.cpp b/src/common/AnalogAudio.cpp new file mode 100644 index 000000000..429d04d2b --- /dev/null +++ b/src/common/AnalogAudio.cpp @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "AnalogAudio.h" +#include "Log.h" +#include "Utils.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define SIGN_BIT (0x80) // sign bit for a A-law byte +#define QUANT_MASK (0xf) // quantization field mask +#define NSEGS (8) // number of A-law segments +#define SEG_SHIFT (4) // left shift for segment number +#define SEG_MASK (0x70) // segment field mask + +static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF }; +static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF }; + +#define BIAS (0x84) // bias for linear code +#define CLIP 8159 + +#define SHORT_CLIP_SMPL_MIN -32767 +#define SHORT_CLIP_SMPL_MAX 32767 + +// --------------------------------------------------------------------------- +// Public Static Class Members +// --------------------------------------------------------------------------- + +/* Helper to convert PCM into G.711 aLaw. */ + +uint8_t AnalogAudio::encodeALaw(short pcm) +{ + short mask; + uint8_t aval; + + pcm = pcm >> 3; + + if (pcm >= 0) { + mask = 0xD5U; // sign (7th) bit = 1 + } else { + mask = 0x55U; // sign bit = 0 + pcm = -pcm - 1; + } + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_aend, 8); + + /* + ** combine the sign, segment, quantization bits + */ + if (seg >= 8) // out of range, return maximum value + return (uint8_t)(0x7F ^ mask); + else { + aval = (uint8_t) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm >> 1) & QUANT_MASK; + else + aval |= (pcm >> seg) & QUANT_MASK; + + return (aval ^ mask); + } +} + +/* Helper to convert G.711 aLaw into PCM. */ + +short AnalogAudio::decodeALaw(uint8_t alaw) +{ + alaw ^= 0x55U; + + short t = (alaw & QUANT_MASK) << 4; + short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108U; + break; + default: + t += 0x108U; + t <<= seg - 1; + } + + return ((alaw & SIGN_BIT) ? t : -t); +} + +/* Helper to convert PCM into G.711 MuLaw. */ + +uint8_t AnalogAudio::encodeMuLaw(short pcm) +{ + short mask; + + // get the sign and the magnitude of the value + pcm = pcm >> 2; + if (pcm < 0) { + pcm = -pcm; + mask = 0x7FU; + } else { + mask = 0xFFU; + } + + // clip the magnitude + if (pcm > CLIP) + pcm = CLIP; + pcm += (BIAS >> 2); + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_uend, 8); + + /* + ** combine the sign, segment, quantization bits; + ** and complement the code word + */ + if (seg >= 8) // out of range, return maximum value. + return (uint8_t)(0x7F ^ mask); + else { + uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF); + return (ulaw ^ mask); + } +} + +/* Helper to convert G.711 MuLaw into PCM. */ + +short AnalogAudio::decodeMuLaw(uint8_t ulaw) +{ + // complement to obtain normal u-law value + ulaw = ~ulaw; + + /* + ** extract and bias the quantization bits; then + ** shift up by the segment number and subtract out the bias + */ + short t = ((ulaw & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT; + + return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* Helper to apply linear gain to PCM samples. */ + +void AnalogAudio::gain(short* pcm, int length, float gain) +{ + assert(pcm != nullptr); + assert(length > 0); + + if (gain == 1.0f) + return; + + for (int i = 0; i < length; i++) { + int sample = static_cast(pcm[i] * gain); + if (sample > SHORT_CLIP_SMPL_MAX) + sample = SHORT_CLIP_SMPL_MAX; + else if (sample < SHORT_CLIP_SMPL_MIN) + sample = SHORT_CLIP_SMPL_MIN; + + pcm[i] = static_cast(sample); + } +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- + +/* Searches for the segment in the given array. */ + +short AnalogAudio::search(short val, short* table, short size) +{ + for (short i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + + return (size); +} diff --git a/src/common/AnalogAudio.h b/src/common/AnalogAudio.h new file mode 100644 index 000000000..5a507a174 --- /dev/null +++ b/src/common/AnalogAudio.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file AnalogAudio.h + * @ingroup common + * @file AnalogAudio.cpp + * @ingroup common + */ +#if !defined(__ANALOG_AUDIO_H__) +#define __ANALOG_AUDIO_H__ + +#include "common/Defines.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define AUDIO_SAMPLES_LENGTH 160 // sample size for 20ms of 16-bit audio at 8kHz + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief Defines and implements aLaw and uLaw audio codecs, along with helper routines for analog audio. + * @ingroup common + */ +class HOST_SW_API AnalogAudio { +public: + /** + * @brief Helper to convert PCM into G.711 aLaw. + * @param pcm PCM value. + * @return uint8_t aLaw value. + */ + static uint8_t encodeALaw(short pcm); + /** + * @brief Helper to convert G.711 aLaw into PCM. + * @param alaw aLaw value. + * @return short PCM value. + */ + static short decodeALaw(uint8_t alaw); + /** + * @brief Helper to convert PCM into G.711 MuLaw. + * @param pcm PCM value. + * @return uint8_t MuLaw value. + */ + static uint8_t encodeMuLaw(short pcm); + /** + * @brief Helper to convert G.711 MuLaw into PCM. + * @param ulaw MuLaw value. + * @return short PCM value. + */ + static short decodeMuLaw(uint8_t ulaw); + + /** + * @brief Helper to apply linear gain to PCM samples. + * @param pcm Buffer containing PCM samples. + * @param length Length of the PCM buffer in samples. + * @param gain Gain factor to apply to the PCM samples (1.0f = no change). + */ + static void gain(short *pcm, int length, float gain); + + /** + * @brief (ms) to sample count conversion. + * @param sampleRate Sample rate. + * @param channels Number of audio channels. + * @param ms Number of milliseconds. + * @returns int Number of samples. + */ + static int toSamples(uint32_t sampleRate, uint8_t channels, int ms) { return (int)(((long)ms) * sampleRate * channels / 1000); } + + /** + * @brief Sample count to (ms) conversion. + * @param sampleRate Sample rate. + * @param channels Number of audio channels. + * @param samples Number of samples. + * @returns int Number of milliseconds. + */ + static int toMS(uint32_t sampleRate, uint8_t channels, int samples) { return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); } + +private: + /** + * @brief Searches for the segment in the given array. + * @param val PCM value to search for. + * @param table Array of segment values. + * @param size Size of the segment array. + * @return short + */ + static short search(short val, short* table, short size); +}; + +#endif // __ANALOG_AUDIO_H__ diff --git a/src/common/RC4Crypto.cpp b/src/common/RC4Crypto.cpp index f8b425531..78c9e6cf1 100644 --- a/src/common/RC4Crypto.cpp +++ b/src/common/RC4Crypto.cpp @@ -22,7 +22,7 @@ using namespace crypto; // Public Class Members // --------------------------------------------------------------------------- -/* Initializes a new instance of the AES class. */ +/* Initializes a new instance of the RC4 class. */ RC4::RC4() = default; diff --git a/src/common/VariableLengthArray.h b/src/common/VariableLengthArray.h index 243648397..afe62707b 100644 --- a/src/common/VariableLengthArray.h +++ b/src/common/VariableLengthArray.h @@ -41,6 +41,12 @@ typedef std::unique_ptr UInt8Array; */ typedef std::unique_ptr CharArray; +/** + * @brief Unique char array. + * @ingroup common + */ +typedef std::unique_ptr ShortArray; + /** @} */ // --------------------------------------------------------------------------- @@ -66,7 +72,7 @@ typedef std::unique_ptr CharArray; /** * @brief Declares a unique char array/buffer. - * This macro creates a unique pointer to a uint8_t array of the specified length and initializes it to zero. + * This macro creates a unique pointer to a char array of the specified length and initializes it to zero. * The resulting pointer is named after the parameter name passed to the macro. * @ingroup common * @param name Name of array/buffer. @@ -77,6 +83,19 @@ typedef std::unique_ptr CharArray; char* name = __##name##__CharArray.get(); \ ::memset(name, 0, len); +/** + * @brief Declares a unique short array/buffer. + * This macro creates a unique pointer to a short array of the specified length and initializes it to zero. + * The resulting pointer is named after the parameter name passed to the macro. + * @ingroup common + * @param name Name of array/buffer. + * @param len Length of array/buffer. + */ +#define DECLARE_SHORT_ARRAY(name, len) \ + ShortArray __##name##__ShortArray = std::make_unique(len); \ + short* name = __##name##__ShortArray.get(); \ + ::memset(name, 0, len); + /** @} */ #endif // __VARIABLE_LENGTH_ARRAY_H__ \ No newline at end of file diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index e317f7fd1..7d5492a64 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -52,6 +52,7 @@ #define TAG_DMR_DATA "DMRD" #define TAG_P25_DATA "P25D" #define TAG_NXDN_DATA "NXDD" +#define TAG_ANALOG_DATA "ANOD" #define TAG_REPEATER_LOGIN "RPTL" #define TAG_REPEATER_AUTH "RPTK" diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 041863f14..492934575 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -28,6 +28,8 @@ namespace network // Constants // --------------------------------------------------------------------------- + const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; + const uint8_t DVM_RTP_PAYLOAD_TYPE = 0x56U; // --------------------------------------------------------------------------- diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 02d106fe7..8719f8dcc 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -87,6 +87,7 @@ namespace network PROTOCOL_SUBFUNC_DMR = 0x00U, //! DMR PROTOCOL_SUBFUNC_P25 = 0x01U, //! P25 PROTOCOL_SUBFUNC_NXDN = 0x02U, //! NXDN + PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //! Analog MASTER_SUBFUNC_WL_RID = 0x00U, //! Whitelist RIDs MASTER_SUBFUNC_BL_RID = 0x01U, //! Blacklist RIDs From d71c02f454f5a562ebbd26370b6d808121b23add Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 20 Jun 2025 17:28:20 -0400 Subject: [PATCH 042/133] add support in the DVM network protocol for analog FM audio traffic; add support to the DVM FNE to properly switch analog FM audio traffic; bump revision from H to J; --- src/bridge/HostBridge.cpp | 28 +- src/bridge/network/PeerNetwork.cpp | 2 +- src/common/AnalogAudio.h | 99 --- src/common/CMakeLists.txt | 10 +- src/common/Defines.h | 2 +- src/common/{ => analog}/AnalogAudio.cpp | 4 +- src/common/analog/AnalogAudio.h | 97 +++ src/common/analog/AnalogDefines.h | 56 ++ src/common/analog/data/NetData.cpp | 97 +++ src/common/analog/data/NetData.h | 104 +++ src/common/network/BaseNetwork.cpp | 148 +++- src/common/network/BaseNetwork.h | 71 +- src/common/network/Network.cpp | 104 ++- src/common/network/Network.h | 22 +- src/fne/HostFNE.cpp | 32 +- src/fne/HostFNE.h | 13 + src/fne/network/FNENetwork.cpp | 40 +- src/fne/network/FNENetwork.h | 12 +- src/fne/network/PeerNetwork.cpp | 15 +- src/fne/network/PeerNetwork.h | 13 +- src/fne/network/callhandler/TagAnalogData.cpp | 710 ++++++++++++++++++ src/fne/network/callhandler/TagAnalogData.h | 196 +++++ src/fne/network/callhandler/TagDMRData.h | 8 +- src/host/Host.Config.cpp | 2 +- src/patch/network/PeerNetwork.cpp | 2 +- src/sysview/SysViewMain.cpp | 58 +- src/sysview/network/PeerNetwork.cpp | 4 +- src/sysview/network/PeerNetwork.h | 7 +- 28 files changed, 1805 insertions(+), 151 deletions(-) delete mode 100644 src/common/AnalogAudio.h rename src/common/{ => analog}/AnalogAudio.cpp (98%) create mode 100644 src/common/analog/AnalogAudio.h create mode 100644 src/common/analog/AnalogDefines.h create mode 100644 src/common/analog/data/NetData.cpp create mode 100644 src/common/analog/data/NetData.h create mode 100644 src/fne/network/callhandler/TagAnalogData.cpp create mode 100644 src/fne/network/callhandler/TagAnalogData.h diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 3b704a431..64c37ea46 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -9,6 +9,8 @@ * */ #include "Defines.h" +#include "common/analog/AnalogDefines.h" +#include "common/analog/AnalogAudio.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/data/EMB.h" #include "common/dmr/data/NetData.h" @@ -22,7 +24,6 @@ #include "common/p25/P25Utils.h" #include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" -#include "common/AnalogAudio.h" #include "common/Clock.h" #include "common/StopWatch.h" #include "common/Thread.h" @@ -32,6 +33,8 @@ #include "HostBridge.h" #include "BridgeMain.h" +using namespace analog; +using namespace analog::defines; using namespace network; using namespace network::frame; using namespace network::udp; @@ -1616,6 +1619,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ assert(pcm != nullptr); using namespace dmr; using namespace dmr::defines; + using namespace dmr::data; uint32_t srcId = m_srcId; if (m_srcIdOverride != 0 && (m_overrideSrcIdFromMDC)) @@ -1656,7 +1660,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ fullLC.encode(dmrLC, data, DataType::VOICE_LC_HEADER); // generate DMR network frame - data::NetData dmrData; + NetData dmrData; dmrData.setSlotNo(m_slot); dmrData.setDataType(DataType::VOICE_LC_HEADER); dmrData.setSrcId(srcId); @@ -1701,7 +1705,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); // generated embedded signalling - data::EMB emb = data::EMB(); + EMB emb = EMB(); emb.setColorCode(0U); emb.setLCSS(lcss); emb.encode(data); @@ -1710,7 +1714,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ LogMessage(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); // generate DMR network frame - data::NetData dmrData; + NetData dmrData; dmrData.setSlotNo(m_slot); dmrData.setDataType(dataType); dmrData.setSrcId(srcId); @@ -1769,6 +1773,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) using namespace p25; using namespace p25::defines; using namespace p25::dfsi::defines; + using namespace p25::data; if (m_txMode != TX_MODE_P25) return; @@ -1815,7 +1820,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) uint8_t lsd2 = buffer[21U]; lc::LC control; - data::LowSpeedData lsd; + LowSpeedData lsd; control.setLCO(lco); control.setSrcId(srcId); @@ -2268,6 +2273,7 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ assert(pcm != nullptr); using namespace p25; using namespace p25::defines; + using namespace p25::data; if (m_p25N > 17) m_p25N = 0; @@ -2423,7 +2429,7 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ m_p25Crypto->getMI(mi); lc.setMI(mi); - data::LowSpeedData lsd = data::LowSpeedData(); + LowSpeedData lsd = LowSpeedData(); // send P25 LDU1 if (m_p25N == 8U) { @@ -2703,7 +2709,7 @@ void* HostBridge::threadAudioProcess(void* arg) // perform maximum sample detection float maxSample = 0.0f; - for (int i = 0; i < AUDIO_SAMPLES_LENGTH; i++) { + for (int i = 0; i < (int)AUDIO_SAMPLES_LENGTH; i++) { float sampleValue = fabs((float)samples[i]); maxSample = fmax(maxSample, sampleValue); } @@ -3091,6 +3097,7 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) { using namespace dmr; using namespace dmr::defines; + using namespace dmr::data; m_dmrN = (uint8_t)(m_dmrSeqNo % 6); @@ -3120,7 +3127,7 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); // generated embedded signalling - data::EMB emb = data::EMB(); + EMB emb = EMB(); emb.setColorCode(0U); emb.setLCSS(lcss); emb.encode(data); @@ -3129,7 +3136,7 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); // generate DMR network frame - data::NetData dmrData; + NetData dmrData; dmrData.setSlotNo(m_slot); dmrData.setDataType(dataType); dmrData.setSrcId(srcId); @@ -3152,6 +3159,7 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) { using namespace p25; using namespace p25::defines; + using namespace p25::data; // fill the LDU buffers appropriately switch (m_p25N) { @@ -3176,7 +3184,7 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) lc.setAlgId(ALGO_UNENCRYPT); lc.setKId(0); - data::LowSpeedData lsd = data::LowSpeedData(); + LowSpeedData lsd = LowSpeedData(); // send P25 LDU1 if (m_p25N == 0U) { diff --git a/src/bridge/network/PeerNetwork.cpp b/src/bridge/network/PeerNetwork.cpp index 80f980e2a..deb9cc911 100644 --- a/src/bridge/network/PeerNetwork.cpp +++ b/src/bridge/network/PeerNetwork.cpp @@ -29,7 +29,7 @@ using namespace network; PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, false, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) { assert(!address.empty()); assert(port > 0U); diff --git a/src/common/AnalogAudio.h b/src/common/AnalogAudio.h deleted file mode 100644 index 5a507a174..000000000 --- a/src/common/AnalogAudio.h +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file AnalogAudio.h - * @ingroup common - * @file AnalogAudio.cpp - * @ingroup common - */ -#if !defined(__ANALOG_AUDIO_H__) -#define __ANALOG_AUDIO_H__ - -#include "common/Defines.h" - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -#define AUDIO_SAMPLES_LENGTH 160 // sample size for 20ms of 16-bit audio at 8kHz - -// --------------------------------------------------------------------------- -// Class Declaration -// --------------------------------------------------------------------------- - -/** - * @brief Defines and implements aLaw and uLaw audio codecs, along with helper routines for analog audio. - * @ingroup common - */ -class HOST_SW_API AnalogAudio { -public: - /** - * @brief Helper to convert PCM into G.711 aLaw. - * @param pcm PCM value. - * @return uint8_t aLaw value. - */ - static uint8_t encodeALaw(short pcm); - /** - * @brief Helper to convert G.711 aLaw into PCM. - * @param alaw aLaw value. - * @return short PCM value. - */ - static short decodeALaw(uint8_t alaw); - /** - * @brief Helper to convert PCM into G.711 MuLaw. - * @param pcm PCM value. - * @return uint8_t MuLaw value. - */ - static uint8_t encodeMuLaw(short pcm); - /** - * @brief Helper to convert G.711 MuLaw into PCM. - * @param ulaw MuLaw value. - * @return short PCM value. - */ - static short decodeMuLaw(uint8_t ulaw); - - /** - * @brief Helper to apply linear gain to PCM samples. - * @param pcm Buffer containing PCM samples. - * @param length Length of the PCM buffer in samples. - * @param gain Gain factor to apply to the PCM samples (1.0f = no change). - */ - static void gain(short *pcm, int length, float gain); - - /** - * @brief (ms) to sample count conversion. - * @param sampleRate Sample rate. - * @param channels Number of audio channels. - * @param ms Number of milliseconds. - * @returns int Number of samples. - */ - static int toSamples(uint32_t sampleRate, uint8_t channels, int ms) { return (int)(((long)ms) * sampleRate * channels / 1000); } - - /** - * @brief Sample count to (ms) conversion. - * @param sampleRate Sample rate. - * @param channels Number of audio channels. - * @param samples Number of samples. - * @returns int Number of milliseconds. - */ - static int toMS(uint32_t sampleRate, uint8_t channels, int samples) { return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); } - -private: - /** - * @brief Searches for the segment in the given array. - * @param val PCM value to search for. - * @param table Array of segment values. - * @param size Size of the segment array. - * @return short - */ - static short search(short val, short* table, short size); -}; - -#endif // __ANALOG_AUDIO_H__ diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a7d394f4c..ea4983dbb 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -4,7 +4,7 @@ # * GPLv2 Open Source. Use is subject to license terms. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * -# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL +# * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL # * # */ file(GLOB common_SRC @@ -39,6 +39,10 @@ file(GLOB common_SRC "src/common/nxdn/lc/*.cpp" "src/common/nxdn/lc/rcch/*.cpp" + # Analog Module + "src/common/analog/*.cpp" + "src/common/analog/data/*.cpp" + # Core "src/common/edac/*.cpp" "src/common/lookups/*.cpp" @@ -87,6 +91,10 @@ file(GLOB common_INCLUDE "src/common/nxdn/lc/*.h" "src/common/nxdn/lc/rcch/*.h" + # Analog Module + "src/common/analog/*.h" + "src/common/analog/data/*.h" + # Core "src/common/concurrent/*.h" "src/common/edac/*.h" diff --git a/src/common/Defines.h b/src/common/Defines.h index c58243c5f..bb9db1e26 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -118,7 +118,7 @@ typedef unsigned long long ulong64_t; #define VERSION_MAJOR "04" #define VERSION_MINOR "32" -#define VERSION_REV "H" +#define VERSION_REV "J" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR #define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" diff --git a/src/common/AnalogAudio.cpp b/src/common/analog/AnalogAudio.cpp similarity index 98% rename from src/common/AnalogAudio.cpp rename to src/common/analog/AnalogAudio.cpp index 429d04d2b..1d1dab26f 100644 --- a/src/common/AnalogAudio.cpp +++ b/src/common/analog/AnalogAudio.cpp @@ -8,10 +8,12 @@ * */ #include "Defines.h" -#include "AnalogAudio.h" +#include "analog/AnalogAudio.h" #include "Log.h" #include "Utils.h" +using namespace analog; + #include #include #include diff --git a/src/common/analog/AnalogAudio.h b/src/common/analog/AnalogAudio.h new file mode 100644 index 000000000..dfcbf955e --- /dev/null +++ b/src/common/analog/AnalogAudio.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file AnalogAudio.h + * @ingroup analog + * @file AnalogAudio.cpp + * @ingroup analog + */ +#if !defined(__ANALOG_AUDIO_H__) +#define __ANALOG_AUDIO_H__ + +#include "common/Defines.h" +#include "common/analog/AnalogDefines.h" + +namespace analog +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements aLaw and uLaw audio codecs, along with helper routines for analog audio. + * @ingroup analog + */ + class HOST_SW_API AnalogAudio { + public: + /** + * @brief Helper to convert PCM into G.711 aLaw. + * @param pcm PCM value. + * @return uint8_t aLaw value. + */ + static uint8_t encodeALaw(short pcm); + /** + * @brief Helper to convert G.711 aLaw into PCM. + * @param alaw aLaw value. + * @return short PCM value. + */ + static short decodeALaw(uint8_t alaw); + /** + * @brief Helper to convert PCM into G.711 MuLaw. + * @param pcm PCM value. + * @return uint8_t MuLaw value. + */ + static uint8_t encodeMuLaw(short pcm); + /** + * @brief Helper to convert G.711 MuLaw into PCM. + * @param ulaw MuLaw value. + * @return short PCM value. + */ + static short decodeMuLaw(uint8_t ulaw); + + /** + * @brief Helper to apply linear gain to PCM samples. + * @param pcm Buffer containing PCM samples. + * @param length Length of the PCM buffer in samples. + * @param gain Gain factor to apply to the PCM samples (1.0f = no change). + */ + static void gain(short *pcm, int length, float gain); + + /** + * @brief (ms) to sample count conversion. + * @param sampleRate Sample rate. + * @param channels Number of audio channels. + * @param ms Number of milliseconds. + * @returns int Number of samples. + */ + static int toSamples(uint32_t sampleRate, uint8_t channels, int ms) { return (int)(((long)ms) * sampleRate * channels / 1000); } + + /** + * @brief Sample count to (ms) conversion. + * @param sampleRate Sample rate. + * @param channels Number of audio channels. + * @param samples Number of samples. + * @returns int Number of milliseconds. + */ + static int toMS(uint32_t sampleRate, uint8_t channels, int samples) { return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); } + + private: + /** + * @brief Searches for the segment in the given array. + * @param val PCM value to search for. + * @param table Array of segment values. + * @param size Size of the segment array. + * @return short + */ + static short search(short val, short* table, short size); + }; +} // namespace analog + +#endif // __ANALOG_AUDIO_H__ diff --git a/src/common/analog/AnalogDefines.h b/src/common/analog/AnalogDefines.h new file mode 100644 index 000000000..85e7a4da6 --- /dev/null +++ b/src/common/analog/AnalogDefines.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup analog Digital Mobile Radio + * @brief Defines and implements aLaw and uLaw audio codecs, along with helper routines for analog audio. + * @ingroup common + * + * @file AnalogDefines.h + * @ingroup analog + */ +#if !defined(__ANALOG_DEFINES_H__) +#define __ANALOG_DEFINES_H__ + +#include "common/Defines.h" + +// Shorthand macro to analog::defines -- keeps source code that doesn't use "using" concise +#if !defined(ANODEF) +#define ANODEF analog::defines +#endif // DMRDEF +namespace analog +{ + namespace defines + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + /** + * @addtogroup analog + * @{ + */ + + const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //! Sample size for 20ms of 16-bit audio at 8kHz. + const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //! Sample size for 20ms of 16-bit audio at 8kHz in bytes. + /** @} */ + + /** @brief Audio Frame Type(s) */ + namespace AudioFrameType { + /** @brief Audio Frame Type(s) */ + enum E : uint8_t { + VOICE_START = 0x00U, //! Voice Start Frame + VOICE = 0x01U, //! Voice Continuation Frame + TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator + }; + } + } // namespace defines +} // namespace analog + +#endif // __ANALOG_DEFINES_H__ diff --git a/src/common/analog/data/NetData.cpp b/src/common/analog/data/NetData.cpp new file mode 100644 index 000000000..f6a509fe7 --- /dev/null +++ b/src/common/analog/data/NetData.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "analog/AnalogAudio.h" +#include "analog/data/NetData.h" + +using namespace analog; +using namespace analog::defines; +using namespace analog::data; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the NetData class. */ + +NetData::NetData(const NetData& data) : + m_srcId(data.m_srcId), + m_dstId(data.m_dstId), + m_control(data.m_control), + m_seqNo(data.m_seqNo), + m_group(true), + m_frameType(data.m_frameType), + m_audio(nullptr) +{ + m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES]; + ::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES); +} + +/* Initializes a new instance of the NetData class. */ + +NetData::NetData() : + m_srcId(0U), + m_dstId(0U), + m_control(0U), + m_seqNo(0U), + m_group(true), + m_frameType(AudioFrameType::TERMINATOR), + m_audio(nullptr) +{ + m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES]; +} + +/* Finalizes a instance of the NetData class. */ + +NetData::~NetData() +{ + delete[] m_audio; +} + +/* Equals operator. */ + +NetData& NetData::operator=(const NetData& data) +{ + if (this != &data) { + ::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES); + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + m_control = data.m_control; + m_frameType = data.m_frameType; + m_seqNo = data.m_seqNo; + m_group = data.m_group; + } + + return *this; +} + +/* Sets audio data. */ + +void NetData::setAudio(const uint8_t* buffer) +{ + assert(buffer != nullptr); + + ::memcpy(m_audio, buffer, AUDIO_SAMPLES_LENGTH_BYTES); +} + +/* Gets audio data. */ + +uint32_t NetData::getAudio(uint8_t* buffer) const +{ + assert(buffer != nullptr); + + ::memcpy(buffer, m_audio, AUDIO_SAMPLES_LENGTH_BYTES); + + return AUDIO_SAMPLES_LENGTH_BYTES; +} diff --git a/src/common/analog/data/NetData.h b/src/common/analog/data/NetData.h new file mode 100644 index 000000000..530451e18 --- /dev/null +++ b/src/common/analog/data/NetData.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file NetData.h + * @ingroup analog + * @file NetData.cpp + * @ingroup analog + */ +#if !defined(__ANALOG_DATA__NET_DATA_H__) +#define __ANALOG_DATA__NET_DATA_H__ + +#include "common/Defines.h" +#include "common/analog/AnalogDefines.h" + +namespace analog +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents network analog data. When setting audio data, it is expected to be 20ms of 16-bit + * audio at 8kHz, or 320 bytes in length. + * @ingroup analog + */ + class HOST_SW_API NetData { + public: + /** + * @brief Initializes a new instance of the NetData class. + * @param data Instance of NetData class to copy from. + */ + NetData(const NetData& data); + /** + * @brief Initializes a new instance of the NetData class. + */ + NetData(); + /** + * @brief Finalizes a instance of the NetData class. + */ + ~NetData(); + + /** + * @brief Equals operator. + * @param data Instance of NetData class to copy from. + */ + NetData& operator=(const NetData& data); + + /** + * @brief Sets audio data. + * @param[in] buffer Audio data buffer. + */ + void setAudio(const uint8_t* buffer); + /** + * @brief Gets audio data. + * @param[out] buffer Audio data buffer. + */ + uint32_t getAudio(uint8_t* buffer) const; + + public: + /** + * @brief Source ID. + */ + DECLARE_PROPERTY(uint32_t, srcId, SrcId); + /** + * @brief Destination ID. + */ + DECLARE_PROPERTY(uint32_t, dstId, DstId); + + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, control, Control); + + /** + * @brief Sequence number. + */ + DECLARE_PROPERTY(uint8_t, seqNo, SeqNo); + + /** + * @brief Flag indicatin if this group audio or individual audio. + */ + DECLARE_PROPERTY(bool, group, Group); + + /** + * @brief Audio frame type. + */ + DECLARE_PROPERTY(defines::AudioFrameType::E, frameType, FrameType); + + private: + uint8_t* m_audio; + }; + } // namespace data +} // namespace analog + +#endif // __ANALOG_DATA__NET_DATA_H__ diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index 660021a22..e0cf509a7 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -10,6 +10,7 @@ * */ #include "Defines.h" +#include "common/analog/AnalogDefines.h" #include "common/dmr/DMRDefines.h" #include "common/p25/P25Defines.h" #include "common/nxdn/NXDNDefines.h" @@ -53,10 +54,12 @@ BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, b m_rxDMRData(NET_RING_BUF_SIZE, "DMR Net Buffer"), m_rxP25Data(NET_RING_BUF_SIZE, "P25 Net Buffer"), m_rxNXDNData(NET_RING_BUF_SIZE, "NXDN Net Buffer"), + m_rxAnalogData(NET_RING_BUF_SIZE, "Analog Net Buffer"), m_random(), m_dmrStreamId(nullptr), m_p25StreamId(0U), m_nxdnStreamId(0U), + m_analogStreamId(0U), m_pktSeq(0U), m_audio() { @@ -74,6 +77,7 @@ BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, b m_dmrStreamId[1U] = createStreamId(); m_p25StreamId = createStreamId(); m_nxdnStreamId = createStreamId(); + m_analogStreamId = createStreamId(); } /* Finalizes a instance of the BaseNetwork class. */ @@ -346,6 +350,15 @@ void BaseNetwork::resetNXDN() m_rxNXDNData.clear(); } +/* Resets the analog ring buffer. */ + +void BaseNetwork::resetAnalog() +{ + m_analogStreamId = createStreamId(); + m_pktSeq = 0U; + m_rxAnalogData.clear(); +} + /* Gets the current DMR stream ID. */ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const @@ -502,6 +515,11 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength) return nullptr; } + if (length == 254U) { + m_rxP25Data.get(&length, 1U); // read the next byte for the actual length + length += 254U; // a packet length of 254 is a special case for P25 frames, so we need to add the 254 to the length + } + UInt8Array buffer; frameLength = length; buffer = std::unique_ptr(new uint8_t[length]); @@ -653,6 +671,8 @@ bool BaseNetwork::hasP25Data() const return true; } +/* Helper to validate a P25 network frame length. */ + bool BaseNetwork::validateP25FrameLength(uint8_t& frameLength, uint32_t len, const P25DEF::DUID::E duid) { using namespace p25::defines; @@ -825,6 +845,91 @@ bool BaseNetwork::hasNXDNData() const return true; } +/* Reads analog raw frame data from the analog ring buffer. */ + +UInt8Array BaseNetwork::readAnalog(bool& ret, uint32_t& frameLength) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return nullptr; + + ret = true; + if (m_rxAnalogData.isEmpty()) { + ret = false; + return nullptr; + } + + uint8_t length = 0U; + m_rxAnalogData.get(&length, 1U); + if (length == 0U) { + ret = false; + return nullptr; + } + + if (length < 254U) { + // if the length is less than 254, the analog packet is malformed, analog packets should never be less than 254 bytes + LogError(LOG_NET, "malformed analog packet, length < 254 (%u), shouldn't happen", length); + ret = false; + return nullptr; + } + + if (length == 254U) { + m_rxAnalogData.get(&length, 1U); // read the next byte for the actual length + length += 254U; // a packet length of 254 is a special case for P25 frames, so we need to add the 254 to the length + } + + UInt8Array buffer; + frameLength = length; + buffer = std::unique_ptr(new uint8_t[length]); + ::memset(buffer.get(), 0x00U, length); + m_rxAnalogData.get(buffer.get(), length); + + return buffer; +} + +/* Writes analog frame data to the network. */ + +bool BaseNetwork::writeAnalog(const analog::data::NetData& data, bool noSequence) +{ + using namespace analog::defines; + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + AudioFrameType::E frameType = data.getFrameType(); + + bool resetSeq = false; + if (m_analogStreamId == 0U) { + resetSeq = true; + m_analogStreamId = createStreamId(); + } + + uint32_t messageLength = 0U; + UInt8Array message = createAnalog_Message(messageLength, m_analogStreamId, data); + if (message == nullptr) { + return false; + } + + uint16_t seq = pktSeq(resetSeq); + if (frameType == AudioFrameType::TERMINATOR) { + seq = RTP_END_OF_CALL_SEQ; + } + + if (noSequence) { + seq = RTP_END_OF_CALL_SEQ; + } + + return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, message.get(), messageLength, seq, m_analogStreamId); +} + +/* Helper to test if the analog ring buffer has data. */ + +bool BaseNetwork::hasAnalogData() const +{ + if (m_rxAnalogData.isEmpty()) + return false; + + return true; +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- @@ -1245,8 +1350,8 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC { assert(data != nullptr); - uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; - ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + uint8_t* buffer = new uint8_t[NXDN_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, NXDN_PACKET_LENGTH + PACKET_PAD); // construct NXDN message header ::memcpy(buffer + 0U, TAG_NXDN_DATA, 4U); @@ -1272,8 +1377,43 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, NXDN", buffer, (count + PACKET_PAD)); + Utils::dump(1U, "Network Message, NXDN", buffer, (NXDN_PACKET_LENGTH + PACKET_PAD)); - length = (count + PACKET_PAD); + length = (NXDN_PACKET_LENGTH + PACKET_PAD); + return UInt8Array(buffer); +} + +/* Creates an analog frame message. */ + +UInt8Array BaseNetwork::createAnalog_Message(uint32_t& length, const uint32_t streamId, const analog::data::NetData& data) +{ + using namespace analog::defines; + uint8_t* buffer = new uint8_t[ANALOG_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, ANALOG_PACKET_LENGTH + PACKET_PAD); + + // construct analog message header + ::memcpy(buffer + 0U, TAG_ANALOG_DATA, 4U); + + uint32_t srcId = data.getSrcId(); // Source Address + SET_UINT24(srcId, buffer, 5U); + + uint32_t dstId = data.getDstId(); // Target Address + SET_UINT24(dstId, buffer, 8U); + + buffer[14U] = data.getControl(); // Control Bits + + AudioFrameType::E frameType = data.getFrameType(); + buffer[15U] = (uint8_t)frameType; // Audio Frame Type + buffer[15U] |= data.getGroup() ? 0x00U : 0x40U; // Group + + buffer[4U] = data.getSeqNo(); // Sequence Number + + // pack raw audio message bytes + data.getAudio(buffer + 20U); + + if (m_debug) + Utils::dump(1U, "Network Message, Analog", buffer, (ANALOG_PACKET_LENGTH + PACKET_PAD)); + + length = (ANALOG_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); } diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 7d5492a64..7f25d654e 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -25,6 +25,7 @@ #define __BASE_NETWORK_H__ #include "common/Defines.h" +#include "common/analog/data/NetData.h" #include "common/dmr/data/NetData.h" #include "common/p25/data/DataHeader.h" #include "common/p25/data/LowSpeedData.h" @@ -89,6 +90,8 @@ namespace network const uint32_t P25_LDU2_PACKET_LENGTH = 181U; // 24 byte header + DFSI data + 1 byte frame type const uint32_t P25_TSDU_PACKET_LENGTH = 69U; // 24 byte header + TSDU data const uint32_t P25_TDULC_PACKET_LENGTH = 78U; // 24 byte header + TDULC data + const uint32_t NXDN_PACKET_LENGTH = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer + const uint32_t ANALOG_PACKET_LENGTH = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer /** * @brief Network Peer Connection Status @@ -298,6 +301,10 @@ namespace network * @brief Resets the NXDN ring buffer. */ virtual void resetNXDN(); + /** + * @brief Resets the analog ring buffer. + */ + virtual void resetAnalog(); /** * @brief Gets the current DMR stream ID. @@ -315,6 +322,11 @@ namespace network * @return uint32_t Stream ID. */ uint32_t getNXDNStreamId() const { return m_nxdnStreamId; } + /** + * @brief Gets the current analog stream ID. + * @return uint32_t Stream ID. + */ + uint32_t getAnalogtreamId() const { return m_analogStreamId; } /** * @brief Helper to send a data message to the master. @@ -453,6 +465,28 @@ namespace network */ bool hasNXDNData() const; + // Analog Audio + /** + * @brief Reads analog raw frame data from the analog ring buffer. + * @param[out] ret Flag indicating whether or not data was received. + * @param[out] frameLength Length in bytes of received frame. + * @returns UInt8Array Buffer containing received frame. + */ + virtual UInt8Array readAnalog(bool& ret, uint32_t& frameLength); + /** + * @brief Writes analog frame data to the network. + * @param[in] data Instance of the analog::data::NetData class containing the analog message. + * @param noSequence Flag indicating the message should be sent with no RTP sequence (65535). + * @returns bool True, if message was sent, otherwise false. + */ + virtual bool writeAnalog(const analog::data::NetData& data, bool noSequence = false); + + /** + * @brief Helper to test if the analog ring buffer has data. + * @returns bool True, if the network analog ring buffer has data, otherwise false. + */ + bool hasAnalogData() const; + public: /** * @brief Gets the peer ID of the network. @@ -500,12 +534,14 @@ namespace network RingBuffer m_rxDMRData; RingBuffer m_rxP25Data; RingBuffer m_rxNXDNData; + RingBuffer m_rxAnalogData; std::mt19937 m_random; uint32_t* m_dmrStreamId; uint32_t m_p25StreamId; uint32_t m_nxdnStreamId; + uint32_t m_analogStreamId; /** * @brief Helper to update the RTP packet sequence. @@ -540,7 +576,7 @@ namespace network * | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * - * The data starting at offset 20 for 33 bytes if the raw DMR frame. + * The data starting at offset 20 for 33 bytes of the raw DMR frame. * * DMR frame message has 2 trailing bytes: * @@ -711,7 +747,7 @@ namespace network */ UInt8Array createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, const uint8_t currentBlock, const uint8_t* data, const uint32_t len); - + /** * @brief Creates an NXDN frame message. * \code{.unparsed} @@ -743,7 +779,36 @@ namespace network * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createNXDN_Message(uint32_t& length, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); - + + /** + * @brief Creates an analog frame message. + * \code{.unparsed} + * Below is the representation of the data layout for the analog frame + * message header. The header is 20 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (ANOD) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Seq No. | Source ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination ID | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | Control Flags | R | Data Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The data starting at offset 20 for 320 bytes of the raw analog frame. + * \endcode + * @param[out] length Length of network message buffer. + * @param streamId Stream ID. + * @param data Instance of the analog::data::Data class containing the analog message. + * @returns UInt8Array Buffer containing the built network message. + */ + UInt8Array createAnalog_Message(uint32_t& length, const uint32_t streamId, const analog::data::NetData& data); + private: uint16_t m_pktSeq; diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index d1a112804..8bff9828d 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -34,7 +34,8 @@ using namespace network; /* Initializes a new instance of the Network class. */ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, + bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : BaseNetwork(peerId, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, localPort), m_pktLastSeq(0U), m_address(address), @@ -44,6 +45,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_dmrEnabled(dmr), m_p25Enabled(p25), m_nxdnEnabled(nxdn), + m_analogEnabled(analog), m_updateLookup(updateLookup), m_saveLookup(saveLookup), m_ridLookup(nullptr), @@ -63,6 +65,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_dmrInCallCallback(nullptr), m_p25InCallCallback(nullptr), m_nxdnInCallCallback(nullptr), + m_analogInCallCallback(nullptr), m_keyRespCallback(nullptr) { assert(!address.empty()); @@ -76,6 +79,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_rxDMRStreamId[1U] = 0U; m_rxP25StreamId = 0U; m_rxNXDNStreamId = 0U; + m_rxAnalogStreamId = 0U; m_metadata = new PeerMetadata(); } @@ -120,6 +124,14 @@ void Network::resetNXDN() m_rxNXDNStreamId = 0U; } +/* Resets the analog ring buffer. */ + +void Network::resetAnalog() +{ + BaseNetwork::resetAnalog(); + m_rxAnalogStreamId = 0U; +} + /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) @@ -315,7 +327,7 @@ void Network::clock(uint32_t ms) if (m_debug) Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length); - if (length > 255) + if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD)) LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); uint8_t len = length; @@ -367,11 +379,20 @@ void Network::clock(uint32_t ms) if (m_debug) Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length); - if (length > 255) + if (length > 512) LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + // P25 frames can be up to 512 bytes, but we need to handle the case where the frame is larger than 255 bytes uint8_t len = length; - m_rxP25Data.addData(&len, 1U); + if (length > 254) { + len = 254U; + m_rxP25Data.addData(&len, 1U); + len = length - 254U; + m_rxP25Data.addData(&len, 1U); + } else { + m_rxP25Data.addData(&len, 1U); + } + m_rxP25Data.addData(buffer.get(), len); } } @@ -419,7 +440,7 @@ void Network::clock(uint32_t ms) if (m_debug) Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length); - if (length > 255) + if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD)) LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); uint8_t len = length; @@ -429,6 +450,66 @@ void Network::clock(uint32_t ms) } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated Analog data frame + { + if (m_enabled && m_analogEnabled) { + if (m_debug) { + LogDebug(LOG_NET, "Analog, peer = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, length, rtpHeader.getSequence(), streamId); + } + + if (m_promiscuousPeer) { + m_rxAnalogStreamId = streamId; + m_pktLastSeq = m_pktSeq; + } + else { + if (m_rxAnalogStreamId == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxAnalogStreamId = 0U; + } + else { + m_rxAnalogStreamId = streamId; + } + + m_pktLastSeq = m_pktSeq; + } + else { + if (m_rxAnalogStreamId == streamId) { + if (m_pktSeq != 0U && m_pktLastSeq != 0U) { + if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { + LogWarning(LOG_NET, "Analog Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } + + m_pktLastSeq = m_pktSeq; + } + + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxAnalogStreamId = 0U; + } + } + } + + if (m_debug) + Utils::dump(1U, "[Network::clock()] Network Received, Analog", buffer.get(), length); + if (length < (int)ANALOG_PACKET_LENGTH) { + LogError(LOG_NET, "Analog Stream %u, frame too short? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + } else { + if (length > 512) + LogError(LOG_NET, "Analog Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + + // Analog frames are larger then 254 bytes, but we need to handle the case where the frame is larger than 255 bytes + uint8_t len = 254U; + m_rxAnalogData.addData(&len, 1U); + len = length - 254U; + m_rxAnalogData.addData(&len, 1U); + + m_rxAnalogData.addData(buffer.get(), len); + } + } + } + break; + default: Utils::dump("unknown protocol opcode from the master", buffer.get(), length); break; @@ -628,6 +709,19 @@ void Network::clock(uint32_t ms) } } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Analog In-Call Control + { + if (m_enabled && m_nxdnEnabled) { + NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; + uint32_t dstId = GET_UINT24(buffer, 11U); + + // fire off analog in-call callback if we have one + if (m_analogInCallCallback != nullptr) { + m_analogInCallCallback(command, dstId); + } + } + } + break; default: Utils::dump("unknown incall control opcode from the master", buffer.get(), length); diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 5ce70a7a9..3b34670c6 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -92,6 +92,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -99,7 +100,8 @@ namespace network * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, + bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Finalizes a instance of the Network class. */ @@ -118,7 +120,11 @@ namespace network * @brief Resets the NXDN ring buffer. */ void resetNXDN() override; - + /** + * @brief Resets the analog ring buffer. + */ + void resetAnalog() override; + /** * @brief Sets the instances of the Radio ID and Talkgroup ID lookup tables. * @param ridLookup Radio ID Lookup Table Instance @@ -213,6 +219,11 @@ namespace network * @param callback */ void setNXDNICCCallback(std::function&& callback) { m_nxdnInCallCallback = callback; } + /** + * @brief Helper to set the analog In-Call Control callback. + * @param callback + */ + void setAnalogICCCallback(std::function&& callback) { m_analogInCallCallback = callback; } /** * @brief Helper to set the enc. key response callback. @@ -237,6 +248,7 @@ namespace network bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_analogEnabled; bool m_updateLookup; bool m_saveLookup; @@ -252,6 +264,7 @@ namespace network uint32_t* m_rxDMRStreamId; uint32_t m_rxP25StreamId; uint32_t m_rxNXDNStreamId; + uint32_t m_rxAnalogStreamId; uint16_t m_pktSeq; uint32_t m_loginStreamId; @@ -300,6 +313,11 @@ namespace network * (This is called once the master sends a In-Call Control request.) */ std::function m_nxdnInCallCallback; + /** + * @brief Analog In-Call Control Function Callback. + * (This is called once the master sends a In-Call Control request.) + */ + std::function m_analogInCallCallback; /** * @brief Encryption Key Response Function Callback. diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index c54f26308..4d32f8967 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -15,6 +15,7 @@ #include "network/callhandler/TagDMRData.h" #include "network/callhandler/TagP25Data.h" #include "network/callhandler/TagNXDNData.h" +#include "network/callhandler/TagAnalogData.h" #include "ActivityLog.h" #include "HostFNE.h" #include "FNEMain.h" @@ -68,6 +69,7 @@ HostFNE::HostFNE(const std::string& confFile) : m_dmrEnabled(false), m_p25Enabled(false), m_nxdnEnabled(false), + m_analogEnabled(false), m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), @@ -578,6 +580,7 @@ bool HostFNE::createMasterNetwork() m_dmrEnabled = masterConf["allowDMRTraffic"].as(true); m_p25Enabled = masterConf["allowP25Traffic"].as(true); m_nxdnEnabled = masterConf["allowNXDNTraffic"].as(true); + m_analogEnabled = masterConf["allowAnalogTraffic"].as(false); uint32_t parrotDelay = masterConf["parrotDelay"].as(2500U); if (m_pingTime * 1000U < parrotDelay) { @@ -593,6 +596,7 @@ bool HostFNE::createMasterNetwork() LogInfo(" Allow DMR Traffic: %s", m_dmrEnabled ? "yes" : "no"); LogInfo(" Allow P25 Traffic: %s", m_p25Enabled ? "yes" : "no"); LogInfo(" Allow NXDN Traffic: %s", m_nxdnEnabled ? "yes" : "no"); + LogInfo(" Allow Analog Traffic: %s", m_analogEnabled ? "yes" : "no"); LogInfo(" Parrot Repeat Delay: %u ms", parrotDelay); LogInfo(" Parrot Grant Demand: %s", parrotGrantDemand ? "yes" : "no"); @@ -611,7 +615,7 @@ bool HostFNE::createMasterNetwork() } // initialize networking - m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, + m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime, workerCnt); m_network->setOptions(masterConf, true); @@ -828,7 +832,8 @@ bool HostFNE::createPeerNetworks() } // initialize networking - network::PeerNetwork* network = new PeerNetwork(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, true, true, m_allowActivityTransfer, m_allowDiagnosticTransfer, false, false); + network::PeerNetwork* network = new PeerNetwork(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, true, true, + m_allowActivityTransfer, m_allowDiagnosticTransfer, false, false); network->setMetadata(identity, rxFrequency, txFrequency, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); network->setLookups(m_ridLookup, m_tidLookup); network->setPeerLookups(m_peerListLookup); @@ -840,6 +845,7 @@ bool HostFNE::createPeerNetworks() network->setDMRCallback(std::bind(&HostFNE::processPeerDMR, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); network->setP25Callback(std::bind(&HostFNE::processPeerP25, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); network->setNXDNCallback(std::bind(&HostFNE::processPeerNXDN, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setAnalogCallback(std::bind(&HostFNE::processPeerAnalog, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); /* ** Block Traffic To Peers @@ -1111,3 +1117,25 @@ void HostFNE::processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* m_network->nxdnTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } } + +/* Processes analog peer network traffic. */ + +void HostFNE::processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) +{ + if (peerNetwork == nullptr) + return; // this shouldn't happen... + + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; + + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; + + // process analog data + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->analogTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); + } +} diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index 052753f55..ef675d2c2 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -43,6 +43,7 @@ namespace network { namespace callhandler { namespace packetdata { class HOST_SW namespace network { namespace callhandler { class HOST_SW_API TagP25Data; } } namespace network { namespace callhandler { namespace packetdata { class HOST_SW_API P25PacketData; } } } namespace network { namespace callhandler { class HOST_SW_API TagNXDNData; } } +namespace network { namespace callhandler { class HOST_SW_API TagAnalogData; } } // --------------------------------------------------------------------------- // Class Declaration @@ -89,6 +90,7 @@ class HOST_SW_API HostFNE { friend class network::callhandler::TagP25Data; friend class network::callhandler::packetdata::P25PacketData; friend class network::callhandler::TagNXDNData; + friend class network::callhandler::TagAnalogData; network::FNENetwork* m_network; network::DiagNetwork* m_diagNetwork; @@ -101,6 +103,7 @@ class HOST_SW_API HostFNE { bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_analogEnabled; lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; @@ -207,6 +210,16 @@ class HOST_SW_API HostFNE { */ void processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Processes analog peer network traffic. + * @param data Buffer containing analog data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. + */ + void processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); }; #endif // __HOST_FNE_H__ diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index ede136b41..2e3158f1b 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -18,6 +18,7 @@ #include "network/callhandler/TagDMRData.h" #include "network/callhandler/TagP25Data.h" #include "network/callhandler/TagNXDNData.h" +#include "network/callhandler/TagAnalogData.h" #include "fne/ActivityLog.h" #include "HostFNE.h" @@ -56,12 +57,13 @@ std::timed_mutex FNENetwork::m_keyQueueMutex; /* Initializes a new instance of the FNENetwork class. */ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, - bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool parrotGrantDemand, + bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, bool analog, uint32_t parrotDelay, bool parrotGrantDemand, bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt) : BaseNetwork(peerId, true, debug, true, true, allowActivityTransfer, allowDiagnosticTransfer), m_tagDMR(nullptr), m_tagP25(nullptr), m_tagNXDN(nullptr), + m_tagAnalog(nullptr), m_host(host), m_address(address), m_port(port), @@ -69,6 +71,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_dmrEnabled(dmr), m_p25Enabled(p25), m_nxdnEnabled(nxdn), + m_analogEnabled(analog), m_parrotDelay(parrotDelay), m_parrotDelayTimer(1000U, 0U, parrotDelay), m_parrotGrantDemand(parrotGrantDemand), @@ -124,6 +127,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_tagDMR = new TagDMRData(this, debug); m_tagP25 = new TagP25Data(this, debug); m_tagNXDN = new TagNXDNData(this, debug); + m_tagAnalog = new TagAnalogData(this, debug); } /* Finalizes a instance of the FNENetwork class. */ @@ -133,6 +137,7 @@ FNENetwork::~FNENetwork() delete m_tagDMR; delete m_tagP25; delete m_tagNXDN; + delete m_tagAnalog; } /* Helper to set configuration options. */ @@ -450,9 +455,14 @@ void FNENetwork::clock(uint32_t ms) if (m_tagNXDN->hasParrotFrames()) { m_tagNXDN->playbackParrot(); } + + // if the analog handler has parrot frames to playback, playback a frame + if (m_tagAnalog->hasParrotFrames()) { + m_tagAnalog->playbackParrot(); + } } - if (!m_tagDMR->hasParrotFrames() && !m_tagP25->hasParrotFrames() && !m_tagNXDN->hasParrotFrames() && + if (!m_tagDMR->hasParrotFrames() && !m_tagP25->hasParrotFrames() && !m_tagNXDN->hasParrotFrames() && !m_tagAnalog->hasParrotFrames() && m_parrotDelayTimer.isRunning() && m_parrotDelayTimer.hasExpired()) { m_parrotDelayTimer.stop(); } @@ -691,6 +701,32 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_analogEnabled) { + if (network->m_tagAnalog != nullptr) { + network->m_tagAnalog->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); + } + } else { + network->writePeerNAK(peerId, streamId, TAG_ANALOG_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + } + } + else { + network->writePeerNAK(peerId, TAG_ANALOG_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } + } + break; + default: Utils::dump("unknown protocol opcode from peer", req->buffer, req->length); break; diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 74f60e1e4..b5da2adff 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -55,6 +55,7 @@ namespace network { namespace callhandler { namespace packetdata { class HOST_SW namespace network { namespace callhandler { class HOST_SW_API TagP25Data; } } namespace network { namespace callhandler { namespace packetdata { class HOST_SW_API P25PacketData; } } } namespace network { namespace callhandler { class HOST_SW_API TagNXDNData; } } +namespace network { namespace callhandler { class HOST_SW_API TagAnalogData; } } namespace network { @@ -422,6 +423,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param parrotDelay Delay for end of call to parrot TG playback. * @param parrotGrantDemand Flag indicating whether a parrot TG will generate a grant demand. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -431,7 +433,7 @@ namespace network * @param workerCnt Number of worker threads. */ FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, - bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool parrotGrantDemand, + bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, bool analog, uint32_t parrotDelay, bool parrotGrantDemand, bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt); /** * @brief Finalizes a instance of the FNENetwork class. @@ -466,6 +468,11 @@ namespace network * @returns callhandler::TagNXDNData* Instance of the TagNXDNData call handler. */ callhandler::TagNXDNData* nxdnTrafficHandler() const { return m_tagNXDN; } + /** + * @brief Gets the instance of the analog call handler. + * @returns callhandler::TagAnalogData* Instance of the TagAnalogData call handler. + */ + callhandler::TagAnalogData* analogTrafficHandler() const { return m_tagAnalog; } /** * @brief Sets the instances of the Radio ID, Talkgroup ID Peer List, and Crypto lookup tables. @@ -530,6 +537,8 @@ namespace network callhandler::TagP25Data* m_tagP25; friend class callhandler::TagNXDNData; callhandler::TagNXDNData* m_tagNXDN; + friend class callhandler::TagAnalogData; + callhandler::TagAnalogData* m_tagAnalog; friend class ::RESTAPI; HostFNE* m_host; @@ -542,6 +551,7 @@ namespace network bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_analogEnabled; uint32_t m_parrotDelay; Timer m_parrotDelayTimer; diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index fc26538c0..84a282e0a 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -38,10 +38,14 @@ const uint64_t PACKET_LATE_TIME = 200U; // 200ms /* Initializes a new instance of the PeerNetwork class. */ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, analog, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), m_attachedKeyRSPHandler(false), m_blockTrafficToTable(), + m_dmrCallback(nullptr), + m_p25Callback(nullptr), + m_nxdnCallback(nullptr), + m_analogCallback(nullptr), m_pidLookup(nullptr), m_peerLink(false), m_peerLinkSavesACL(false), @@ -501,6 +505,13 @@ void PeerNetwork::taskNetworkRx(PeerPacketRequest* req) } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + { + if (network->m_analogCallback != nullptr) + network->m_analogCallback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + default: Utils::dump("unknown protocol opcode from the master", req->buffer, req->length); break; diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 3b9510ad6..7addad323 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -72,6 +72,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -79,7 +80,7 @@ namespace network * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Sets the instances of the Peer List lookup tables. @@ -113,6 +114,11 @@ namespace network * @param callback */ void setNXDNCallback(std::function&& callback) { m_nxdnCallback = callback; } + /** + * @brief Helper to set the analog protocol callback. + * @param callback + */ + void setAnalogCallback(std::function&& callback) { m_analogCallback = callback; } /** * @brief Gets the blocked traffic peer ID table. @@ -173,6 +179,11 @@ namespace network * (This is called when the master sends a NXDN packet.) */ std::function m_nxdnCallback; + /** + * @brief Analog Protocol Callback. + * (This is called when the master sends a analog packet.) + */ + std::function m_analogCallback; /** * @brief User overrideable handler that allows user code to process network packets not handled by this class. diff --git a/src/fne/network/callhandler/TagAnalogData.cpp b/src/fne/network/callhandler/TagAnalogData.cpp new file mode 100644 index 000000000..c809ab17e --- /dev/null +++ b/src/fne/network/callhandler/TagAnalogData.cpp @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/analog/AnalogDefines.h" +#include "common/analog/data/NetData.h" +#include "common/Clock.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "network/FNENetwork.h" +#include "network/callhandler/TagAnalogData.h" +#include "HostFNE.h" + +using namespace system_clock; +using namespace network; +using namespace network::callhandler; +using namespace network::callhandler::packetdata; +using namespace analog; +using namespace analog::defines; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t CALL_COLL_TIMEOUT = 10U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the TagAnalogData class. */ + +TagAnalogData::TagAnalogData(FNENetwork* network, bool debug) : + m_network(network), + m_parrotFrames(), + m_parrotFramesReady(false), + m_status(), + m_debug(debug) +{ + assert(network != nullptr); +} + +/* Finalizes a instance of the TagAnalogData class. */ + +TagAnalogData::~TagAnalogData() = default; + +/* Process a data frame from the network. */ + +bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) +{ + hrc::hrc_t pktTime = hrc::now(); + + DECLARE_UINT8_ARRAY(buffer, len); + ::memcpy(buffer, data, len); + + uint8_t seqNo = data[4U]; + + uint32_t srcId = GET_UINT24(data, 5U); + uint32_t dstId = GET_UINT24(data, 8U); + + bool individual = (data[15] & 0x40U) == 0x40U; + + AudioFrameType::E frameType = (AudioFrameType::E)(data[15U] & 0x0FU); + + data::NetData analogData; + analogData.setSeqNo(seqNo); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setFrameType(frameType); + + analogData.setAudio(data + 20U); + + uint8_t frame[AUDIO_SAMPLES_LENGTH_BYTES]; + analogData.getAudio(frame); + + // perform TGID route rewrites if configured + routeRewrite(buffer, peerId, dstId, false); + dstId = GET_UINT24(buffer, 8U); + + // is the stream valid? + if (validate(peerId, analogData, streamId)) { + // is this peer ignored? + if (!isPeerPermitted(peerId, analogData, streamId, external)) { + return false; + } + + // is this the end of the call stream? + if (frameType == AudioFrameType::TERMINATOR) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + return false; + } + + RxStatus status = m_status[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status[dstId].reset(); + + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + if (m_parrotFrames.size() > 0) { + m_parrotFramesReady = true; + LogMessage(LOG_NET, "Analog, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); + m_network->m_parrotDelayTimer.start(); + } + } + + LogMessage(LOG_NET, "Analog, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + + // report call event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_event") + .tag("peerId", std::to_string(peerId)) + .tag("mode", "Analog") + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(srcId)) + .tag("dstId", std::to_string(dstId)) + .field("duration", duration) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + m_network->eraseStreamPktSeq(peerId, streamId); + m_network->m_callInProgress = false; + } + } + + // is this a new call stream? + if (frameType == AudioFrameType::VOICE_START) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + return false; + } + + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + RxStatus status = it->second; + if (streamId != status.streamId) { + if (status.srcId != 0U && status.srcId != srcId) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { + LogWarning(LOG_NET, "Analog, Call Collision, lasted more then %us with no further updates, forcibly ending call"); + m_status[dstId].reset(); + m_network->m_callInProgress = false; + } + + LogWarning(LOG_NET, "Analog, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + return false; + } + } + } + else { + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + m_parrotFramesReady = false; + if (m_parrotFrames.size() > 0) { + for (auto& pkt : m_parrotFrames) { + if (pkt.buffer != nullptr) { + delete[] pkt.buffer; + } + } + m_parrotFrames.clear(); + } + } + + // this is a new call stream + // bryanb: this could be problematic and is naive, if a dstId appears on both slots (which shouldn't happen) + m_status[dstId].callStartTime = pktTime; + m_status[dstId].srcId = srcId; + m_status[dstId].dstId = dstId; + m_status[dstId].streamId = streamId; + m_status[dstId].peerId = peerId; + m_status[dstId].activeCall = true; + + LogMessage(LOG_NET, "Analog, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + + m_network->m_callInProgress = true; + } + } + + // is this a parrot talkgroup? + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + uint8_t* copy = new uint8_t[len]; + ::memcpy(copy, buffer, len); + + ParrotFrame parrotFrame = ParrotFrame(); + parrotFrame.buffer = copy; + parrotFrame.bufferLen = len; + + parrotFrame.pktSeq = pktSeq; + parrotFrame.streamId = streamId; + parrotFrame.peerId = peerId; + + parrotFrame.srcId = srcId; + parrotFrame.dstId = dstId; + + m_parrotFrames.push_back(parrotFrame); + + if (m_network->m_parrotOnlyOriginating) { + return true; // end here because parrot calls should never repeat anywhere + } + } + + m_status[dstId].lastPacket = hrc::now(); + + // repeat traffic to the connected peers + if (m_network->m_peers.size() > 0U) { + uint32_t i = 0U; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + + // is this peer ignored? + if (!isPeerPermitted(peer.first, analogData, streamId)) { + continue; + } + + // every 5 peers flush the queue + if (i % 5U == 0U) { + m_network->m_frameQueue->flushQueue(); + } + + DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID route rewrites if configured + routeRewrite(outboundPeerBuffer, peer.first, dstId); + + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, true); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, peer.first, seqNo, srcId, dstId, len, pktSeq, streamId, external); + } + + if (!m_network->m_callInProgress) + m_network->m_callInProgress = true; + i++; + } + } + m_network->m_frameQueue->flushQueue(); + } + + // repeat traffic to external peers + if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { + for (auto peer : m_network->m_host->m_peerNetworks) { + uint32_t dstPeerId = peer.second->getPeerId(); + + // don't try to repeat traffic to the source peer...if this traffic + // is coming from a external peer + if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + + // is this peer ignored? + if (!isPeerPermitted(dstPeerId, analogData, streamId, true)) { + continue; + } + + // check if the source peer is blocked from sending to this peer + if (peer.second->checkBlockedPeer(peerId)) { + continue; + } + + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { + continue; + } + + DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID route rewrites if configured + routeRewrite(outboundPeerBuffer, dstPeerId, dstId); + + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, dstPeerId, seqNo, srcId, dstId, len, pktSeq, streamId, external); + } + + if (!m_network->m_callInProgress) + m_network->m_callInProgress = true; + } + } + } + + return true; + } + + return false; +} + +/* Helper to playback a parrot frame to the network. */ + +void TagAnalogData::playbackParrot() +{ + if (m_parrotFrames.size() == 0) { + m_parrotFramesReady = false; + return; + } + + auto& pkt = m_parrotFrames[0]; + if (pkt.buffer != nullptr) { + if (m_network->m_parrotOnlyOriginating) { + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); + } + } + else { + // repeat traffic to the connected peers + for (auto peer : m_network->m_peers) { + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); + } + } + } + + delete[] pkt.buffer; + } + Thread::sleep(60); + m_parrotFrames.pop_front(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Helper to route rewrite the network data buffer. */ + +void TagAnalogData::routeRewrite(uint8_t* buffer, uint32_t peerId, uint32_t dstId, bool outbound) +{ + uint32_t rewriteDstId = dstId; + + // does the data require route writing? + if (peerRewrite(peerId, rewriteDstId, outbound)) { + // rewrite destination TGID in the frame + SET_UINT24(rewriteDstId, buffer, 8U); + } +} + +/* Helper to route rewrite destination ID and slot. */ + +bool TagAnalogData::peerRewrite(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->findByRewrite(peerId, dstId); + } + + bool rewrote = false; + if (tg.config().rewriteSize() > 0) { + std::vector rewrites = tg.config().rewrite(); + for (auto entry : rewrites) { + if (entry.peerId() == peerId) { + if (outbound) { + dstId = entry.tgId(); + } + else { + dstId = tg.source().tgId(); + } + rewrote = true; + break; + } + } + } + + return rewrote; +} + +/* Helper to determine if the peer is permitted for traffic. */ + +bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool external) +{ + if (!data.getGroup()) { + if (m_network->m_disallowU2U) + return false; + if (!m_network->checkU2UDroppedPeer(peerId)) + return true; + return false; + } + + FNEPeerConnection* connection = nullptr; // bryanb: this is a possible null ref concurrency issue + // it is possible if the timing is just right to get a valid + // connection back initially, and then for it to be deleted + if (peerId > 0 && (m_network->m_peers.find(peerId) != m_network->m_peers.end())) { + connection = m_network->m_peers[peerId]; + } + + // is this peer a Peer-Link peer? + if (connection != nullptr) { + if (connection->isPeerLink()) { + return true; // Peer Link peers are *always* allowed to receive traffic and no other rules may filter + // these peers + } + } + + // is this a group call? + if (data.getGroup()) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + + std::vector inclusion = tg.config().inclusion(); + std::vector exclusion = tg.config().exclusion(); + + // peer inclusion lists take priority over exclusion lists + if (inclusion.size() > 0) { + auto it = std::find(inclusion.begin(), inclusion.end(), peerId); + if (it == inclusion.end()) { + return false; + } + } + else { + if (exclusion.size() > 0) { + auto it = std::find(exclusion.begin(), exclusion.end(), peerId); + if (it != exclusion.end()) { + return false; + } + } + } + + // peer always send list takes priority over any following affiliation rules + std::vector alwaysSend = tg.config().alwaysSend(); + if (alwaysSend.size() > 0) { + auto it = std::find(alwaysSend.begin(), alwaysSend.end(), peerId); + if (it != alwaysSend.end()) { + return true; // skip any following checks and always send traffic + } + } + + // is this peer a conventional peer? + if (m_network->m_allowConvSiteAffOverride) { + if (connection != nullptr) { + if (connection->isConventionalPeer()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for conventional peers + } + } + } + + // is this peer a SysView peer? + if (connection != nullptr) { + if (connection->isSysView()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for SysView peers + } + } + + // is this a TG that requires affiliations to repeat? + // NOTE: external peers *always* repeat traffic regardless of affiliation + if (tg.config().affiliated() && !external) { + uint32_t lookupPeerId = peerId; + if (connection != nullptr) { + if (connection->ccPeerId() > 0U) + lookupPeerId = connection->ccPeerId(); + } + + // check the affiliations for this peer to see if we can repeat traffic + lookups::AffiliationLookup* aff = m_network->m_peerAffiliations[lookupPeerId]; + if (aff == nullptr) { + std::string peerIdentity = m_network->resolvePeerIdentity(lookupPeerId); + //LogError(LOG_NET, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", lookupPeerId, peerIdentity.c_str()); + return false; // this will cause no traffic to pass for this peer now...I'm not sure this is good behavior + } + else { + if (!aff->hasGroupAff(data.getDstId())) { + return false; + } + } + } + } + + return true; +} + +/* Helper to validate the analog call stream. */ + +bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t streamId) +{ + // is the source ID a blacklisted ID? + bool rejectUnknownBadCall = false; + lookups::RadioId rid = m_network->m_ridLookup->find(data.getSrcId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + } + else { + // if this is a default radio -- and we are rejecting undefined radios + // report call error + if (m_network->m_rejectUnknownRID) { + rejectUnknownBadCall = true; + } + } + + // always validate a terminator if the source is valid + if (data.getFrameType() == AudioFrameType::TERMINATOR) + return true; + + // is this a private call? + if (!data.getGroup()) { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(data.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_DST_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + return false; + } + } + else { + // if this is a default radio -- and we are rejecting undefined radios + // report call error + if (m_network->m_rejectUnknownRID) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + LogWarning(LOG_NET, "Analog, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + } + } + + // is this a group call? + if (data.getGroup()) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + if (tg.isInvalid()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_INV_TALKGROUP)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + + // peer always send list takes priority over any following affiliation rules + bool isAlwaysPeer = false; + std::vector alwaysSend = tg.config().alwaysSend(); + if (alwaysSend.size() > 0) { + auto it = std::find(alwaysSend.begin(), alwaysSend.end(), peerId); + if (it != alwaysSend.end()) { + isAlwaysPeer = true; // skip any following checks and always send traffic + rejectUnknownBadCall = false; + } + } + + // fail call if the reject flag is set + if (rejectUnknownBadCall) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + LogWarning(LOG_NET, "Analog, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + + // is the TGID active? + if (!tg.config().active()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_TALKGROUP)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + + // always peers can violate the rules...hurray + if (!isAlwaysPeer) { + // does the TGID have a permitted RID list? + if (tg.config().permittedRIDs().size() > 0) { + // does the transmitting RID have permission? + std::vector permittedRIDs = tg.config().permittedRIDs(); + if (std::find(permittedRIDs.begin(), permittedRIDs.end(), data.getSrcId()) == permittedRIDs.end()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_RID_NOT_PERMITTED)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + } + } + } + + return true; +} diff --git a/src/fne/network/callhandler/TagAnalogData.h b/src/fne/network/callhandler/TagAnalogData.h new file mode 100644 index 000000000..c7ae6624c --- /dev/null +++ b/src/fne/network/callhandler/TagAnalogData.h @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file TagAnalogData.h + * @ingroup fne_callhandler + * @file TagAnalogData.cpp + * @ingroup fne_callhandler + */ +#if !defined(__CALLHANDLER__TAG_ANALOG_DATA_H__) +#define __CALLHANDLER__TAG_ANALOG_DATA_H__ + +#include "fne/Defines.h" +#include "common/concurrent/deque.h" +#include "common/concurrent/unordered_map.h" +#include "common/dmr/DMRDefines.h" +#include "common/dmr/data/NetData.h" +#include "common/dmr/lc/CSBK.h" +#include "common/Clock.h" +#include "network/FNENetwork.h" +#include "network/callhandler/packetdata/DMRPacketData.h" + +namespace network +{ + namespace callhandler + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements the analog call handler and data FNE networking logic. + * @ingroup fne_callhandler + */ + class HOST_SW_API TagAnalogData { + public: + /** + * @brief Initializes a new instance of the TagAnalogData class. + * @param network Instance of the FNENetwork class. + * @param debug Flag indicating whether network debug is enabled. + */ + TagAnalogData(FNENetwork* network, bool debug); + /** + * @brief Finalizes a instance of the TagAnalogData class. + */ + ~TagAnalogData(); + + /** + * @brief Process a data frame from the network. + * @param data Network data buffer. + * @param len Length of data. + * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. + * @param pktSeq RTP packet sequence. + * @param streamId Stream ID. + * @param external Flag indicating traffic is from an external peer. + * @returns bool True, if frame is processed, otherwise false. + */ + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); + + /** + * @brief Helper to playback a parrot frame to the network. + */ + void playbackParrot(); + /** + * @brief Helper to determine if there are stored parrot frames. + * @returns True, if there are queued parrot frames to playback, otherwise false. + */ + bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + + private: + FNENetwork* m_network; + + /** + * @brief Represents a stored parrot frame. + */ + class ParrotFrame { + public: + uint8_t* buffer; + uint32_t bufferLen; + + /** + * @brief RTP Packet Sequence. + */ + uint16_t pktSeq; + /** + * @brief Call Stream ID. + */ + uint32_t streamId; + /** + * @brief Peer ID. + */ + uint32_t peerId; + + /** + * @brief Source ID. + */ + uint32_t srcId; + /** + * @brief Destination ID. + */ + uint32_t dstId; + }; + concurrent::deque m_parrotFrames; + bool m_parrotFramesReady; + + /** + * @brief Represents the receive status of a call. + */ + class RxStatus { + public: + system_clock::hrc::hrc_t callStartTime; + system_clock::hrc::hrc_t lastPacket; + /** + * @brief Source ID. + */ + uint32_t srcId; + /** + * @brief Destination ID. + */ + uint32_t dstId; + /** + * @brief Call Stream ID. + */ + uint32_t streamId; + /** + * @brief Peer ID. + */ + uint32_t peerId; + /** + * @brief Flag indicating this call is active with traffic currently in progress. + */ + bool activeCall; + + /** + * @brief Helper to reset call status. + */ + void reset() + { + srcId = 0U; + dstId = 0U; + streamId = 0U; + peerId = 0U; + activeCall = false; + } + }; + typedef std::pair StatusMapPair; + concurrent::unordered_map m_status; + + bool m_debug; + + /** + * @brief Helper to route rewrite the network data buffer. + * @param buffer Frame buffer. + * @param peerId Peer ID. + * @param dstId Destination ID. + * @param outbound Flag indicating whether or not this is outbound traffic. + */ + void routeRewrite(uint8_t* buffer, uint32_t peerId, uint32_t dstId, bool outbound = true); + /** + * @brief Helper to route rewrite destination ID and slot. + * @param peerId Peer ID. + * @param dstId Destination ID. + * @param outbound Flag indicating whether or not this is outbound traffic. + * @returns bool True, if rewritten successfully, otherwise false. + */ + bool peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound = true); + + /** + * @brief Helper to determine if the peer is permitted for traffic. + * @param peerId Peer ID. + * @param data Instance of data::NetData Analog data container class. + * @param streamId Stream ID. + * @param external Flag indicating this traffic came from an external peer. + * @returns bool True, if valid, otherwise false. + */ + bool isPeerPermitted(uint32_t peerId, analog::data::NetData& data, uint32_t streamId, bool external = false); + /** + * @brief Helper to validate the DMR call stream. + * @param peerId Peer ID. + * @param data Instance of data::NetData Analog data container class. + * @param streamId Stream ID. + * @returns bool True, if valid, otherwise false. + */ + bool validate(uint32_t peerId, analog::data::NetData& data, uint32_t streamId); + }; + } // namespace callhandler +} // namespace network + +#endif // __CALLHANDLER__TAG_ANALOG_DATA_H__ diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index d827877bb..efd24713b 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -8,9 +8,9 @@ * */ /** - * @file TagNXDNData.h + * @file TagDMRData.h * @ingroup fne_callhandler - * @file TagNXDNData.cpp + * @file TagDMRData.cpp * @ingroup fne_callhandler */ #if !defined(__CALLHANDLER__TAG_DMR_DATA_H__) @@ -237,7 +237,7 @@ namespace network /** * @brief Helper to determine if the peer is permitted for traffic. * @param peerId Peer ID. - * @param dmrData Instance of data::NetData DMR data container class. + * @param data Instance of data::NetData DMR data container class. * @param streamId Stream ID. * @param external Flag indicating this traffic came from an external peer. * @returns bool True, if valid, otherwise false. @@ -246,7 +246,7 @@ namespace network /** * @brief Helper to validate the DMR call stream. * @param peerId Peer ID. - * @param dmrData Instance of data::NetData DMR data container class. + * @param data Instance of data::NetData DMR data container class. * @param[in] csbk Instance of dmr::lc::CSBK. * @param streamId Stream ID. * @returns bool True, if valid, otherwise false. diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 49443172a..2e2ac38b6 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -945,7 +945,7 @@ bool Host::createNetwork() // initialize networking if (netEnable) { - m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, slot1, slot2, + m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, false, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup); m_network->setLookups(m_ridLookup, m_tidLookup); diff --git a/src/patch/network/PeerNetwork.cpp b/src/patch/network/PeerNetwork.cpp index a16c0ecfa..568334925 100644 --- a/src/patch/network/PeerNetwork.cpp +++ b/src/patch/network/PeerNetwork.cpp @@ -29,7 +29,7 @@ using namespace network; PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, false, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) { assert(!address.empty()); assert(port > 0U); diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index ecee7b562..b3d594fd3 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -4,11 +4,12 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" #include "common/Clock.h" +#include "common/analog/AnalogDefines.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/lc/csbk/CSBKFactory.h" #include "common/dmr/lc/LC.h" @@ -93,6 +94,7 @@ typedef std::pair StatusMapPair; std::unordered_map g_dmrStatus; std::unordered_map g_p25Status; std::unordered_map g_nxdnStatus; +std::unordered_map g_analogStatus; // --------------------------------------------------------------------------- // Global Functions @@ -237,7 +239,7 @@ bool createPeerNetwork() } // initialize networking - g_network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, true, true, true, true, true, false, true, false); + g_network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, false, true, false); g_network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); g_network->setLookups(g_ridLookup, g_tidLookup); @@ -981,6 +983,58 @@ void* threadNetworkPump(void* arg) if (g_debug) LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); } + + UInt8Array analogBuffer = g_network->readAnalog(netReadRet, length); + if (netReadRet) { + using namespace analog; + + uint32_t srcId = GET_UINT24(analogBuffer, 5U); + uint32_t dstId = GET_UINT24(analogBuffer, 8U); + + ANODEF::AudioFrameType::E frameType = (ANODEF::AudioFrameType::E)(analogBuffer[15U] & 0x0FU); + + // specifically only check the following logic for end of call, voice or data frames + if (frameType == ANODEF::AudioFrameType::TERMINATOR) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid TX_REL, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + RxStatus status = g_analogStatus[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_analogStatus.end()) { + g_analogStatus.erase(dstId); + + LogMessage(LOG_NET, "Analog, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + } + } + + // is this a new call stream? + if (frameType == ANODEF::AudioFrameType::VOICE_START) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid call, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + auto it = std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + if (it == g_analogStatus.end()) { + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + g_analogStatus[dstId] = status; + + LogMessage(LOG_NET, "Analog, Call Start, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + + if (g_debug) + LogMessage(LOG_NET, "Analog, frameType = $%02X, srcId = %u, dstId = %u, len = %u", frameType, srcId, dstId, length); + } } if (ms < 2U) diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index a2e887981..f8cd7b34f 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -35,8 +35,8 @@ std::mutex PeerNetwork::m_peerStatusMutex; /* Initializes a new instance of the PeerNetwork class. */ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), + bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + Network(address, port, localPort, peerId, password, duplex, debug, true, true, true, true, true, true, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), peerStatus(), m_peerLink(false), m_tgidPkt(true, "Peer-Link, TGID List"), diff --git a/src/sysview/network/PeerNetwork.h b/src/sysview/network/PeerNetwork.h index b9038815f..c4bb5daa7 100644 --- a/src/sysview/network/PeerNetwork.h +++ b/src/sysview/network/PeerNetwork.h @@ -49,17 +49,12 @@ namespace network * @param password Network authentication password. * @param duplex Flag indicating full-duplex operation. * @param debug Flag indicating whether network debug is enabled. - * @param dmr Flag indicating whether DMR is enabled. - * @param p25 Flag indicating whether P25 is enabled. - * @param nxdn Flag indicating whether NXDN is enabled. - * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. - * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. * @param allowDiagnosticTransfer Flag indicating that the system diagnostic logs will be sent to the network. * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Flag indicating whether or not SysView has received Peer-Link data transfers. From b9e69e68bdde179eab2a6e2d8aef382c1c4ade72 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 20 Jun 2025 19:51:08 -0400 Subject: [PATCH 043/133] add analog enable flag to fne config YAML; --- configs/fne-config.example.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 9a8c407cd..adf03582b 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -70,6 +70,8 @@ master: allowP25Traffic: true # Flag indicating whether or not NXDN traffic will be passed. allowNXDNTraffic: true + # Flag indicating whether or not analog traffic will be passed. + allowAnalogTraffic: false # Flag indicating whether packet data will be passed. disablePacketData: false From f912df902a22a5f926e496d82248ce0341cbf747 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 20 Jun 2025 20:26:20 -0400 Subject: [PATCH 044/133] prelim work to make dvmbridge pass analog audio directly into FNE network; --- configs/bridge-config.example.yml | 2 +- src/bridge/HostBridge.cpp | 636 ++++++++++++++++++++++------- src/bridge/HostBridge.h | 27 +- src/bridge/network/PeerNetwork.cpp | 4 +- src/bridge/network/PeerNetwork.h | 3 +- src/common/analog/AnalogDefines.h | 3 + src/common/analog/data/NetData.h | 2 +- src/common/network/BaseNetwork.h | 6 +- 8 files changed, 515 insertions(+), 168 deletions(-) diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 36fc829ab..0576a6cfe 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -145,7 +145,7 @@ system: # - (Not used when utilizing external USB vocoder!) vocoderEncoderAudioGain: 3.0 - # Audio transmit mode (1 - DMR, 2 - P25). + # Audio transmit mode (1 - DMR, 2 - P25, 3 - Analog). txMode: 1 # Relative sample level for VOX to activate. diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 64c37ea46..a62566756 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -11,6 +11,7 @@ #include "Defines.h" #include "common/analog/AnalogDefines.h" #include "common/analog/AnalogAudio.h" +#include "common/analog/data/NetData.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/data/EMB.h" #include "common/dmr/data/NetData.h" @@ -224,6 +225,7 @@ HostBridge::HostBridge(const std::string& confFile) : m_netLDU2(nullptr), m_p25SeqNo(0U), m_p25N(0U), + m_analogN(0U), m_audioDetect(false), m_trafficFromUDP(false), m_udpSrcId(0U), @@ -495,6 +497,10 @@ int HostBridge::run() if (m_txMode == TX_MODE_P25) { m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId, 0U); }); } + + if (m_txMode == TX_MODE_ANALOG) { + m_network->setAnalogICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId, 0U); }); + } } /* @@ -854,8 +860,8 @@ bool HostBridge::readParams() m_txMode = (uint8_t)systemConf["txMode"].as(1U); if (m_txMode < TX_MODE_DMR) m_txMode = TX_MODE_DMR; - if (m_txMode > TX_MODE_P25) - m_txMode = TX_MODE_P25; + if (m_txMode > TX_MODE_ANALOG) + m_txMode = TX_MODE_ANALOG; m_voxSampleLevel = systemConf["voxSampleLevel"].as(30.0f); m_dropTimeMS = (uint16_t)systemConf["dropTimeMs"].as(180U); @@ -875,6 +881,8 @@ bool HostBridge::readParams() } } break; + case TX_MODE_ANALOG: + break; } m_localDropTime = Timer(1000U, 0U, m_dropTimeMS); @@ -899,13 +907,19 @@ bool HostBridge::readParams() m_trace = systemConf["trace"].as(false); m_debug = systemConf["debug"].as(false); + std::string txModeStr = "DMR"; + if (m_txMode == TX_MODE_P25) + txModeStr = "P25"; + if (m_txMode == TX_MODE_ANALOG) + txModeStr = "Analog"; + LogInfo("General Parameters"); LogInfo(" Rx Audio Gain: %.1f", m_rxAudioGain); LogInfo(" Vocoder Decoder Audio Gain: %.1f", m_vocoderDecoderAudioGain); LogInfo(" Vocoder Decoder Auto Gain: %s", m_vocoderDecoderAutoGain ? "yes" : "no"); LogInfo(" Tx Audio Gain: %.1f", m_txAudioGain); LogInfo(" Vocoder Encoder Audio Gain: %.1f", m_vocoderEncoderAudioGain); - LogInfo(" Transmit Mode: %s", m_txMode == TX_MODE_DMR ? "DMR" : "P25"); + LogInfo(" Transmit Mode: %s", txModeStr.c_str()); LogInfo(" VOX Sample Level: %.1f", m_voxSampleLevel); LogInfo(" Drop Time: %ums", m_dropTimeMS); LogInfo(" Detect Analog MDC1200: %s", m_detectAnalogMDC1200 ? "yes" : "no"); @@ -992,6 +1006,13 @@ bool HostBridge::createNetwork() m_tekKeyId = 0U; } + // ensure encryption is currently disabled for analog (its not supported) + if (m_txMode == TX_MODE_ANALOG && m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U) { + ::LogError(LOG_HOST, "Encryption is not supported for Analog. Disabling."); + m_tekAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekKeyId = 0U; + } + m_srcId = (uint32_t)networkConf["sourceId"].as(p25::defines::WUID_FNE); m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as(false); m_overrideSrcIdFromUDP = networkConf["overrideSourceIdFromUDP"].as(false); @@ -1022,6 +1043,7 @@ bool HostBridge::createNetwork() } break; case TX_MODE_P25: + case TX_MODE_ANALOG: { if (m_dstId > 65535) { ::LogError(LOG_HOST, "Bridge destination ID cannot be greater than 65535."); @@ -1121,7 +1143,7 @@ bool HostBridge::createNetwork() LogInfo(" Debug: yes"); } - bool dmr = false, p25 = false; + bool dmr = false, p25 = false, analog = false; switch (m_txMode) { case TX_MODE_DMR: dmr = true; @@ -1129,10 +1151,13 @@ bool HostBridge::createNetwork() case TX_MODE_P25: p25 = true; break; + case TX_MODE_ANALOG: + analog = true; + break; } // initialize networking - m_network = new PeerNetwork(address, port, local, id, password, true, debug, dmr, p25, false, true, true, true, allowDiagnosticTransfer, true, false); + m_network = new PeerNetwork(address, port, local, id, password, true, debug, dmr, p25, false, analog, true, true, true, allowDiagnosticTransfer, true, false); m_network->setMetadata(m_identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); m_network->setConventional(true); @@ -2448,6 +2473,271 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ m_p25N++; } +/* Helper to process analog network traffic. */ + +void HostBridge::processAnalogNetwork(uint8_t* buffer, uint32_t length) +{ + assert(buffer != nullptr); + using namespace analog; + using namespace analog::defines; + + if (m_txMode != TX_MODE_ANALOG) + return; + + // process network message header + uint8_t seqNo = buffer[4U]; + + uint32_t srcId = GET_UINT24(buffer, 5U); + uint32_t dstId = GET_UINT24(buffer, 8U); + + bool individual = (buffer[15] & 0x40U) == 0x40U; + + AudioFrameType::E frameType = (AudioFrameType::E)(buffer[15U] & 0x0FU); + + data::NetData analogData; + analogData.setSeqNo(seqNo); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setFrameType(frameType); + + analogData.setAudio(buffer + 20U); + + uint8_t frame[AUDIO_SAMPLES_LENGTH_BYTES]; + analogData.getAudio(frame); + + if (m_debug) { + LogDebug(LOG_NET, "Analog, seqNo = %u, srcId = %u, dstId = %u, len = %u", seqNo, srcId, dstId, length); + } + + if (!individual) { + if (srcId == 0) + return; + + // ensure destination ID matches and slot matches + if (dstId != m_dstId) + return; + + // is this a new call stream? + if (m_network->getAnalogStreamId() != m_rxStreamId) { + m_callInProgress = true; + m_callAlgoId = 0U; + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_rxStartTime = now; + + LogMessage(LOG_HOST, "Analog, call start, srcId = %u, dstId = %u", srcId, dstId); + if (m_preambleLeaderTone) + generatePreambleTone(); + } + + if (frameType == AudioFrameType::TERMINATOR) { + m_callInProgress = false; + m_ignoreCall = false; + m_callAlgoId = 0U; + + if (m_rxStartTime > 0U) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t diff = now - m_rxStartTime; + + LogMessage(LOG_HOST, "Analog, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + } + + m_rxStartTime = 0U; + m_rxStreamId = 0U; + + m_rtpSeqNo = 0U; + m_rtpTimestamp = INVALID_TS; + return; + } + + if (m_ignoreCall && m_callAlgoId == 0U) + m_ignoreCall = false; + + if (m_ignoreCall) + return; + + if (frameType == AudioFrameType::VOICE_START || frameType == AudioFrameType::VOICE) { + LogMessage(LOG_NET, ANO_VOICE ", audio, srcId = %u, dstId = %u, seqNo = %u", srcId, dstId, analogData.getSeqNo()); + + short samples[AUDIO_SAMPLES_LENGTH]; + int smpIdx = 0; + for (uint32_t pcmIdx = 0; pcmIdx < AUDIO_SAMPLES_LENGTH; pcmIdx++) { + samples[smpIdx] = AnalogAudio::decodeMuLaw(frame[pcmIdx]); + smpIdx++; + } + + // post-process: apply gain to decoded audio frames + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); + + if (m_localAudio) { + m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); + } + + if (m_udpAudio) { + int pcmIdx = 0; + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; + if (m_udpUseULaw) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); + } + + if (m_trace) + Utils::dump(1U, "HostBridge()::processAnalogNetwork() Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); + } + else { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } + } + + uint32_t length = (AUDIO_SAMPLES_LENGTH * 2U) + 4U; + uint8_t* audioData = nullptr; + + if (!m_udpUsrp) { + if (!m_udpMetadata) { + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + if (m_udpUseULaw) { + length = (AUDIO_SAMPLES_LENGTH)+4U; + if (m_udpNoIncludeLength) { + length = AUDIO_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, AUDIO_SAMPLES_LENGTH); + } + else { + SET_UINT32(AUDIO_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH); + } + + // are we sending RTP audio frames? + if (m_udpRTPFrames) { + uint8_t* rtpFrame = generateRTPHeaders(AUDIO_SAMPLES_LENGTH, m_rtpSeqNo); + if (rtpFrame != nullptr) { + length += RTP_HEADER_LENGTH_BYTES; + uint8_t* newAudioData = new uint8_t[length]; + ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, AUDIO_SAMPLES_LENGTH); + delete[] audioData; + + audioData = newAudioData; + } + + m_rtpSeqNo++; + } + } + else { + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); + } + } + else { + length = (AUDIO_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); + + // embed destination and source IDs + SET_UINT32(dstId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 4U)); + SET_UINT32(srcId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 8U)); + } + } + else { + uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; + + length = (AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + + m_usrpSeqNo++; + usrpHeader[15U] = 1; // set PTT state to true + SET_UINT32(m_usrpSeqNo, usrpHeader, 4U); + + ::memcpy(usrpHeader, "USRP", 4); + ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, AUDIO_SAMPLES_LENGTH * 2U); + } + + sockaddr_storage addr; + uint32_t addrLen; + + if (udp::Socket::lookup(m_udpSendAddress, m_udpSendPort, addr, addrLen) == 0) { + m_udpAudioSocket->write(audioData, length, addr, addrLen); + } + + delete[] audioData; + } + } + + m_rxStreamId = m_network->getAnalogStreamId(); + } +} + +/* Helper to encode analog network traffic audio frames. */ + +void HostBridge::encodeAnalogAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_t forcedDstId) +{ + assert(pcm != nullptr); + using namespace analog; + using namespace analog::defines; + using namespace analog::data; + + if (m_analogN == 254U) + m_analogN = 0; + + int smpIdx = 0; + short samples[AUDIO_SAMPLES_LENGTH]; + for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) { + samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); + smpIdx++; + } + + // pre-process: apply gain to PCM audio frames + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain); + + uint32_t srcId = m_srcId; + if (m_srcIdOverride != 0 && (m_overrideSrcIdFromMDC)) + srcId = m_srcIdOverride; + if (m_overrideSrcIdFromUDP) + srcId = m_udpSrcId; + if (forcedSrcId > 0 && forcedSrcId != m_srcId) + srcId = forcedSrcId; + uint32_t dstId = m_dstId; + if (forcedDstId > 0 && forcedDstId != m_dstId) + dstId = forcedDstId; + + // never allow a source ID of 0 + if (srcId == 0U) + srcId = m_srcId; + + data::NetData analogData; + analogData.setSeqNo(m_analogN); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setControl(0U); + analogData.setFrameType(AudioFrameType::VOICE); + if (m_txStreamId <= 1U) { + analogData.setFrameType(AudioFrameType::VOICE_START); + + if (m_grantDemand) { + analogData.setControl(0x80U); // analog remote grant demand flag + } + } + + int pcmIdx = 0; + uint8_t outPcm[AUDIO_SAMPLES_LENGTH * 2U]; + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + outPcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); + } + + if (m_trace) + Utils::dump(1U, "HostBridge()::encodeAnalogAudioFrame() Encoded uLaw Audio", outPcm, AUDIO_SAMPLES_LENGTH); + + analogData.setAudio(outPcm); + + m_network->writeAnalog(analogData); + m_txStreamId = m_network->getAnalogStreamId(); + m_analogN++; +} + /* Helper to send USRP end of transmission */ void HostBridge::sendUsrpEot() @@ -2559,47 +2849,67 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) if (!m_callInProgress) { switch (m_txMode) { case TX_MODE_DMR: - { - dmr::defines::DataType::E dataType = dmr::defines::DataType::VOICE_SYNC; - if (m_dmrN == 0) - dataType = dmr::defines::DataType::VOICE_SYNC; - else { - dataType = dmr::defines::DataType::VOICE; - } - - dmr::data::NetData data = dmr::data::NetData(); - data.setSlotNo(m_slot); - data.setDataType(dataType); - data.setSrcId(srcId); - data.setDstId(dstId); - data.setFLCO(dmr::defines::FLCO::GROUP); - data.setN(m_dmrN); - data.setSeqNo(m_dmrSeqNo); - data.setBER(0U); - data.setRSSI(0U); - - LogMessage(LOG_HOST, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot, dstId); + { + dmr::defines::DataType::E dataType = dmr::defines::DataType::VOICE_SYNC; + if (m_dmrN == 0) + dataType = dmr::defines::DataType::VOICE_SYNC; + else { + dataType = dmr::defines::DataType::VOICE; + } - m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData); - m_network->resetDMR(data.getSlotNo()); - } - break; + dmr::data::NetData data = dmr::data::NetData(); + data.setSlotNo(m_slot); + data.setDataType(dataType); + data.setSrcId(srcId); + data.setDstId(dstId); + data.setFLCO(dmr::defines::FLCO::GROUP); + data.setN(m_dmrN); + data.setSeqNo(m_dmrSeqNo); + data.setBER(0U); + data.setRSSI(0U); + + LogMessage(LOG_HOST, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot, dstId); + + m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData); + m_network->resetDMR(data.getSlotNo()); + } + break; case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(dstId); - lc.setSrcId(srcId); + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(p25::defines::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - LogMessage(LOG_HOST, P25_TDU_STR); + LogMessage(LOG_HOST, P25_TDU_STR); - uint8_t controlByte = 0x00U; - m_network->writeP25TDU(lc, lsd, controlByte); - m_network->resetP25(); - } - break; + uint8_t controlByte = 0x00U; + m_network->writeP25TDU(lc, lsd, controlByte); + m_network->resetP25(); + } + break; + case TX_MODE_ANALOG: + { + LogMessage(LOG_HOST, ANO_TERMINATOR); + + uint8_t controlByte = 0x00U; + + data::NetData analogData; + analogData.setSeqNo(m_analogN); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setFrameType(AudioFrameType::TERMINATOR); + + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; + ::memset(pcm, 0x00U, AUDIO_SAMPLES_LENGTH * 2U); + analogData.setAudio(pcm); + + m_network->writeAnalog(analogData, true); + m_network->resetAnalog(); + } + break; } } @@ -2615,6 +2925,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) m_dmrN = 0U; m_p25SeqNo = 0U; m_p25N = 0U; + m_analogN = 0U; m_rtpSeqNo = 0U; m_rtpTimestamp = INVALID_TS; @@ -2733,19 +3044,19 @@ void* HostBridge::threadAudioProcess(void* arg) if (bridge->m_grantDemand) { switch (bridge->m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(dstId); - lc.setSrcId(srcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = 0x80U; - bridge->m_network->writeP25TDU(lc, lsd, controlByte); - } - break; + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(p25::defines::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = 0x80U; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; } } } @@ -2782,6 +3093,9 @@ void* HostBridge::threadAudioProcess(void* arg) case TX_MODE_P25: bridge->encodeP25AudioFrame(pcm); break; + case TX_MODE_ANALOG: + bridge->encodeAnalogAudioFrame(pcm); + break; } } } @@ -2913,19 +3227,19 @@ void* HostBridge::threadUDPAudioProcess(void* arg) LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, bridge->m_udpSrcId, bridge->m_udpDstId); if (bridge->m_grantDemand) { switch (bridge->m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(bridge->m_udpDstId); - lc.setSrcId(bridge->m_udpSrcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = 0x80U; - bridge->m_network->writeP25TDU(lc, lsd, controlByte); - } - break; + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(p25::defines::LCO::GROUP); + lc.setDstId(bridge->m_udpDstId); + lc.setSrcId(bridge->m_udpSrcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = 0x80U; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; } } } @@ -2950,6 +3264,9 @@ void* HostBridge::threadUDPAudioProcess(void* arg) case TX_MODE_P25: bridge->encodeP25AudioFrame(req->pcm, bridge->m_udpSrcId); break; + case TX_MODE_ANALOG: + bridge->encodeAnalogAudioFrame(req->pcm, bridge->m_udpSrcId); + break; } } @@ -3039,6 +3356,14 @@ void* HostBridge::threadNetworkProcess(void* arg) } } + if (bridge->m_txMode == TX_MODE_ANALOG) { + std::lock_guard lock(HostBridge::m_networkMutex); + UInt8Array analogBuffer = bridge->m_network->readAnalog(netReadRet, length); + if (netReadRet) { + bridge->processAnalogNetwork(analogBuffer.get(), length); + } + } + Thread::sleep(1U); } @@ -3094,115 +3419,115 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) { switch (m_txMode) { case TX_MODE_DMR: - { - using namespace dmr; - using namespace dmr::defines; - using namespace dmr::data; + { + using namespace dmr; + using namespace dmr::defines; + using namespace dmr::data; - m_dmrN = (uint8_t)(m_dmrSeqNo % 6); + m_dmrN = (uint8_t)(m_dmrSeqNo % 6); - // send DMR voice - uint8_t data[DMR_FRAME_LENGTH_BYTES]; + // send DMR voice + uint8_t data[DMR_FRAME_LENGTH_BYTES]; - m_ambeCount = 0U; + m_ambeCount = 0U; - ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); - m_ambeCount++; - ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); - m_ambeCount++; - ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); - m_ambeCount++; + ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); + m_ambeCount++; + ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); + m_ambeCount++; + ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); + m_ambeCount++; - ::memcpy(data, m_ambeBuffer, 13U); - data[13U] = (uint8_t)(m_ambeBuffer[13U] & 0xF0); - data[19U] = (uint8_t)(m_ambeBuffer[13U] & 0x0F); - ::memcpy(data + 20U, m_ambeBuffer + 14U, 13U); + ::memcpy(data, m_ambeBuffer, 13U); + data[13U] = (uint8_t)(m_ambeBuffer[13U] & 0xF0); + data[19U] = (uint8_t)(m_ambeBuffer[13U] & 0x0F); + ::memcpy(data + 20U, m_ambeBuffer + 14U, 13U); - DataType::E dataType = DataType::VOICE_SYNC; - if (m_dmrN == 0) - dataType = DataType::VOICE_SYNC; - else { - dataType = DataType::VOICE; + DataType::E dataType = DataType::VOICE_SYNC; + if (m_dmrN == 0) + dataType = DataType::VOICE_SYNC; + else { + dataType = DataType::VOICE; - uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); + uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); - // generated embedded signalling - EMB emb = EMB(); - emb.setColorCode(0U); - emb.setLCSS(lcss); - emb.encode(data); - } + // generated embedded signalling + EMB emb = EMB(); + emb.setColorCode(0U); + emb.setLCSS(lcss); + emb.encode(data); + } - LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); + LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); - // generate DMR network frame - NetData dmrData; - dmrData.setSlotNo(m_slot); - dmrData.setDataType(dataType); - dmrData.setSrcId(srcId); - dmrData.setDstId(dstId); - dmrData.setFLCO(FLCO::GROUP); - dmrData.setN(m_dmrN); - dmrData.setSeqNo(m_dmrSeqNo); - dmrData.setBER(0U); - dmrData.setRSSI(0U); + // generate DMR network frame + NetData dmrData; + dmrData.setSlotNo(m_slot); + dmrData.setDataType(dataType); + dmrData.setSrcId(srcId); + dmrData.setDstId(dstId); + dmrData.setFLCO(FLCO::GROUP); + dmrData.setN(m_dmrN); + dmrData.setSeqNo(m_dmrSeqNo); + dmrData.setBER(0U); + dmrData.setRSSI(0U); - dmrData.setData(data); + dmrData.setData(data); - m_network->writeDMR(dmrData, false); - m_txStreamId = m_network->getDMRStreamId(m_slot); + m_network->writeDMR(dmrData, false); + m_txStreamId = m_network->getDMRStreamId(m_slot); - m_dmrSeqNo++; - } - break; + m_dmrSeqNo++; + } + break; case TX_MODE_P25: - { - using namespace p25; - using namespace p25::defines; - using namespace p25::data; - - // fill the LDU buffers appropriately - switch (m_p25N) { - // LDU1 - case 0: - resetWithNullAudio(m_netLDU1, false); - break; + { + using namespace p25; + using namespace p25::defines; + using namespace p25::data; + + // fill the LDU buffers appropriately + switch (m_p25N) { + // LDU1 + case 0: + resetWithNullAudio(m_netLDU1, false); + break; - // LDU2 - case 1: - resetWithNullAudio(m_netLDU2, false); - break; - } + // LDU2 + case 1: + resetWithNullAudio(m_netLDU2, false); + break; + } - lc::LC lc = lc::LC(); - lc.setLCO(LCO::GROUP); - lc.setGroup(true); - lc.setPriority(4U); - lc.setDstId(dstId); - lc.setSrcId(srcId); + lc::LC lc = lc::LC(); + lc.setLCO(LCO::GROUP); + lc.setGroup(true); + lc.setPriority(4U); + lc.setDstId(dstId); + lc.setSrcId(srcId); - lc.setAlgId(ALGO_UNENCRYPT); - lc.setKId(0); + lc.setAlgId(ALGO_UNENCRYPT); + lc.setKId(0); - LowSpeedData lsd = LowSpeedData(); + LowSpeedData lsd = LowSpeedData(); - // send P25 LDU1 - if (m_p25N == 0U) { - LogMessage(LOG_HOST, P25_LDU1_STR " silence audio, srcId = %u, dstId = %u", srcId, dstId); - m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::DATA_UNIT); - m_p25N++; - break; - } + // send P25 LDU1 + if (m_p25N == 0U) { + LogMessage(LOG_HOST, P25_LDU1_STR " silence audio, srcId = %u, dstId = %u", srcId, dstId); + m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::DATA_UNIT); + m_p25N++; + break; + } - // send P25 LDU2 - if (m_p25N == 1U) { - LogMessage(LOG_HOST, P25_LDU2_STR " silence audio, algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U); - m_network->writeP25LDU2(lc, lsd, m_netLDU2); - m_p25N = 0U; - break; + // send P25 LDU2 + if (m_p25N == 1U) { + LogMessage(LOG_HOST, P25_LDU2_STR " silence audio, algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U); + m_network->writeP25LDU2(lc, lsd, m_netLDU2); + m_p25N = 0U; + break; + } } - } - break; + break; } } @@ -3283,6 +3608,7 @@ void* HostBridge::threadCallWatchdog(void* arg) bridge->m_dmrN = 0U; bridge->m_p25N = 0U; + bridge->m_analogN = 0U; } if (bridge->m_udpHangTime.isRunning()) diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index b29343fa6..68edba2b0 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -62,6 +62,7 @@ const uint8_t HALF_RATE_MODE = 0x01U; const uint8_t TX_MODE_DMR = 1U; const uint8_t TX_MODE_P25 = 2U; +const uint8_t TX_MODE_ANALOG = 3U; // --------------------------------------------------------------------------- @@ -234,6 +235,8 @@ class HOST_SW_API HostBridge { uint32_t m_p25SeqNo; uint8_t m_p25N; + uint8_t m_analogN; + bool m_audioDetect; bool m_trafficFromUDP; uint32_t m_udpSrcId; @@ -459,12 +462,18 @@ class HOST_SW_API HostBridge { void encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U); /** - * @brief Helper to generate outgoing RTP headers. - * @param msgLen Message Length. - * @param rtpSeq RTP Sequence. - * @returns uint8_t* Buffer containing the encoded RTP headers. + * @brief Helper to process analog network traffic. + * @param buffer + * @param length */ - uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); + void processAnalogNetwork(uint8_t* buffer, uint32_t length); + /** + * @brief Helper to encode analog network traffic audio frames. + * @param pcm + * @param forcedSrcId + * @param forcedDstId + */ + void encodeAnalogAudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U); /** * @brief Helper to generate USRP end of transmission @@ -476,6 +485,14 @@ class HOST_SW_API HostBridge { */ void generatePreambleTone(); + /** + * @brief Helper to generate outgoing RTP headers. + * @param msgLen Message Length. + * @param rtpSeq RTP Sequence. + * @returns uint8_t* Buffer containing the encoded RTP headers. + */ + uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); + /** * @brief Helper to end a local or UDP call. * @param srcId diff --git a/src/bridge/network/PeerNetwork.cpp b/src/bridge/network/PeerNetwork.cpp index deb9cc911..95697e312 100644 --- a/src/bridge/network/PeerNetwork.cpp +++ b/src/bridge/network/PeerNetwork.cpp @@ -28,8 +28,8 @@ using namespace network; /* Initializes a new instance of the PeerNetwork class. */ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, false, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, analog, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) { assert(!address.empty()); assert(port > 0U); diff --git a/src/bridge/network/PeerNetwork.h b/src/bridge/network/PeerNetwork.h index 0d58146a7..ab2f27f0b 100644 --- a/src/bridge/network/PeerNetwork.h +++ b/src/bridge/network/PeerNetwork.h @@ -48,6 +48,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -55,7 +56,7 @@ namespace network * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Writes P25 LDU1 frame data to the network. diff --git a/src/common/analog/AnalogDefines.h b/src/common/analog/AnalogDefines.h index 85e7a4da6..ee604b16f 100644 --- a/src/common/analog/AnalogDefines.h +++ b/src/common/analog/AnalogDefines.h @@ -50,6 +50,9 @@ namespace analog TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator }; } + + #define ANO_TERMINATOR "Analog, TERMINATOR (Terminator)" + #define ANO_VOICE "Analog, VOICE (Voice Data)" } // namespace defines } // namespace analog diff --git a/src/common/analog/data/NetData.h b/src/common/analog/data/NetData.h index 530451e18..2691c5ef3 100644 --- a/src/common/analog/data/NetData.h +++ b/src/common/analog/data/NetData.h @@ -55,7 +55,7 @@ namespace analog NetData& operator=(const NetData& data); /** - * @brief Sets audio data. + * @brief Sets audio data. This data buffer should be MuLaw encoded. * @param[in] buffer Audio data buffer. */ void setAudio(const uint8_t* buffer); diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 7f25d654e..d8d88109c 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -326,7 +326,7 @@ namespace network * @brief Gets the current analog stream ID. * @return uint32_t Stream ID. */ - uint32_t getAnalogtreamId() const { return m_analogStreamId; } + uint32_t getAnalogStreamId() const { return m_analogStreamId; } /** * @brief Helper to send a data message to the master. @@ -467,14 +467,14 @@ namespace network // Analog Audio /** - * @brief Reads analog raw frame data from the analog ring buffer. + * @brief Reads analog MuLaw audio frame data from the analog ring buffer. * @param[out] ret Flag indicating whether or not data was received. * @param[out] frameLength Length in bytes of received frame. * @returns UInt8Array Buffer containing received frame. */ virtual UInt8Array readAnalog(bool& ret, uint32_t& frameLength); /** - * @brief Writes analog frame data to the network. + * @brief Writes analog MuLaw audio frame data to the network. * @param[in] data Instance of the analog::data::NetData class containing the analog message. * @param noSequence Flag indicating the message should be sent with no RTP sequence (65535). * @returns bool True, if message was sent, otherwise false. From fc839a53c51fde505e803232c540c574ce21ac88 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 24 Jun 2025 14:58:23 -0400 Subject: [PATCH 045/133] handle locking inside deleteBuffers(); --- src/common/network/RawFrameQueue.cpp | 41 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index fbd874595..844cbc12e 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.cpp @@ -169,29 +169,34 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock bool RawFrameQueue::flushQueue() { bool ret = true; - std::lock_guard lock(m_queueMutex); - m_queueFlushing = true; - if (m_buffers.empty()) { - return false; - } + // scope is intentional + { + std::lock_guard lock(m_queueMutex); + m_queueFlushing = true; - // bryanb: this is the same as above -- but for some assinine reason prevents - // weirdness - if (m_buffers.size() == 0U) { - return false; - } + if (m_buffers.empty()) { + return false; + } - // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); + // bryanb: this is the same as above -- but for some assinine reason prevents + // weirdness + if (m_buffers.size() == 0U) { + return false; + } - ret = true; - if (!m_socket->write(m_buffers)) { - // LogError(LOG_NET, "Failed writing data to the network"); - ret = false; + // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); + + ret = true; + if (!m_socket->write(m_buffers)) { + // LogError(LOG_NET, "Failed writing data to the network"); + ret = false; + } + + m_queueFlushing = false; } deleteBuffers(); - m_queueFlushing = false; return ret; } @@ -203,6 +208,9 @@ bool RawFrameQueue::flushQueue() void RawFrameQueue::deleteBuffers() { + std::lock_guard lock(m_queueMutex); + m_queueFlushing = true; + for (auto& buffer : m_buffers) { if (buffer != nullptr) { // LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length); @@ -217,4 +225,5 @@ void RawFrameQueue::deleteBuffers() } } m_buffers.clear(); + m_queueFlushing = false; } From 59f007dda4ab43dc0704db4d711437155ef02284 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 24 Jun 2025 22:23:25 -0400 Subject: [PATCH 046/133] refactor FrameQueue mutex handling; don't start and stop the thread pool when the peer network opens/closes; --- src/common/network/FrameQueue.cpp | 23 ----------------------- src/common/network/FrameQueue.h | 4 ---- src/fne/network/PeerNetwork.cpp | 19 ++++++++++++------- src/fne/network/PeerNetwork.h | 4 ++++ 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 1d5b04228..556497a72 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -32,9 +32,7 @@ using namespace network::frame; FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug), m_peerId(peerId), -#if defined(_WIN32) m_streamTSMtx(), -#endif // defined(_WIN32) m_streamTimestamps() { assert(peerId < 999999999U); @@ -234,11 +232,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui uint32_t timestamp = INVALID_TS; if (streamId != 0U) { -#if defined(_WIN32) std::lock_guard lock(m_streamTSMtx); -#else - m_streamTimestamps.lock(false); -#endif // defined(_WIN32) auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { timestamp = entry->second; @@ -250,9 +244,6 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, previous TS = %u, TS = %u, rtpSeq = %u", streamId, m_streamTimestamps[streamId], timestamp, rtpSeq); m_streamTimestamps[streamId] = timestamp; } -#if !defined(_WIN32) - m_streamTimestamps.unlock(); -#endif // defined(_WIN32) } uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES + length; @@ -274,34 +265,20 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui timestamp = (uint32_t)system_clock::ntp::now(); header.setTimestamp(timestamp); -#if defined(_WIN32) std::lock_guard lock(m_streamTSMtx); m_streamTimestamps.insert({ streamId, timestamp }); -#else - m_streamTimestamps.insert(streamId, timestamp); -#endif // defined(_WIN32) } header.encode(buffer); if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { -#if defined(_WIN32) std::lock_guard lock(m_streamTSMtx); -#else - m_streamTimestamps.lock(false); -#endif // defined(_WIN32) auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { if (m_debug) LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq); -#if !defined(_WIN32) - m_streamTimestamps.unlock(); -#endif // defined(_WIN32) m_streamTimestamps.erase(streamId); } -#if !defined(_WIN32) - m_streamTimestamps.unlock(); -#endif // defined(_WIN32) } RTPFNEHeader fneHeader = RTPFNEHeader(); diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 492934575..4d60ecc6f 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -117,12 +117,8 @@ namespace network private: uint32_t m_peerId; -#if defined(_WIN32) std::mutex m_streamTSMtx; std::unordered_map m_streamTimestamps; -#else - concurrent::unordered_map m_streamTimestamps; -#endif // defined(_WIN32) /** * @brief Generate RTP message for the frame queue. diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 84a282e0a..6dd2ae504 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -66,6 +66,18 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc // FNE peer network manually handle protocol packets m_userHandleProtocol = true; + + // start thread pool + m_threadPool.start(); +} + +/* Finalizes a instance of the PeerNetwork class. */ + +PeerNetwork::~PeerNetwork() +{ + // stop thread pool + m_threadPool.stop(); + m_threadPool.wait(); } /* Sets the instances of the Peer List lookup tables. */ @@ -82,9 +94,6 @@ bool PeerNetwork::open() if (!m_enabled) return false; - // start thread pool - m_threadPool.start(); - return Network::open(); } @@ -92,10 +101,6 @@ bool PeerNetwork::open() void PeerNetwork::close() { - // stop thread pool - m_threadPool.stop(); - m_threadPool.wait(); - Network::close(); } diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 7addad323..28c9d7008 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -81,6 +81,10 @@ namespace network */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + /** + * @brief Finalizes a instance of the PeerNetwork class. + */ + ~PeerNetwork() override; /** * @brief Sets the instances of the Peer List lookup tables. From fcdb82a7d9f9631016c82a4320a862085c592dfd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 25 Jun 2025 10:06:30 -0400 Subject: [PATCH 047/133] use unique_ptr for compression class instead of passing raw pointers; --- src/common/network/PacketBuffer.cpp | 17 +++++++++-------- src/common/zlib/Compression.cpp | 28 ++++++++++++++++------------ src/common/zlib/Compression.h | 8 ++++---- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/common/network/PacketBuffer.cpp b/src/common/network/PacketBuffer.cpp index 5bb9fe8a1..67c33529e 100644 --- a/src/common/network/PacketBuffer.cpp +++ b/src/common/network/PacketBuffer.cpp @@ -119,7 +119,10 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL if (m_compression) { uint32_t decompressedLen = 0U; - *message = Compression::decompress(buffer, compressedLen, &decompressedLen); + UInt8Array decompressed = Compression::decompress(buffer, compressedLen, &decompressedLen); + *message = new uint8_t[decompressedLen]; + ::memset(*message, 0x00U, decompressedLen); + ::memcpy(*message, decompressed.get(), decompressedLen); // check that we got the appropriate data if (decompressedLen == len && message != nullptr) { @@ -161,13 +164,13 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length) // create temporary buffer uint32_t compressedLen = 0U; - uint8_t* buffer = nullptr; + UInt8Array buffer = nullptr; if (m_compression) { buffer = Compression::compress(data, length, &compressedLen); } else { - buffer = new uint8_t[length]; - ::memset(buffer, 0x00U, length); - ::memcpy(buffer, data, length); + buffer = std::make_unique(length); + ::memset(buffer.get(), 0x00U, length); + ::memcpy(buffer.get(), data, length); compressedLen = length; } @@ -198,15 +201,13 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length) if (offs + FRAG_BLOCK_SIZE > compressedLen) blockSize = FRAG_BLOCK_SIZE - ((offs + FRAG_BLOCK_SIZE) - compressedLen); - ::memcpy(frag->data + FRAG_HDR_SIZE, buffer + offs, blockSize); + ::memcpy(frag->data + FRAG_HDR_SIZE, buffer.get() + offs, blockSize); offs += FRAG_BLOCK_SIZE; fragments.insert(i, frag); LogInfoEx(LOG_NET, "%s, Outbound Packet Fragment, block %u of %u, txFragments = %u", m_name, i, blockCnt - 1U, fragments.size()); } - - delete[] buffer; } /* Helper to clear currently buffered fragments. */ diff --git a/src/common/zlib/Compression.cpp b/src/common/zlib/Compression.cpp index 0d8dd5d28..e1d1773c4 100644 --- a/src/common/zlib/Compression.cpp +++ b/src/common/zlib/Compression.cpp @@ -24,7 +24,7 @@ using namespace compress; /* Compress the given input buffer. */ -uint8_t* Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen) +UInt8Array Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen) { assert(buffer != nullptr); assert(len > 0U); @@ -87,18 +87,20 @@ uint8_t* Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* co Utils::dump(2U, "Compression::compress(), Compressed Data", compressed, strm.total_out); #endif - // reuse data buffer to return compressed data delete[] data; - data = new uint8_t[strm.total_out]; - ::memset(data, 0x00U, strm.total_out); - ::memcpy(data, compressed, strm.total_out); - return data; + // return compressed data + UInt8Array out = std::make_unique(strm.total_out); + ::memset(out.get(), 0x00U, strm.total_out); + ::memcpy(out.get(), compressed, strm.total_out); + + compressedData.clear(); // clear the vector to release memory + return out; } /* Decompress the given input buffer. */ -uint8_t* Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen) +UInt8Array Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen) { assert(buffer != nullptr); assert(len > 0U); @@ -159,11 +161,13 @@ uint8_t* Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* Utils::dump(2U, "Compression::decompress(), Decompressed Data", decompressed, strm.total_out); #endif - // reuse data buffer to return decompressed data delete[] data; - data = new uint8_t[strm.total_out]; - ::memset(data, 0x00U, strm.total_out); - ::memcpy(data, decompressed, strm.total_out); - return data; + // return decompressed data + UInt8Array out = std::make_unique(strm.total_out); + ::memset(out.get(), 0x00U, strm.total_out); + ::memcpy(out.get(), decompressed, strm.total_out); + + decompressedData.clear(); // clear the vector to release memory + return out; } diff --git a/src/common/zlib/Compression.h b/src/common/zlib/Compression.h index f740aea12..74519b7db 100644 --- a/src/common/zlib/Compression.h +++ b/src/common/zlib/Compression.h @@ -39,18 +39,18 @@ namespace compress * @param[in] buffer Buffer containing data to zlib compress. * @param[in] len Length of data to compress. * @param[out] compressedLen Length of compressed data. - * @returns uint8_t* Buffer containing compressed data. + * @returns UInt8Array Buffer containing compressed data. */ - static uint8_t* compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen); + static UInt8Array compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen); /** * @brief Decompress the given input buffer using zlib compression. * @param[in] buffer Buffer containing zlib compressed data. * @param[in] len Length of compressed data. * @param[out] decompressedLen Length of decompressed data. - * @returns uint8_t* Buffer containing decompressed data. + * @returns UInt8Array Buffer containing decompressed data. */ - static uint8_t* decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen); + static UInt8Array decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen); }; } // namespace compress From 4d4e1d4bc12459f8dc686146284553a3a6e659f3 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 3 Jul 2025 11:04:04 -0400 Subject: [PATCH 048/133] refactor FrameQueue from using a std::unordered_map to a simple fixed array that is directly controlled; --- src/common/network/FrameQueue.cpp | 92 ++++++++++++++++++++++++++----- src/common/network/FrameQueue.h | 39 ++++++++++++- 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 556497a72..2de00cf5a 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -32,10 +32,19 @@ using namespace network::frame; FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug), m_peerId(peerId), - m_streamTSMtx(), - m_streamTimestamps() + m_timestampMtx(), + m_streamTimestamps(nullptr) { assert(peerId < 999999999U); + + m_streamTimestamps = new Timestamp[RTP_STREAM_COUNT_MAX]; +} + +/* Finalizes a instance of the FrameQueue class. */ + +FrameQueue::~FrameQueue() +{ + delete[] m_streamTimestamps; } /* Read message from the received UDP packet. */ @@ -209,13 +218,71 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::clearTimestamps() { - m_streamTimestamps.clear(); + std::lock_guard lock(m_timestampMtx); + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + m_streamTimestamps[i].streamId = 0U; + m_streamTimestamps[i].timestamp = INVALID_TS; + } } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- +/* Search for a timestamp entry by stream ID. */ + +FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId) +{ + std::lock_guard lock(m_timestampMtx); + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + if (m_streamTimestamps[i].streamId == streamId) + return &m_streamTimestamps[i]; + } + + return nullptr; +} + +/* Insert a timestamp for a stream ID. */ + +void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp) +{ + std::lock_guard lock(m_timestampMtx); + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + if (m_streamTimestamps[i].streamId == 0U) { + m_streamTimestamps[i].streamId = streamId; + m_streamTimestamps[i].timestamp = timestamp; + break; + } + } +} + +/* Update a timestamp for a stream ID. */ + +void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp) +{ + std::lock_guard lock(m_timestampMtx); + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + if (m_streamTimestamps[i].streamId == streamId) { + m_streamTimestamps[i].timestamp = timestamp; + break; + } + } +} + +/* Erase a timestamp for a stream ID. */ + +void FrameQueue::eraseTimestamp(uint32_t streamId) +{ + std::lock_guard lock(m_timestampMtx); + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + if (m_streamTimestamps[i].streamId == streamId) { + m_streamTimestamps[i].streamId = 0U; + m_streamTimestamps[i].timestamp = INVALID_TS; + break; + } + } +} + /* Generate RTP message for the frame queue. */ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, @@ -232,17 +299,16 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui uint32_t timestamp = INVALID_TS; if (streamId != 0U) { - std::lock_guard lock(m_streamTSMtx); - auto entry = m_streamTimestamps.find(streamId); - if (entry != m_streamTimestamps.end()) { - timestamp = entry->second; + auto entry = findTimestamp(streamId); + if (entry != nullptr) { + timestamp = entry->timestamp; } if (timestamp != INVALID_TS) { timestamp += (RTP_GENERIC_CLOCK_RATE / 133); if (m_debug) LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, previous TS = %u, TS = %u, rtpSeq = %u", streamId, m_streamTimestamps[streamId], timestamp, rtpSeq); - m_streamTimestamps[streamId] = timestamp; + updateTimestamp(streamId, timestamp); } } @@ -265,19 +331,17 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui timestamp = (uint32_t)system_clock::ntp::now(); header.setTimestamp(timestamp); - std::lock_guard lock(m_streamTSMtx); - m_streamTimestamps.insert({ streamId, timestamp }); + insertTimestamp(streamId, timestamp); } header.encode(buffer); if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { - std::lock_guard lock(m_streamTSMtx); - auto entry = m_streamTimestamps.find(streamId); - if (entry != m_streamTimestamps.end()) { + auto entry = findTimestamp(streamId); + if (entry != nullptr) { if (m_debug) LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq); - m_streamTimestamps.erase(streamId); + eraseTimestamp(streamId); } } diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 4d60ecc6f..f873e5621 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -28,6 +28,8 @@ namespace network // Constants // --------------------------------------------------------------------------- + #define RTP_STREAM_COUNT_MAX 16384 + const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; const uint8_t DVM_RTP_PAYLOAD_TYPE = 0x56U; @@ -53,6 +55,10 @@ namespace network * @param peerId Unique ID of this modem on the network. */ FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug); + /** + * @brief Finalizes a instance of the FrameQueue class. + */ + ~FrameQueue(); /** * @brief Read message from the received UDP packet. @@ -117,8 +123,37 @@ namespace network private: uint32_t m_peerId; - std::mutex m_streamTSMtx; - std::unordered_map m_streamTimestamps; + std::mutex m_timestampMtx; + + typedef struct { + uint32_t streamId; + uint32_t timestamp; + } Timestamp; + Timestamp* m_streamTimestamps; + + /** + * @brief Search for a timestamp entry by stream ID. + * @param streamId Stream ID to find. + * @return Timestamp* Table entry. + */ + Timestamp* findTimestamp(uint32_t streamId); + /** + * @brief Insert a timestamp for a stream ID. + * @param streamId Stream ID. + * @param timestamp Timestamp. + */ + void insertTimestamp(uint32_t streamId, uint32_t timestamp); + /** + * @brief Update a timestamp for a stream ID. + * @param streamId Stream ID. + * @param timestamp Timestamp. + */ + void updateTimestamp(uint32_t streamId, uint32_t timestamp); + /** + * @brief Erase a timestamp for a stream ID. + * @param streamId Stream ID. + */ + void eraseTimestamp(uint32_t streamId); /** * @brief Generate RTP message for the frame queue. From a3d00563f932b205282b42ac88a62bd57fa4721a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 3 Jul 2025 13:37:05 -0400 Subject: [PATCH 049/133] refactor compression slightly; --- src/common/network/PacketBuffer.cpp | 6 +++--- src/common/zlib/Compression.cpp | 26 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/common/network/PacketBuffer.cpp b/src/common/network/PacketBuffer.cpp index 67c33529e..d185fcacc 100644 --- a/src/common/network/PacketBuffer.cpp +++ b/src/common/network/PacketBuffer.cpp @@ -106,7 +106,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL uint32_t compressedLen = fragments[0]->compressedSize; uint32_t len = fragments[0]->size; - buffer = new uint8_t[len +1U]; + buffer = new uint8_t[len + 1U]; ::memset(buffer, 0x00U, len + 1U); if (fragments.size() == 1U) { ::memcpy(buffer, fragments[0U]->data, len); @@ -168,8 +168,8 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length) if (m_compression) { buffer = Compression::compress(data, length, &compressedLen); } else { - buffer = std::make_unique(length); - ::memset(buffer.get(), 0x00U, length); + buffer = std::make_unique(length + 1U); + ::memset(buffer.get(), 0x00U, length + 1U); ::memcpy(buffer.get(), data, length); compressedLen = length; } diff --git a/src/common/zlib/Compression.cpp b/src/common/zlib/Compression.cpp index e1d1773c4..d3cf80d37 100644 --- a/src/common/zlib/Compression.cpp +++ b/src/common/zlib/Compression.cpp @@ -33,8 +33,8 @@ UInt8Array Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* *compressedLen = 0U; } - uint8_t* data = new uint8_t[len]; - ::memset(data, 0x00U, len); + uint8_t* data = new uint8_t[len + 1U]; + ::memset(data, 0x00U, len + 1U); ::memcpy(data, buffer, len); // compression structures @@ -89,9 +89,14 @@ UInt8Array Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* delete[] data; + if (strm.total_out == 0U) { + LogError(LOG_HOST, "ZLIB compression resulted in zero bytes of output"); + return nullptr; // return nullptr if no data was compressed + } + // return compressed data - UInt8Array out = std::make_unique(strm.total_out); - ::memset(out.get(), 0x00U, strm.total_out); + UInt8Array out = std::make_unique(strm.total_out + 1U); + ::memset(out.get(), 0x00U, strm.total_out + 1U); ::memcpy(out.get(), compressed, strm.total_out); compressedData.clear(); // clear the vector to release memory @@ -109,8 +114,8 @@ UInt8Array Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t *decompressedLen = 0U; } - uint8_t* data = new uint8_t[len]; - ::memset(data, 0x00U, len); + uint8_t* data = new uint8_t[len + 1U]; + ::memset(data, 0x00U, len + 1U); ::memcpy(data, buffer, len); // compression structures @@ -163,9 +168,14 @@ UInt8Array Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t delete[] data; + if (strm.total_out == 0U) { + LogError(LOG_HOST, "ZLIB decompression resulted in zero bytes of output"); + return nullptr; // return nullptr if no data was decompressed + } + // return decompressed data - UInt8Array out = std::make_unique(strm.total_out); - ::memset(out.get(), 0x00U, strm.total_out); + UInt8Array out = std::make_unique(strm.total_out + 1U); + ::memset(out.get(), 0x00U, strm.total_out + 1U); ::memcpy(out.get(), decompressed, strm.total_out); decompressedData.clear(); // clear the vector to release memory From a3852b9bcc3feeca90064975fa4fe6dee9fc3647 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 3 Jul 2025 14:13:28 -0400 Subject: [PATCH 050/133] better handle multi-threaded problems; --- src/common/network/FrameQueue.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 2de00cf5a..15ca32dcf 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -44,6 +44,7 @@ FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFr FrameQueue::~FrameQueue() { + std::lock_guard lock(m_timestampMtx); delete[] m_streamTimestamps; } @@ -219,6 +220,11 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::clearTimestamps() { std::lock_guard lock(m_timestampMtx); + if (m_streamTimestamps == nullptr) { + LogError(LOG_NET, "FrameQueue::clearTimestamps(), streamTimestamps is invalid, cannot clear timestamps"); + return; + } + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { m_streamTimestamps[i].streamId = 0U; m_streamTimestamps[i].timestamp = INVALID_TS; @@ -234,6 +240,11 @@ void FrameQueue::clearTimestamps() FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId) { std::lock_guard lock(m_timestampMtx); + if (m_streamTimestamps == nullptr) { + LogError(LOG_NET, "FrameQueue::findTimestamp(), streamTimestamps is invalid, cannot find timestamp for streamId %u", streamId); + return nullptr; + } + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { if (m_streamTimestamps[i].streamId == streamId) return &m_streamTimestamps[i]; @@ -247,6 +258,11 @@ FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId) void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp) { std::lock_guard lock(m_timestampMtx); + if (m_streamTimestamps == nullptr) { + LogError(LOG_NET, "FrameQueue::insertTimestamp(), streamTimestamps is invalid, cannot insert timestamp for streamId %u", streamId); + return; + } + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { if (m_streamTimestamps[i].streamId == 0U) { m_streamTimestamps[i].streamId = streamId; @@ -261,6 +277,11 @@ void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp) void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp) { std::lock_guard lock(m_timestampMtx); + if (m_streamTimestamps == nullptr) { + LogError(LOG_NET, "FrameQueue::updateTimestamp(), streamTimestamps is invalid, cannot update timestamp for streamId %u", streamId); + return; + } + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { if (m_streamTimestamps[i].streamId == streamId) { m_streamTimestamps[i].timestamp = timestamp; @@ -274,6 +295,11 @@ void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp) void FrameQueue::eraseTimestamp(uint32_t streamId) { std::lock_guard lock(m_timestampMtx); + if (m_streamTimestamps == nullptr) { + LogError(LOG_NET, "FrameQueue::eraseTimestamp(), streamTimestamps is invalid, cannot erase timestamp for streamId %u", streamId); + return; + } + for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { if (m_streamTimestamps[i].streamId == streamId) { m_streamTimestamps[i].streamId = 0U; From eccb8ba00085b4af963e2e63dc51cd7869c7272f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 7 Jul 2025 10:41:25 -0400 Subject: [PATCH 051/133] use a std::vector instead of a classical C allocated array for timestamp list; add error/logic checking for V.24/DFSI where the VHDR may send a TGID0 during a call causing a call rejection; --- src/common/network/FrameQueue.cpp | 65 +++++++------------------------ src/common/network/FrameQueue.h | 12 ++---- src/host/modem/ModemV24.cpp | 36 ++++++++++++++++- 3 files changed, 53 insertions(+), 60 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 15ca32dcf..7785d7134 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -33,19 +33,9 @@ using namespace network::frame; FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug), m_peerId(peerId), m_timestampMtx(), - m_streamTimestamps(nullptr) + m_streamTimestamps() { assert(peerId < 999999999U); - - m_streamTimestamps = new Timestamp[RTP_STREAM_COUNT_MAX]; -} - -/* Finalizes a instance of the FrameQueue class. */ - -FrameQueue::~FrameQueue() -{ - std::lock_guard lock(m_timestampMtx); - delete[] m_streamTimestamps; } /* Read message from the received UDP packet. */ @@ -220,15 +210,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::clearTimestamps() { std::lock_guard lock(m_timestampMtx); - if (m_streamTimestamps == nullptr) { - LogError(LOG_NET, "FrameQueue::clearTimestamps(), streamTimestamps is invalid, cannot clear timestamps"); - return; - } - - for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { - m_streamTimestamps[i].streamId = 0U; - m_streamTimestamps[i].timestamp = INVALID_TS; - } + m_streamTimestamps.clear(); } // --------------------------------------------------------------------------- @@ -240,12 +222,7 @@ void FrameQueue::clearTimestamps() FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId) { std::lock_guard lock(m_timestampMtx); - if (m_streamTimestamps == nullptr) { - LogError(LOG_NET, "FrameQueue::findTimestamp(), streamTimestamps is invalid, cannot find timestamp for streamId %u", streamId); - return nullptr; - } - - for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + for (size_t i = 0; i < m_streamTimestamps.size(); i++) { if (m_streamTimestamps[i].streamId == streamId) return &m_streamTimestamps[i]; } @@ -258,18 +235,13 @@ FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId) void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp) { std::lock_guard lock(m_timestampMtx); - if (m_streamTimestamps == nullptr) { - LogError(LOG_NET, "FrameQueue::insertTimestamp(), streamTimestamps is invalid, cannot insert timestamp for streamId %u", streamId); + if (streamId == 0U || timestamp == INVALID_TS) { + LogError(LOG_NET, "FrameQueue::insertTimestamp(), invalid streamId or timestamp"); return; } - for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { - if (m_streamTimestamps[i].streamId == 0U) { - m_streamTimestamps[i].streamId = streamId; - m_streamTimestamps[i].timestamp = timestamp; - break; - } - } + Timestamp entry = { streamId, timestamp }; + m_streamTimestamps.push_back(entry); } /* Update a timestamp for a stream ID. */ @@ -277,12 +249,13 @@ void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp) void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp) { std::lock_guard lock(m_timestampMtx); - if (m_streamTimestamps == nullptr) { - LogError(LOG_NET, "FrameQueue::updateTimestamp(), streamTimestamps is invalid, cannot update timestamp for streamId %u", streamId); + if (streamId == 0U || timestamp == INVALID_TS) { + LogError(LOG_NET, "FrameQueue::updateTimestamp(), invalid streamId or timestamp"); return; } - for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { + // find the timestamp entry and update it + for (size_t i = 0; i < m_streamTimestamps.size(); i++) { if (m_streamTimestamps[i].streamId == streamId) { m_streamTimestamps[i].timestamp = timestamp; break; @@ -295,18 +268,10 @@ void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp) void FrameQueue::eraseTimestamp(uint32_t streamId) { std::lock_guard lock(m_timestampMtx); - if (m_streamTimestamps == nullptr) { - LogError(LOG_NET, "FrameQueue::eraseTimestamp(), streamTimestamps is invalid, cannot erase timestamp for streamId %u", streamId); - return; - } - - for (size_t i = 0; i < RTP_STREAM_COUNT_MAX; i++) { - if (m_streamTimestamps[i].streamId == streamId) { - m_streamTimestamps[i].streamId = 0U; - m_streamTimestamps[i].timestamp = INVALID_TS; - break; - } - } + m_streamTimestamps.erase( + std::remove_if(m_streamTimestamps.begin(), m_streamTimestamps.end(), + [streamId](const Timestamp& entry) { return entry.streamId == streamId; }), + m_streamTimestamps.end()); } /* Generate RTP message for the frame queue. */ diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index f873e5621..ef85d6a3c 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -17,18 +17,18 @@ #define __FRAME_QUEUE_H__ #include "common/Defines.h" -#include "common/concurrent/unordered_map.h" #include "common/network/RTPHeader.h" #include "common/network/RTPFNEHeader.h" #include "common/network/RawFrameQueue.h" +#include +#include + namespace network { // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- - - #define RTP_STREAM_COUNT_MAX 16384 const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; @@ -55,10 +55,6 @@ namespace network * @param peerId Unique ID of this modem on the network. */ FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug); - /** - * @brief Finalizes a instance of the FrameQueue class. - */ - ~FrameQueue(); /** * @brief Read message from the received UDP packet. @@ -129,7 +125,7 @@ namespace network uint32_t streamId; uint32_t timestamp; } Timestamp; - Timestamp* m_streamTimestamps; + std::vector m_streamTimestamps; /** * @brief Search for a timestamp entry by stream ID. diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 44f898a1d..f026c67a4 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -674,7 +674,23 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); - m_rxCall->dstId = GET_UINT16(vhdr, 13U); + + if (m_rxCallInProgress && m_rxCall->dstId != 0U) { + LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header while call is in progress?, ignoring header TGID"); + } else { + uint32_t dstId = GET_UINT32(vhdr, 13U); + + // if we have a destination ID in the VHDR, set it + if (m_rxCall->dstId == 0U && dstId != 0U) { + m_rxCall->dstId = dstId; + } + + // if we don't have a destination ID, we can't continue + if (dstId == 0U && m_rxCall->dstId == 0U && m_rxCallInProgress) { + m_rxCall->dstId = 1U; // this is a terrible hack, but we need to have a destination ID to continue + LogError(LOG_MODEM, "V.24/DFSI traffic has no destination ID in VHDR, setting to default TGID 1"); + } + } if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); @@ -1246,7 +1262,23 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); - m_rxCall->dstId = GET_UINT16(vhdr, 13U); + + if (m_rxCallInProgress && m_rxCall->dstId != 0U) { + LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header while call is in progress?, ignoring header TGID"); + } else { + uint32_t dstId = GET_UINT32(vhdr, 13U); + + // if we have a destination ID in the VHDR, set it + if (m_rxCall->dstId == 0U && dstId != 0U) { + m_rxCall->dstId = dstId; + } + + // if we don't have a destination ID, we can't continue + if (dstId == 0U && m_rxCall->dstId == 0U && m_rxCallInProgress) { + m_rxCall->dstId = 1U; // this is a terrible hack, but we need to have a destination ID to continue + LogError(LOG_MODEM, "V.24/DFSI traffic has no destination ID in VHDR, setting to default TGID 1"); + } + } if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); From c0479de3136d0db57bfc08a3ef43ed14d2fd56f8 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 7 Jul 2025 12:02:07 -0400 Subject: [PATCH 052/133] make timestamps vector static; --- src/common/network/FrameQueue.cpp | 9 +++++++-- src/common/network/FrameQueue.h | 11 ++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 7785d7134..38231dba7 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -24,6 +24,12 @@ using namespace network::frame; #include #include +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::vector FrameQueue::m_streamTimestamps; + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -32,8 +38,7 @@ using namespace network::frame; FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug), m_peerId(peerId), - m_timestampMtx(), - m_streamTimestamps() + m_timestampMtx() { assert(peerId < 999999999U); } diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index ef85d6a3c..b4411ee0e 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -45,6 +45,11 @@ namespace network class HOST_SW_API FrameQueue : public RawFrameQueue { public: typedef std::pair OpcodePair; public: + typedef struct { + uint32_t streamId; + uint32_t timestamp; + } Timestamp; + auto operator=(FrameQueue&) -> FrameQueue& = delete; auto operator=(FrameQueue&&) -> FrameQueue& = delete; FrameQueue(FrameQueue&) = delete; @@ -121,11 +126,7 @@ namespace network std::mutex m_timestampMtx; - typedef struct { - uint32_t streamId; - uint32_t timestamp; - } Timestamp; - std::vector m_streamTimestamps; + static std::vector m_streamTimestamps; /** * @brief Search for a timestamp entry by stream ID. From 087b1abef90d6e2a26f3df8ba0bae171d914cd11 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 8 Jul 2025 12:26:18 -0400 Subject: [PATCH 053/133] dump out of VHDR processing if call is in progress; --- src/host/modem/ModemV24.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index f026c67a4..4b7e1781b 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -669,14 +669,9 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } - ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); - - m_rxCall->mfId = vhdr[9U]; - m_rxCall->algoId = vhdr[10U]; - m_rxCall->kId = GET_UINT16(vhdr, 11U); - if (m_rxCallInProgress && m_rxCall->dstId != 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header while call is in progress?, ignoring header TGID"); + break; } else { uint32_t dstId = GET_UINT32(vhdr, 13U); @@ -696,6 +691,12 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); } + ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); + + m_rxCall->mfId = vhdr[9U]; + m_rxCall->algoId = vhdr[10U]; + m_rxCall->kId = GET_UINT16(vhdr, 11U); + // generate a HDU lc::LC lc = lc::LC(); lc.setDstId(m_rxCall->dstId); @@ -1257,14 +1258,9 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } - ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); - - m_rxCall->mfId = vhdr[9U]; - m_rxCall->algoId = vhdr[10U]; - m_rxCall->kId = GET_UINT16(vhdr, 11U); - if (m_rxCallInProgress && m_rxCall->dstId != 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header while call is in progress?, ignoring header TGID"); + break; } else { uint32_t dstId = GET_UINT32(vhdr, 13U); @@ -1280,6 +1276,12 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } } + ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); + + m_rxCall->mfId = vhdr[9U]; + m_rxCall->algoId = vhdr[10U]; + m_rxCall->kId = GET_UINT16(vhdr, 11U); + if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); } From 8602935318ef67ee349c4ae3f8a5e328fb311dbd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 11 Jul 2025 10:58:25 -0400 Subject: [PATCH 054/133] bump tarball version build stamp; --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d64beb349..d3e486a4b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,7 +61,7 @@ set(CPACK_PACKAGE_VENDOR "DVMProject") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") -set(CPACK_DEBIAN_PACKAGE_VERSION "R04Hxx") +set(CPACK_DEBIAN_PACKAGE_VERSION "R04Jxx") set(CPACK_DEBIAN_PACKAGE_RELEASE "0") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") From 28bd5a882375799bb7ee13b7aae37f225803d372 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 11 Jul 2025 15:30:23 -0400 Subject: [PATCH 055/133] add option to forcibly allow TGID0 for badly behaved systems this will cause dvmhost to accept the call and not drop it, with the caveat that the TGID will be rewritten to TGID1, the network stack simply cannot service a TGID of 0 and as such we must rewrite the TGID a TIA-102 valid TGID; --- configs/config.example.yml | 4 ++++ src/common/p25/acl/AccessControl.cpp | 8 ++++++-- src/common/p25/acl/AccessControl.h | 3 ++- src/host/p25/Control.cpp | 10 ++++++++++ src/host/p25/Control.h | 1 + src/host/p25/packet/Voice.cpp | 8 +++++++- 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index 0538343c1..469d5a900 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -244,6 +244,10 @@ protocols: # (This applies only in conventional operations where channel granting is utilized and RF-only talkgroup # steering is required.) disableNetworkGrant: false + # Flag indicating whether or not TGID 0 (P25 blackhole talkgroup) will be allowed. + # (Normally this should *never* be enabled, it is here to support poorly behaved systems that transmit + # TGID 0 for some reason.) + forceAllowTG0: false # Flag indicating whether or not a TGID will be tested for affiliations before being granted. ignoreAffiliationCheck: false # Flag indicating that the host will attempt to automatically inhibit unauthorized RIDs (those not in the diff --git a/src/common/p25/acl/AccessControl.cpp b/src/common/p25/acl/AccessControl.cpp index 881902bc8..2c0fd7915 100644 --- a/src/common/p25/acl/AccessControl.cpp +++ b/src/common/p25/acl/AccessControl.cpp @@ -53,12 +53,16 @@ bool AccessControl::validateSrcId(uint32_t id) /* Helper to validate a talkgroup ID. */ -bool AccessControl::validateTGId(uint32_t id) +bool AccessControl::validateTGId(uint32_t id, bool allowZero) { // TG0 is never valid - if (id == 0U) + if (id == 0U && !allowZero) return false; + // TG0 is always valid if allow zero is set + if (id == 0U && allowZero) + return true; + // check if TID ACLs are enabled if (!m_tidLookup->getACL()) { return true; diff --git a/src/common/p25/acl/AccessControl.h b/src/common/p25/acl/AccessControl.h index b14593b15..7f1baf76d 100644 --- a/src/common/p25/acl/AccessControl.h +++ b/src/common/p25/acl/AccessControl.h @@ -54,9 +54,10 @@ namespace p25 /** * @brief Helper to validate a talkgroup ID. * @param id Talkgroup ID (TGID). + * @param allowZero Flag indicating whether TGID 0 is allowed or not. * @returns bool True, if talkgroup ID is valid, otherwise false. */ - static bool validateTGId(uint32_t id); + static bool validateTGId(uint32_t id, bool allowZero = false); /** * @brief Helper to determine if a talkgroup ID is non-preferred. diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 7741baf67..5e75b23f0 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -81,6 +81,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_ignoreAffiliationCheck(false), m_demandUnitRegForRefusedAff(true), m_dfsiFDX(false), + m_forceAllowTG0(false), m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), @@ -353,6 +354,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_notifyCC = false; } + m_forceAllowTG0 = p25Protocol["forceAllowTG0"].as(false); + if (m_forceAllowTG0) { + LogWarning(LOG_P25, "TGID 0 (P25 blackhole talkgroup) will be allowed. This is not recommended, and can cause undesired behavior, it is typically only needed by poorly behaved systems."); + } + /* ** Voice Silence and Frame Loss Thresholds */ @@ -502,6 +508,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" DFSI Full Duplex: yes"); } + if (m_forceAllowTG0) { + LogInfo(" Force Allow TGID 0: yes"); + } + LogInfo(" Patch Super Group: $%04X", m_control->m_patchSuperGroup); LogInfo(" Announcement Group: $%04X", m_control->m_announcementGroup); diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index b97094ee3..5e9673d4f 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -312,6 +312,7 @@ namespace p25 bool m_ignoreAffiliationCheck; bool m_demandUnitRegForRefusedAff; bool m_dfsiFDX; + bool m_forceAllowTG0; ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index a1f6a98a3..40600cfdd 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -345,7 +345,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } else { // validate the target ID, if the target is a talkgroup - if (!acl::AccessControl::validateTGId(dstId)) { + if (!acl::AccessControl::validateTGId(dstId, m_p25->m_forceAllowTG0)) { if (m_lastRejectId == 0 || m_lastRejectId != dstId) { LogWarning(LOG_RF, P25_HDU_STR " denial, TGID rejection, dstId = %u", dstId); if (m_p25->m_enableControl) { @@ -364,6 +364,12 @@ bool Voice::process(uint8_t* data, uint32_t len) } } + if (group && dstId == 0U && m_p25->m_forceAllowTG0) { + LogWarning(LOG_RF, P25_HDU_STR " TGID 0 (P25 blackhole talkgroup) detected, srcId = %u", srcId); + dstId = 1U; // force destination ID to TGID 1 -- TGID 0 is not allowed in P25, and the network won't properly handle it + lc.setDstId(dstId); + } + // verify the source RID is affiliated to the group TGID; only if control data // is supported if (group && m_p25->m_enableControl) { From a7bb2f7bae23b0634d5dba73dabca6cf698c2010 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 12 Jul 2025 13:24:15 -0400 Subject: [PATCH 056/133] simplify late entry handling where V.24 may send a dstId of 0; --- src/host/modem/ModemV24.cpp | 40 ++++++++----------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 4b7e1781b..ac9515e93 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -670,25 +670,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } if (m_rxCallInProgress && m_rxCall->dstId != 0U) { - LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header while call is in progress?, ignoring header TGID"); + LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; - } else { - uint32_t dstId = GET_UINT32(vhdr, 13U); - - // if we have a destination ID in the VHDR, set it - if (m_rxCall->dstId == 0U && dstId != 0U) { - m_rxCall->dstId = dstId; - } - - // if we don't have a destination ID, we can't continue - if (dstId == 0U && m_rxCall->dstId == 0U && m_rxCallInProgress) { - m_rxCall->dstId = 1U; // this is a terrible hack, but we need to have a destination ID to continue - LogError(LOG_MODEM, "V.24/DFSI traffic has no destination ID in VHDR, setting to default TGID 1"); - } - } - - if (m_debug) { - LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); } ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); @@ -696,6 +679,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); + m_rxCall->dstId = GET_UINT32(vhdr, 13U); + + if (m_debug) { + LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); + } // generate a HDU lc::LC lc = lc::LC(); @@ -1259,21 +1247,8 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } if (m_rxCallInProgress && m_rxCall->dstId != 0U) { - LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header while call is in progress?, ignoring header TGID"); + LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; - } else { - uint32_t dstId = GET_UINT32(vhdr, 13U); - - // if we have a destination ID in the VHDR, set it - if (m_rxCall->dstId == 0U && dstId != 0U) { - m_rxCall->dstId = dstId; - } - - // if we don't have a destination ID, we can't continue - if (dstId == 0U && m_rxCall->dstId == 0U && m_rxCallInProgress) { - m_rxCall->dstId = 1U; // this is a terrible hack, but we need to have a destination ID to continue - LogError(LOG_MODEM, "V.24/DFSI traffic has no destination ID in VHDR, setting to default TGID 1"); - } } ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); @@ -1281,6 +1256,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); + m_rxCall->dstId = GET_UINT32(vhdr, 13U); if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); From d9bfeed8bb7d945f512c60f18a534bea55d65e61 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 12 Jul 2025 13:27:39 -0400 Subject: [PATCH 057/133] whoops correct my own stupidity; --- src/host/modem/ModemV24.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index ac9515e93..072e42a9f 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -669,7 +669,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } - if (m_rxCallInProgress && m_rxCall->dstId != 0U) { + uint32_t dstId = GET_UINT32(vhdr, 13U); + if (m_rxCallInProgress && dstId != 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } @@ -679,7 +680,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); - m_rxCall->dstId = GET_UINT32(vhdr, 13U); + m_rxCall->dstId = dstId; if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); @@ -1246,7 +1247,8 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } - if (m_rxCallInProgress && m_rxCall->dstId != 0U) { + uint32_t dstId = GET_UINT32(vhdr, 13U); + if (m_rxCallInProgress && dstId != 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } @@ -1256,7 +1258,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); - m_rxCall->dstId = GET_UINT32(vhdr, 13U); + m_rxCall->dstId = dstId; if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); From 1886b4c8893ca4a02beae05deb7e9cb63aeadd7e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 12 Jul 2025 13:28:30 -0400 Subject: [PATCH 058/133] whoops correct my own stupidity (again); --- src/host/modem/ModemV24.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 072e42a9f..4ee9764fb 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -670,7 +670,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } uint32_t dstId = GET_UINT32(vhdr, 13U); - if (m_rxCallInProgress && dstId != 0U) { + if (m_rxCallInProgress && dstId == 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } @@ -1248,7 +1248,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } uint32_t dstId = GET_UINT32(vhdr, 13U); - if (m_rxCallInProgress && dstId != 0U) { + if (m_rxCallInProgress && dstId == 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } From 0073cc12586a22756436f5bae7dcb2a4e940450f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 14 Jul 2025 11:25:46 -0400 Subject: [PATCH 059/133] add opcodes for Harris User Alias and GPS; add opcode for Motorola GPS on PTT; decode Harris User Alias in LDU LCs; --- src/common/p25/P25Defines.h | 19 +++++++++- src/common/p25/lc/LC.cpp | 72 ++++++++++++++++++++++++++++++++++++- src/common/p25/lc/LC.h | 11 ++++++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index b88e56a11..6c11b9493 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -112,6 +112,7 @@ namespace p25 const uint8_t ENCRYPTED_NULL_IMBE[] = { 0xFCU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; const uint8_t MOT_CALLSIGN_LENGTH_BYTES = 8U; + const uint8_t HARRIS_USER_ALIAS_LENGTH_BYTES = 14U; const uint8_t AUTH_RES_LENGTH_BYTES = 4U; const uint8_t AUTH_RAND_SEED_LENGTH_BYTES = 10U; @@ -138,6 +139,8 @@ namespace p25 /** @brief Motorola MFId */ const uint8_t MFG_MOT = 0x90U; + /** @brief L3Harris MFId */ + const uint8_t MFG_HARRIS = 0xA4U; /** @brief DVM; Omaha Communication Systems, LLC ($9C) */ const uint8_t MFG_DVM_OCS = 0x9CU; /** @} */ @@ -692,7 +695,21 @@ namespace p25 CONV_FALLBACK = 0x2AU, //! CONV FALLBACK - Conventional Fallback // LDUx/TDULC Motorola Link Control Opcode(s) - FAILSOFT = 0x02U //! FAILSOFT - Failsoft + FAILSOFT = 0x02U, //! FAILSOFT - Failsoft + + MOT_PTT_LOC_HEADER = 0x29U, //! MOT PTT LOC HEADER - Motorola PTT Location Header + MOT_PTT_LOC_PAYLOAD = 0x2AU, //! MOT PTT LOC PAYLOAD - Motorola PTT Location Payload + + // LDUx/TDULC Harris Link Control Opcode(s) + HARRIS_PTT_PA_ODD = 0x2AU, //! HARRIS PTT PA ODD - Harris PTT Position and Altitude Odd + HARRIS_PTT_PB_ODD = 0x2BU, //! HARRIS PTT PB ODD - Harris PTT Position and Bearing Odd + HARRIS_PTT_PA_EVEN = 0x2CU, //! HARRIS PTT PA EVEN - Harris PTT Position and Altitude Even + HARRIS_PTT_PB_EVEN = 0x2DU, //! HARRIS PTT PB EVEN - Harris PTT Position and Bearing Even + + HARRIS_USER_ALIAS_PA_ODD = 0x32U, //! HARRIS USER ALIAS PA ODD - Harris User Alias Position and Altitude Odd + HARRIS_USER_ALIAS_PB_ODD = 0x33U, //! HARRIS USER ALIAS PB ODD - Harris User Alias Position and Bearing Odd + HARRIS_USER_ALIAS_PA_EVEN = 0x34U, //! HARRIS USER ALIAS PA EVEN - Harris User Alias Position and Altitude Even + HARRIS_USER_ALIAS_PB_EVEN = 0x35U, //! HARRIS USER ALIAS PB EVEN - Harris User Alias Position and Bearing Even }; } diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 9d67e65cb..4f97d7adf 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -67,10 +67,16 @@ LC::LC() : m_encryptOverride(false), m_tsbkVendorSkip(false), m_callTimer(0U), - m_mi(nullptr) + m_mi(nullptr), + m_userAlias(nullptr), + m_gotUserAliasPartA(false), + m_gotUserAlias(false) { m_mi = new uint8_t[MI_LENGTH_BYTES]; ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); } /* Finalizes a instance of LC class. */ @@ -81,6 +87,11 @@ LC::~LC() delete[] m_mi; m_mi = nullptr; } + + if (m_userAlias != nullptr) { + delete[] m_userAlias; + m_userAlias = nullptr; + } } /* Equals operator. */ @@ -484,6 +495,23 @@ void LC::getMI(uint8_t* mi) const ::memcpy(mi, m_mi, MI_LENGTH_BYTES); } +/* +** User Alias data +*/ + +/* Gets the user alias. */ + +std::string LC::getUserAlias() const +{ + std::string alias; + if (m_gotUserAlias) { + for (uint32_t i = 0; i < HARRIS_USER_ALIAS_LENGTH_BYTES; i++) + alias[i] = m_userAlias[i]; + } + + return alias; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -546,6 +574,20 @@ void LC::copy(const LC& data) } } + if (data.m_gotUserAlias && data.m_userAlias != nullptr) { + delete[] m_userAlias; + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memcpy(m_userAlias, data.m_userAlias, HARRIS_USER_ALIAS_LENGTH_BYTES); + m_gotUserAlias = data.m_gotUserAlias; + } else { + delete[] m_userAlias; + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + m_gotUserAlias = false; + } + m_siteData = data.m_siteData; } @@ -578,6 +620,34 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) // as the packed RS value) if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { //Utils::dump(1U, "Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + if (m_mfId == MFG_HARRIS) { + // Harris P25 opcodes + switch (m_lco) { + case LCO::HARRIS_USER_ALIAS_PA_ODD: + case LCO::HARRIS_USER_ALIAS_PA_EVEN: + m_gotUserAliasPartA = true; + m_gotUserAlias = false; + + if (m_userAlias != nullptr) { + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + ::memcpy(m_userAlias, rs + 2U, 7U); + m_gotUserAlias = true; + } + break; + + case LCO::HARRIS_USER_ALIAS_PB_ODD: + case LCO::HARRIS_USER_ALIAS_PB_EVEN: + if (m_gotUserAliasPartA && (m_userAlias != nullptr)) { + ::memcpy(m_userAlias + 7U, rs + 2U, 7U); + m_gotUserAlias = true; + } + break; + + default: + break; + } + } + return true; } diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index 0a54f9338..efcb7bf0b 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -128,6 +128,12 @@ namespace p25 void getMI(uint8_t* mi) const; /** @} */ + /** @name User Alias data */ + /** + * @brief Gets the user alias. + */ + std::string getUserAlias() const; + /** @name Local Site data */ /** * @brief Gets the local site data. @@ -243,6 +249,11 @@ namespace p25 // Encryption data uint8_t* m_mi; + // User Alias data + uint8_t* m_userAlias; + bool m_gotUserAliasPartA; + bool m_gotUserAlias; + // Local Site data static SiteData m_siteData; From a71381eaa5344d3e6dc0a5c60833e8cd6f15417c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 15 Jul 2025 09:57:43 -0400 Subject: [PATCH 060/133] attempt at decoding the LDU RS values for both V.24 and TIA DFSI, this does not abort decoding in the ModemV24 right now and will simply log an error if the RS values for the LDU1 or LDU2 are to badly invalid; --- src/host/modem/ModemV24.cpp | 210 ++++++++++++++++++++++++++++++++++++ src/host/modem/ModemV24.h | 11 ++ 2 files changed, 221 insertions(+) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 4ee9764fb..4aaf7a278 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -720,6 +720,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU1_VOICE1: { MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); m_rxCall->n++; } @@ -727,6 +728,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU2_VOICE10: { MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); m_rxCall->n++; } @@ -850,6 +852,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->lco = voice.additionalData[0U]; m_rxCall->mfId = voice.additionalData[1U]; m_rxCall->serviceOptions = voice.additionalData[2U]; + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); } @@ -860,6 +867,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->dstId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); } @@ -870,6 +882,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->srcId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); } @@ -878,16 +895,40 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU1_VOICE6: { ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC6 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE7: { ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC7 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE8: { ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC8 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE9: @@ -901,6 +942,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } } break; + case DFSIFrameType::LDU2_VOICE11: { ::memcpy(m_rxCall->netLDU2 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); @@ -911,6 +953,12 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); } @@ -921,6 +969,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); } @@ -931,6 +984,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); } @@ -942,6 +1000,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) if (voice.additionalData != nullptr) { m_rxCall->algoId = voice.additionalData[0U]; m_rxCall->kId = GET_UINT16(voice.additionalData, 1U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); } @@ -950,11 +1013,27 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU2_VOICE16: { ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC16 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE17: { ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC17 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE18: @@ -981,6 +1060,19 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) // encode LDU1 if ready if (m_rxCall->n == 9U) { + // decode RS (24,12,13) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode241213(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI LDU1, failed to decode RS (24,12,13) FEC"); + } + } + catch (...) { + Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); @@ -1060,6 +1152,19 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) // encode LDU2 if ready if (m_rxCall->n == 18U) { + // decode RS (24,16,9) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode24169(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI LDU2, failed to decode RS (24,16,9) FEC"); + } + } + catch (...) { + Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); @@ -1311,6 +1416,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) switch (frameType) { case DFSIFrameType::LDU1_VOICE1: { + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; @@ -1326,6 +1432,11 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->lco = voice.additionalData[0U]; m_rxCall->mfId = voice.additionalData[1U]; m_rxCall->serviceOptions = voice.additionalData[2U]; + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); } @@ -1336,6 +1447,12 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->dstId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); } @@ -1346,6 +1463,12 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->srcId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); } @@ -1354,16 +1477,40 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU1_VOICE6: { ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC6 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE7: { ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC7 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE8: { ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC8 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE9: @@ -1380,6 +1527,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU2_VOICE10: { + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; @@ -1393,6 +1541,11 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); } @@ -1403,6 +1556,11 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); } @@ -1413,6 +1571,11 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); } @@ -1424,6 +1587,11 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) if (voice.additionalData != nullptr) { m_rxCall->algoId = voice.additionalData[0U]; m_rxCall->kId = GET_UINT16(voice.additionalData, 1U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); } @@ -1432,11 +1600,27 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU2_VOICE16: { ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC16 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE17: { ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC17 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE18: @@ -1471,6 +1655,19 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) // encode LDU1 if ready if (m_rxCall->n == 9U) { + // decode RS (24,12,13) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode241213(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI LDU1, failed to decode RS (24,12,13) FEC"); + } + } + catch (...) { + Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); @@ -1550,6 +1747,19 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) // encode LDU2 if ready if (m_rxCall->n == 18U) { + // decode RS (24,16,9) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode24169(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI LDU2, failed to decode RS (24,16,9) FEC"); + } + } + catch (...) { + Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index f4069f91a..1ed18b3be 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -85,6 +85,7 @@ namespace modem MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; VHDR1 = new uint8_t[p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH]; VHDR2 = new uint8_t[p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH]; + LDULC = new uint8_t[P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES]; netLDU1 = new uint8_t[9U * 25U]; netLDU2 = new uint8_t[9U * 25U]; @@ -105,6 +106,8 @@ namespace modem delete[] VHDR1; if (VHDR2 != nullptr) delete[] VHDR2; + if (LDULC != nullptr) + delete[] LDULC; if (netLDU1 != nullptr) delete[] netLDU1; if (netLDU2 != nullptr) @@ -136,6 +139,9 @@ namespace modem if (VHDR2 != nullptr) ::memset(VHDR2, 0x00U, p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH); + if (LDULC != nullptr) + ::memset(LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + if (netLDU1 != nullptr) ::memset(netLDU1, 0x00U, 9U * 25U); if (netLDU2 != nullptr) @@ -200,6 +206,11 @@ namespace modem */ uint8_t* VHDR2; + /** + * @brief LDU LC. + */ + uint8_t* LDULC; + /** * @brief Sequence Number. */ From 7813cd63c0d96b982f39da107a0349b89fcff414 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 15 Jul 2025 10:10:25 -0400 Subject: [PATCH 061/133] alter field data based on recent testing; enhance and update debug log entries; add debug logging for Voice 1 and 10 start of voice frames for V.24; --- .../p25/dfsi/frames/MotStartVoiceFrame.cpp | 30 ++++++++++--------- .../p25/dfsi/frames/MotStartVoiceFrame.h | 13 ++++---- .../p25/dfsi/frames/MotVoiceHeader1.cpp | 26 ++++++++-------- src/common/p25/dfsi/frames/MotVoiceHeader1.h | 11 ++++--- src/host/modem/ModemV24.cpp | 22 ++++++++++---- 5 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp b/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp index 91070f4fd..d037f99f0 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp @@ -31,9 +31,8 @@ MotStartVoiceFrame::MotStartVoiceFrame() : startOfStream(nullptr), fullRateVoice(nullptr), m_icw(ICWFlag::DIU), - m_rssi(0U), m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U), + m_rssi(0U), m_adjMM(0U) { startOfStream = new MotStartOfStream(); @@ -46,9 +45,8 @@ MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data) : startOfStream(nullptr), fullRateVoice(nullptr), m_icw(ICWFlag::DIU), - m_rssi(0U), m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U), + m_rssi(0U), m_adjMM(0U) { decode(data); @@ -95,11 +93,13 @@ bool MotStartVoiceFrame::decode(const uint8_t* data) fullRateVoice->decode(voiceBuffer, true); // get rest of data - m_icw = (ICWFlag::E)data[5U]; - m_rssi = data[6U]; - m_rssiValidity = (RssiValidityFlag::E)data[7U]; - m_nRssi = data[8U]; - m_adjMM = data[9U]; + m_icw = (ICWFlag::E)data[5U]; // this field is dubious and questionable + //data[6U]; // unknown -- based on testing this is not related to RSSI + m_rssiValidity = (RssiValidityFlag::E)data[7U]; // this field is dubious and questionable + + m_rssi = data[8U]; + + m_adjMM = data[9U]; // this field is dubious and questionable return true; } @@ -131,9 +131,11 @@ void MotStartVoiceFrame::encode(uint8_t* data) } // Copy the rest - data[5U] = m_icw; - data[6U] = m_rssi; - data[7U] = m_rssiValidity; - data[8U] = m_nRssi; - data[9U] = m_adjMM; + data[5U] = m_icw; // this field is dubious and questionable + data[6U] = 0U; // unknown -- based on testing this is not related to RSSI + data[7U] = m_rssiValidity; // this field is dubious and questionable + + data[8U] = m_rssi; + + data[9U] = m_adjMM; // this field is dubious and questionable } diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h index 0f2877240..966af5897 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h @@ -91,22 +91,23 @@ namespace p25 /** * @brief + * @note bryanb: I doubt the authenticity of this field. */ DECLARE_PROPERTY(ICWFlag::E, icw, ICW); - /** - * @brief RSSI Value. - */ - DECLARE_PROPERTY(uint8_t, rssi, RSSI); /** * @brief Flag indicating whether or not the RSSI field is valid. + * @note bryanb: I doubt the authenticity of this field. */ DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity); + /** - * @brief + * @brief RSSI Value. */ - DECLARE_PROPERTY(uint8_t, nRssi, NRSSI); + DECLARE_PROPERTY(uint8_t, rssi, RSSI); + /** * @brief + * @note bryanb: I doubt the authenticity of this field. */ DECLARE_PROPERTY(uint8_t, adjMM, AdjMM); }; diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp b/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp index 908cd831f..4ab21afc7 100644 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp +++ b/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp @@ -31,9 +31,8 @@ MotVoiceHeader1::MotVoiceHeader1() : header(nullptr), startOfStream(nullptr), m_icw(ICWFlag::DIU), - m_rssi(0U), m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U) + m_rssi(0U) { startOfStream = new MotStartOfStream(); @@ -47,9 +46,8 @@ MotVoiceHeader1::MotVoiceHeader1(uint8_t* data) : header(nullptr), startOfStream(nullptr), m_icw(ICWFlag::DIU), - m_rssi(0U), m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U) + m_rssi(0U) { decode(data); } @@ -78,15 +76,16 @@ bool MotVoiceHeader1::decode(const uint8_t* data) uint8_t buffer[MotStartOfStream::LENGTH]; ::memset(buffer, 0x00U, MotStartOfStream::LENGTH); - // we copy the bytes from [1:4] + // we copy the bytes from [1:4] ::memcpy(buffer + 1U, data + 1U, 4); startOfStream->decode(buffer); // decode the other stuff - m_icw = (ICWFlag::E)data[5U]; - m_rssi = data[6U]; - m_rssiValidity = (RssiValidityFlag::E)data[7U]; - m_nRssi = data[8U]; + m_icw = (ICWFlag::E)data[5U]; // this field is dubious and questionable + //data[6U]; // unknown -- based on testing this is not related to RSSI + m_rssiValidity = (RssiValidityFlag::E)data[7U]; // this field is dubious and questionable + + m_rssi = data[8U]; // our header includes the trailing source and check bytes if (header != nullptr) @@ -117,10 +116,11 @@ void MotVoiceHeader1::encode(uint8_t* data) ::memcpy(data + 1U, buffer + 1U, 4U); } - data[5U] = m_icw; - data[6U] = m_rssi; - data[7U] = m_rssiValidity; - data[8U] = m_nRssi; + data[5U] = m_icw; // this field is dubious and questionable + data[6U] = 0U; // unknown -- based on testing this is not related to RSSI + data[7U] = m_rssiValidity; // this field is dubious and questionable + + data[8U] = m_rssi; // our header includes the trailing source and check bytes if (header != nullptr) { diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.h b/src/common/p25/dfsi/frames/MotVoiceHeader1.h index 05951a0a8..a4e5d807e 100644 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.h +++ b/src/common/p25/dfsi/frames/MotVoiceHeader1.h @@ -95,20 +95,19 @@ namespace p25 /** * @brief + * @note bryanb: I doubt the authenticity of this field. */ DECLARE_PROPERTY(ICWFlag::E, icw, ICW); - /** - * @brief RSSI Value. - */ - DECLARE_PROPERTY(uint8_t, rssi, RSSI); /** * @brief Flag indicating whether or not the RSSI field is valid. + * @note bryanb: I doubt the authenticity of this field. */ DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity); + /** - * @brief + * @brief RSSI Value. */ - DECLARE_PROPERTY(uint8_t, nRssi, NRSSI); + DECLARE_PROPERTY(uint8_t, rssi, RSSI); }; } // namespace frames } // namespace dfsi diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 4aaf7a278..04421111e 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -588,7 +588,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) - Utils::dump("V24 RX data from board", dfsiData, length - 1U); + Utils::dump("V.24 RX data from board", dfsiData, length - 1U); DFSIFrameType::E frameType = (DFSIFrameType::E)dfsiData[0U]; m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -602,14 +602,14 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->resetCallData(); m_rxCallInProgress = true; if (m_debug) { - LogDebug(LOG_MODEM, "V24 RX, ICW START, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW START, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); } } else { if (m_rxCallInProgress) { m_rxCall->resetCallData(); m_rxCallInProgress = false; if (m_debug) { - LogDebug(LOG_MODEM, "V24 RX, ICW STOP, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW STOP, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); } // generate a TDU create_TDU(buffer); @@ -666,7 +666,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCallInProgress = true; m_rxCall->resetCallData(); if (m_debug) - LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX VHDR late entry, resetting call data"); } uint32_t dstId = GET_UINT32(vhdr, 13U); @@ -722,6 +722,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU1, ICW = $%02X, RSSIValid = $%02X, RSSI = $%02X, AdjMM = $%02X", svf.getICW(), svf.getRSSIValidity(), svf.getRSSI(), svf.getAdjMM()); + } + m_rxCall->n++; } break; @@ -730,6 +735,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU2, ICW = $%02X, RSSIValid = $%02X, RSSI = $%02X, AdjMM = $%02X", svf.getICW(), svf.getRSSIValidity(), svf.getRSSI(), svf.getAdjMM()); + } + m_rxCall->n++; } break; @@ -2227,7 +2237,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) if (!m_txCallInProgress) { startOfStream(lc); if (m_debug) - LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAir()", "V.24 TX VHDR late entry, resetting TX call data"); } // generate audio @@ -2563,7 +2573,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) if (!m_txCallInProgress) { startOfStreamTIA(lc); if (m_debug) - LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirTIA()", "DFSI TX VHDR late entry, resetting TX call data"); } // generate audio From 453755cbb3ca216e31afb81a4b42927310ced4dc Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 15 Jul 2025 10:14:31 -0400 Subject: [PATCH 062/133] add missing tag information for V.24 PDU; --- src/host/modem/ModemV24.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 04421111e..1be8a3812 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -784,6 +784,9 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + // add the data uint32_t newBitLength = P25Utils::encodeByLength(data, buffer + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; From a24602d763539e7cb6575bff8048a8e1045f604d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 25 Jul 2025 15:55:50 -0400 Subject: [PATCH 063/133] fix incorrect handling of RFSS ID in host setup; --- src/host/setup/SiteParamSetWnd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/setup/SiteParamSetWnd.h b/src/host/setup/SiteParamSetWnd.h index 251a3f024..b6c1bc493 100644 --- a/src/host/setup/SiteParamSetWnd.h +++ b/src/host/setup/SiteParamSetWnd.h @@ -217,7 +217,7 @@ class HOST_SW_API SiteParamSetWnd final : public CloseWndBase { m_p25RfssIdLabel.setGeometry(FPoint(2, 14), FSize(20, 1)); m_p25RfssId.setGeometry(FPoint(23, 14), FSize(10, 1)); - m_p25RfssId.setText(rfssConfig["dmrNetId"].as("1").c_str()); + m_p25RfssId.setText(rfssConfig["rfssId"].as("1").c_str()); m_p25RfssId.setShadow(false); m_p25RfssId.setMaxLength(3); m_p25RfssId.setInputFilter("[[:xdigit:]]"); From efe1357ef079d0398421380922d73d2750113eb1 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 27 Jul 2025 15:56:58 -0400 Subject: [PATCH 064/133] experimental fix to ignore wildly illegal TGIDs in the HDU; --- src/host/p25/packet/Voice.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 40600cfdd..9ab3bf4bd 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -183,6 +183,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_p25->m_rfLastDstId = lc.getDstId(); m_p25->m_rfLastSrcId = lc.getSrcId(); + // is the TGID presented in the HDU valid? + if (!acl::AccessControl::validateTGId(lc.getDstId(), m_p25->m_forceAllowTG0)) { + m_p25->m_rfLastDstId = 0U; + } + m_rfLastHDU = lc; m_rfLastHDUValid = true; @@ -294,6 +299,12 @@ bool Voice::process(uint8_t* data, uint32_t len) m_p25->m_txQueue.clear(); resetRF(); + } else { + // is the TGID presented in the HDU valid? + if (m_p25->m_rfLastDstId == 0U && !acl::AccessControl::validateTGId(m_rfLastHDU.getDstId(), m_p25->m_forceAllowTG0)) { + // the TGID in the HDU isn't valid -- we'll override it with the one in our LDU1 + m_p25->m_rfLastDstId = dstId; + } } if (m_p25->m_enableControl) { From 1c07853c4170f294de192ff48d22252023f729de Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 27 Jul 2025 16:09:27 -0400 Subject: [PATCH 065/133] simplify the V.24 HDU TGID logic, entirely ignore the TGID presented in the HDU and just wait for the LDU1 to set the rfLastDstId; --- src/host/p25/packet/Voice.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 9ab3bf4bd..31940862d 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -183,8 +183,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_p25->m_rfLastDstId = lc.getDstId(); m_p25->m_rfLastSrcId = lc.getSrcId(); - // is the TGID presented in the HDU valid? - if (!acl::AccessControl::validateTGId(lc.getDstId(), m_p25->m_forceAllowTG0)) { + // if we're DFSI/V.24 -- ignore the TGID presented in the HDU + if (m_p25->m_isModemDFSI) { m_p25->m_rfLastDstId = 0U; } @@ -299,12 +299,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_p25->m_txQueue.clear(); resetRF(); - } else { - // is the TGID presented in the HDU valid? - if (m_p25->m_rfLastDstId == 0U && !acl::AccessControl::validateTGId(m_rfLastHDU.getDstId(), m_p25->m_forceAllowTG0)) { - // the TGID in the HDU isn't valid -- we'll override it with the one in our LDU1 - m_p25->m_rfLastDstId = dstId; - } + } + + if (m_p25->m_rfLastDstId == 0U && m_p25->m_isModemDFSI) { + // if we're DFSI/V.24 -- use the TGID presented in the LDU1 + m_p25->m_rfLastDstId = dstId; } if (m_p25->m_enableControl) { From 3959e85f2b82d44ba412871cae06e754dfe7edc6 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Jul 2025 19:53:45 -0400 Subject: [PATCH 066/133] add grantDemand documentation to dvmpatch; --- configs/patch-config.example.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index b0fedfda8..d1e2e1c91 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -82,6 +82,9 @@ system: # Digital mode (1 - DMR, 2 - P25). digiMode: 1 + # Flag indicating whether a network grant demand packet will be sent before audio. + grantDemand: false + # Flag indicating whether or not the patch is from/to a MMDVM P25 reflector. mmdvmP25Reflector: false From e602a2e0e66c43f4dcae7e1ed4bcc3cfe32c1640 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Jul 2025 20:35:29 -0400 Subject: [PATCH 067/133] [EXPERIMENTAL] add experimental support for cross-encrypting patched P25 traffic; --- configs/patch-config.example.yml | 23 +++ src/patch/HostPatch.cpp | 324 ++++++++++++++++++++++++++++++- src/patch/HostPatch.h | 28 +++ 3 files changed, 374 insertions(+), 1 deletion(-) diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index d1e2e1c91..e93f9eae7 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -60,11 +60,34 @@ network: sourceTGID: 1 # Source Slot for received/transmitted audio frames. sourceSlot: 1 + + # Source Traffic Encryption + srcTek: + # Flag indicating whether or not traffic encryption is enabled. + enable: false + # Traffic Encryption Key Algorithm + # aes - AES-256 Encryption + # arc4 - ARC4/ADP Encryption + tekAlgo: "aes" + # Traffic Encryption Key ID + tekKeyId: 1 + # Destination Talkgroup ID for transmitted/received audio frames. destinationTGID: 1 # Destination Slot for received/transmitted audio frames. destinationSlot: 1 + # Destination Traffic Encryption + dstTek: + # Flag indicating whether or not traffic encryption is enabled. + enable: false + # Traffic Encryption Key Algorithm + # aes - AES-256 Encryption + # arc4 - ARC4/ADP Encryption + tekAlgo: "aes" + # Traffic Encryption Key ID + tekKeyId: 1 + # Flag indicating whether or not the patch is two-way. twoWay: false diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 081c2200f..503d6e266 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -45,6 +45,13 @@ using namespace network::udp; #include #endif // !defined(_WIN32) +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define TEK_AES "aes" +#define TEK_ARC4 "arc4" + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -79,8 +86,17 @@ HostPatch::HostPatch(const std::string& confFile) : m_dmrEmbeddedData(), m_grantDemand(false), m_callInProgress(false), + m_callAlgoId(p25::defines::ALGO_UNENCRYPT), m_rxStartTime(0U), m_rxStreamId(0U), + m_tekSrcAlgoId(p25::defines::ALGO_UNENCRYPT), + m_tekSrcKeyId(0U), + m_tekDstAlgoId(p25::defines::ALGO_UNENCRYPT), + m_tekDstKeyId(0U), + m_requestedSrcTek(false), + m_requestedDstTek(false), + m_p25SrcCrypto(nullptr), + m_p25DstCrypto(nullptr), m_running(false), m_trace(false), m_debug(false) @@ -92,6 +108,9 @@ HostPatch::HostPatch(const std::string& confFile) : resetWithNullAudio(m_netLDU1, false); ::memset(m_netLDU2, 0x00U, 9U * 25U); resetWithNullAudio(m_netLDU2, false); + + m_p25SrcCrypto = new p25::crypto::P25Crypto(); + m_p25DstCrypto = new p25::crypto::P25Crypto(); } /* Finalizes a instance of the HostPatch class. */ @@ -100,6 +119,8 @@ HostPatch::~HostPatch() { delete[] m_netLDU1; delete[] m_netLDU2; + delete m_p25SrcCrypto; + delete m_p25DstCrypto; } /* Executes the main FNE processing loop. */ @@ -306,6 +327,43 @@ bool HostPatch::createNetwork() m_srcSlot = (uint8_t)networkConf["sourceSlot"].as(1U); m_dstTGId = (uint32_t)networkConf["destinationTGID"].as(1U); m_dstSlot = (uint8_t)networkConf["destinationSlot"].as(1U); + + // source TEK parameters + yaml::Node srcTekConf = networkConf["srcTek"]; + bool tekSrcEnable = srcTekConf["enable"].as(false); + std::string tekSrcAlgo = srcTekConf["tekAlgo"].as(); + std::transform(tekSrcAlgo.begin(), tekSrcAlgo.end(), tekSrcAlgo.begin(), ::tolower); + m_tekSrcKeyId = (uint32_t)::strtoul(srcTekConf["tekKeyId"].as("0").c_str(), NULL, 16); + if (tekSrcEnable && m_tekSrcKeyId > 0U) { + if (tekSrcAlgo == TEK_AES) + m_tekSrcAlgoId = p25::defines::ALGO_AES_256; + else if (tekSrcAlgo == TEK_ARC4) + m_tekSrcAlgoId = p25::defines::ALGO_ARC4; + else { + ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); + m_tekSrcAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekSrcKeyId = 0U; + } + } + + // destination TEK parameters + yaml::Node dstTekConf = networkConf["srcTek"]; + bool tekDstEnable = dstTekConf["enable"].as(false); + std::string tekDstAlgo = dstTekConf["tekAlgo"].as(); + std::transform(tekDstAlgo.begin(), tekDstAlgo.end(), tekDstAlgo.begin(), ::tolower); + m_tekDstKeyId = (uint32_t)::strtoul(dstTekConf["tekKeyId"].as("0").c_str(), NULL, 16); + if (tekDstEnable && m_tekDstKeyId > 0U) { + if (tekDstAlgo == TEK_AES) + m_tekDstAlgoId = p25::defines::ALGO_AES_256; + else if (tekDstAlgo == TEK_ARC4) + m_tekDstAlgoId = p25::defines::ALGO_ARC4; + else { + ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); + m_tekDstAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekDstKeyId = 0U; + } + } + m_twoWayPatch = networkConf["twoWay"].as(false); // make sure our destination ID is sane @@ -404,8 +462,22 @@ bool HostPatch::createNetwork() LogInfo(" Source TGID: %u", m_srcTGId); LogInfo(" Source DMR Slot: %u", m_srcSlot); + + LogInfo(" Source Traffic Encrypted: %s", tekSrcEnable ? "yes" : "no"); + if (tekSrcEnable) { + LogInfo(" Source TEK Algorithm: %s", tekSrcAlgo.c_str()); + LogInfo(" Source TEK Key ID: $%04X", m_tekSrcKeyId); + } + LogInfo(" Destination TGID: %u", m_dstTGId); LogInfo(" Destination DMR Slot: %u", m_dstSlot); + + LogInfo(" Destination Traffic Encrypted: %s", tekDstEnable ? "yes" : "no"); + if (tekDstEnable) { + LogInfo(" Destination TEK Algorithm: %s", tekDstAlgo.c_str()); + LogInfo(" Destination TEK Key ID: $%04X", m_tekDstKeyId); + } + LogInfo(" Two-Way Patch: %s", m_twoWayPatch ? "yes" : "no"); if (debug) { @@ -427,6 +499,9 @@ bool HostPatch::createNetwork() m_network->setMetadata(m_identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); m_network->setConventional(true); + m_network->setKeyResponseCallback([=](p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength) { + processTEKResponse(&ki, algId, keyLength); + }); if (encrypted) { m_network->setPresharedKey(presharedKey); @@ -839,21 +914,62 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (dstId != m_srcTGId && dstId != m_dstTGId) return; + bool reverseEncrypt = false; uint32_t actualDstId = m_srcTGId; + uint8_t tekAlgoId = m_tekSrcAlgoId; + uint16_t tekKeyId = m_tekSrcKeyId; + if (!m_mmdvmP25Reflector) { actualDstId = m_dstTGId; if (m_twoWayPatch) { - if (dstId == m_dstTGId) + if (dstId == m_dstTGId) { actualDstId = m_srcTGId; + tekAlgoId = m_tekDstAlgoId; + tekKeyId = m_tekDstKeyId; + reverseEncrypt = true; + } } else { if (dstId == m_dstTGId) return; } } + // is this a new call stream? + uint16_t callKID = 0U; if (m_network->getP25StreamId() != m_rxStreamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { m_callInProgress = true; + // if this is the beginning of a call and we have a valid HDU frame, extract the algo ID + uint8_t frameType = buffer[180U]; + if (frameType == FrameType::HDU_VALID) { + m_callAlgoId = buffer[181U]; + if (m_callAlgoId != ALGO_UNENCRYPT) { + callKID = GET_UINT16(buffer, 182U); + + if (m_callAlgoId != tekAlgoId && callKID != tekKeyId) { + m_callAlgoId = ALGO_UNENCRYPT; + m_callInProgress = false; + + LogWarning(LOG_HOST, "P25, call ignored, using different encryption parameters, callAlgoId = $%02X, callKID = $%04X, tekAlgoId = $%02X, tekKID = $%04X", m_callAlgoId, callKID, tekAlgoId, tekKeyId); + return; + } else { + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { + mi[i] = buffer[184U + i]; + } + + if (reverseEncrypt) { + m_p25DstCrypto->setMI(mi); + m_p25DstCrypto->generateKeystream(); + } else { + m_p25SrcCrypto->setMI(mi); + m_p25SrcCrypto->generateKeystream(); + } + } + } + } + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; @@ -907,6 +1023,11 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) m_callInProgress = false; m_rxStartTime = 0U; m_rxStreamId = 0U; + + m_p25SrcCrypto->clearMI(); + m_p25SrcCrypto->resetKeystream(); + m_p25DstCrypto->clearMI(); + m_p25DstCrypto->resetKeystream(); return; } @@ -965,6 +1086,10 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + cryptP25AudioFrame(netLDU, reverseEncrypt, 1U); + } + control = lc::LC(*dfsiLC.control()); control.setSrcId(srcId); @@ -988,6 +1113,21 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } } + // the previous is nice and all -- but if we're cross-encrypting, we need to use the TEK + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + control.setAlgId(tekAlgoId); + control.setKId(tekKeyId); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + if (!reverseEncrypt) + m_p25SrcCrypto->getMI(mi); + else + m_p25DstCrypto->getMI(mi); + + control.setMI(mi); + } + if (m_mmdvmP25Reflector) { ::memcpy(m_netLDU1, netLDU, 9U * 25U); m_gotNetLDU1 = true; @@ -1046,11 +1186,30 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + cryptP25AudioFrame(netLDU, reverseEncrypt, 2U); + } + control = lc::LC(*dfsiLC.control()); control.setSrcId(srcId); control.setDstId(actualDstId); + // set the algo ID and key ID + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + control.setAlgId(tekAlgoId); + control.setKId(tekKeyId); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + if (!reverseEncrypt) + m_p25SrcCrypto->getMI(mi); + else + m_p25DstCrypto->getMI(mi); + + control.setMI(mi); + } + if (m_mmdvmP25Reflector) { ::memcpy(m_netLDU2, netLDU, 9U * 25U); m_gotNetLDU2 = true; @@ -1077,6 +1236,149 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } } +/* Helper to cross encrypt P25 network traffic audio frames. */ + +void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p25N) +{ + assert(ldu != nullptr); + using namespace p25; + using namespace p25::defines; + + uint8_t tekAlgoId = m_tekSrcAlgoId; + uint16_t tekKeyId = m_tekSrcKeyId; + + if (reverseEncrypt) { + tekAlgoId = m_tekDstAlgoId; + tekKeyId = m_tekDstKeyId; + } + + // decode 9 IMBE codewords into PCM samples + for (int n = 0; n < 9; n++) { + uint8_t imbe[RAW_IMBE_LENGTH_BYTES]; + switch (n) { + case 0: + ::memcpy(imbe, ldu + 10U, RAW_IMBE_LENGTH_BYTES); + break; + case 1: + ::memcpy(imbe, ldu + 26U, RAW_IMBE_LENGTH_BYTES); + break; + case 2: + ::memcpy(imbe, ldu + 55U, RAW_IMBE_LENGTH_BYTES); + break; + case 3: + ::memcpy(imbe, ldu + 80U, RAW_IMBE_LENGTH_BYTES); + break; + case 4: + ::memcpy(imbe, ldu + 105U, RAW_IMBE_LENGTH_BYTES); + break; + case 5: + ::memcpy(imbe, ldu + 130U, RAW_IMBE_LENGTH_BYTES); + break; + case 6: + ::memcpy(imbe, ldu + 155U, RAW_IMBE_LENGTH_BYTES); + break; + case 7: + ::memcpy(imbe, ldu + 180U, RAW_IMBE_LENGTH_BYTES); + break; + case 8: + ::memcpy(imbe, ldu + 204U, RAW_IMBE_LENGTH_BYTES); + break; + } + + // Utils::dump(1U, "IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + + // first -- decrypt the IMBE codeword + if (tekAlgoId != p25::defines::ALGO_UNENCRYPT && tekKeyId > 0U) { + if (!reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { + switch (tekAlgoId) { + case p25::defines::ALGO_AES_256: + m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case p25::defines::ALGO_ARC4: + m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + break; + } + } else { + if (m_p25DstCrypto->getTEKLength() > 0U) { + switch (tekAlgoId) { + case p25::defines::ALGO_AES_256: + m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case p25::defines::ALGO_ARC4: + m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + break; + } + } + } + } + + // second -- reencrypt the IMBE codeword + if (tekAlgoId != p25::defines::ALGO_UNENCRYPT && tekKeyId > 0U) { + if (!reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { + switch (tekAlgoId) { + case p25::defines::ALGO_AES_256: + m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case p25::defines::ALGO_ARC4: + m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + break; + } + } else { + if (m_p25SrcCrypto->getTEKLength() > 0U) { + switch (tekAlgoId) { + case p25::defines::ALGO_AES_256: + m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case p25::defines::ALGO_ARC4: + m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + break; + } + } + } + } + } +} + +/* Helper to process a FNE KMM TEK response. */ + +void HostPatch::processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength) +{ + if (ki == nullptr) + return; + + if (algId == m_tekSrcAlgoId && ki->kId() == m_tekSrcKeyId) { + LogMessage(LOG_HOST, "Source TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + UInt8Array tek = std::make_unique(keyLength); + ki->getKey(tek.get()); + + m_p25SrcCrypto->setTEKAlgoId(algId); + m_p25SrcCrypto->setTEKKeyId(ki->kId()); + m_p25SrcCrypto->setKey(tek.get(), keyLength); + } + + if (algId == m_tekDstAlgoId && ki->kId() == m_tekDstKeyId) { + LogMessage(LOG_HOST, "Destination TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + UInt8Array tek = std::make_unique(keyLength); + ki->getKey(tek.get()); + + m_p25DstCrypto->setTEKAlgoId(algId); + m_p25DstCrypto->setTEKKeyId(ki->kId()); + m_p25DstCrypto->setKey(tek.get(), keyLength); + } +} + /* Helper to check for an unflushed LDU1 packet. */ void HostPatch::checkNet_LDU1() @@ -1255,6 +1557,26 @@ void* HostPatch::threadNetworkProcess(void* arg) continue; } + if (patch->m_network->getStatus() == NET_STAT_RUNNING) { + // check if we need to request a TEK for the source TGID + if (patch->m_tekSrcAlgoId != p25::defines::ALGO_UNENCRYPT && patch->m_tekSrcKeyId > 0U) { + if (patch->m_p25SrcCrypto->getTEKLength() == 0U && !patch->m_requestedSrcTek) { + patch->m_requestedSrcTek = true; + LogMessage(LOG_HOST, "Patch source TGID encryption enabled, requesting TEK from network."); + patch->m_network->writeKeyReq(patch->m_tekSrcKeyId, patch->m_tekSrcAlgoId); + } + } + + // check if we need to request a TEK for the destination TGID + if (patch->m_tekDstAlgoId != p25::defines::ALGO_UNENCRYPT && patch->m_tekDstKeyId > 0U) { + if (patch->m_p25DstCrypto->getTEKLength() == 0U && !patch->m_requestedDstTek) { + patch->m_requestedDstTek = true; + LogMessage(LOG_HOST, "Patch destination TGID encryption enabled, requesting TEK from network."); + patch->m_network->writeKeyReq(patch->m_tekDstKeyId, patch->m_tekDstAlgoId); + } + } + } + uint32_t length = 0U; bool netReadRet = false; if (patch->m_digiMode == TX_MODE_DMR) { diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h index dd0ad07d8..c182dd574 100644 --- a/src/patch/HostPatch.h +++ b/src/patch/HostPatch.h @@ -21,6 +21,7 @@ #include "common/dmr/lc/LC.h" #include "common/dmr/lc/PrivacyLC.h" #include "common/p25/lc/LC.h" +#include "common/p25/Crypto.h" #include "common/network/udp/Socket.h" #include "common/yaml/Yaml.h" #include "common/Timer.h" @@ -94,9 +95,20 @@ class HOST_SW_API HostPatch { bool m_grantDemand; bool m_callInProgress; + uint8_t m_callAlgoId; uint64_t m_rxStartTime; uint32_t m_rxStreamId; + uint8_t m_tekSrcAlgoId; + uint16_t m_tekSrcKeyId; + uint8_t m_tekDstAlgoId; + uint16_t m_tekDstKeyId; + bool m_requestedSrcTek; + bool m_requestedDstTek; + + p25::crypto::P25Crypto* m_p25SrcCrypto; + p25::crypto::P25Crypto* m_p25DstCrypto; + bool m_running; bool m_trace; bool m_debug; @@ -133,6 +145,22 @@ class HOST_SW_API HostPatch { */ void processP25Network(uint8_t* buffer, uint32_t length); + /** + * @brief Helper to cross encrypt P25 network traffic audio frames. + * @param ldu + * @param reverseEncrypt Flag indicating whether or not to reverse the encryption (i.e. use destination TEK vs source TEK). + * @param p25N + */ + void cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p25N); + + /** + * @brief Helper to process a FNE KMM TEK response. + * @param ki Key Item. + * @param algId Algorithm ID. + * @param keyLength Length of key in bytes. + */ + void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); + /** * @brief Helper to check for an unflushed LDU1 packet. */ From 6d755283db2e1f0bf68dac6436e08f5fa1841d0a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Jul 2025 20:39:42 -0400 Subject: [PATCH 068/133] reset call algo when a P25 call ends; --- src/patch/HostPatch.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 503d6e266..6faf54697 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -1021,6 +1021,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) m_rxStreamId = 0U; m_callInProgress = false; + m_callAlgoId = ALGO_UNENCRYPT; m_rxStartTime = 0U; m_rxStreamId = 0U; From 9354dbf46e1276d8ca2a82d87bf7ab3ac334ca6f Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Jul 2025 20:52:27 -0400 Subject: [PATCH 069/133] properly handle algo ID's for source and destinations; --- src/patch/HostPatch.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 6faf54697..41f353a4e 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -1245,12 +1245,16 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 using namespace p25; using namespace p25::defines; - uint8_t tekAlgoId = m_tekSrcAlgoId; - uint16_t tekKeyId = m_tekSrcKeyId; + uint8_t tekSrcAlgoId = m_tekSrcAlgoId; + uint16_t tekSrcKeyId = m_tekSrcKeyId; + uint8_t tekDstAlgoId = m_tekDstAlgoId; + uint16_t tekDstKeyId = m_tekDstKeyId; if (reverseEncrypt) { - tekAlgoId = m_tekDstAlgoId; - tekKeyId = m_tekDstKeyId; + tekSrcAlgoId = m_tekDstAlgoId; + tekSrcKeyId = m_tekDstKeyId; + tekDstAlgoId = m_tekSrcAlgoId; + tekDstKeyId = m_tekSrcKeyId; } // decode 9 IMBE codewords into PCM samples @@ -1289,9 +1293,9 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 // Utils::dump(1U, "IMBE", imbe, RAW_IMBE_LENGTH_BYTES); // first -- decrypt the IMBE codeword - if (tekAlgoId != p25::defines::ALGO_UNENCRYPT && tekKeyId > 0U) { + if (tekSrcAlgoId != p25::defines::ALGO_UNENCRYPT && tekSrcKeyId > 0U) { if (!reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { - switch (tekAlgoId) { + switch (tekSrcAlgoId) { case p25::defines::ALGO_AES_256: m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; @@ -1299,12 +1303,12 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); break; } } else { if (m_p25DstCrypto->getTEKLength() > 0U) { - switch (tekAlgoId) { + switch (tekDstAlgoId) { case p25::defines::ALGO_AES_256: m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; @@ -1312,7 +1316,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); break; } } @@ -1320,9 +1324,9 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 } // second -- reencrypt the IMBE codeword - if (tekAlgoId != p25::defines::ALGO_UNENCRYPT && tekKeyId > 0U) { + if (tekDstAlgoId != p25::defines::ALGO_UNENCRYPT && tekDstKeyId > 0U) { if (!reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { - switch (tekAlgoId) { + switch (tekDstAlgoId) { case p25::defines::ALGO_AES_256: m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; @@ -1330,12 +1334,12 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); break; } } else { if (m_p25SrcCrypto->getTEKLength() > 0U) { - switch (tekAlgoId) { + switch (tekSrcAlgoId) { case p25::defines::ALGO_AES_256: m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; @@ -1343,7 +1347,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekAlgoId); + LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); break; } } From 92cb825fe46eaadaad8987895fe4f6d5aba0773e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Jul 2025 20:53:33 -0400 Subject: [PATCH 070/133] missed reverseCrypto check; --- src/patch/HostPatch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 41f353a4e..88faf5425 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -1307,7 +1307,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 break; } } else { - if (m_p25DstCrypto->getTEKLength() > 0U) { + if (reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { switch (tekDstAlgoId) { case p25::defines::ALGO_AES_256: m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); @@ -1338,7 +1338,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 break; } } else { - if (m_p25SrcCrypto->getTEKLength() > 0U) { + if (reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { switch (tekSrcAlgoId) { case p25::defines::ALGO_AES_256: m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); From d94f8d614239befdb19b444b631db071047d5ac5 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Jul 2025 21:27:45 -0400 Subject: [PATCH 071/133] [EXPERIMENTAL] for some configurations (**you know who you are god damn it**), allow dvmhost to be configured to "idle" on a non-zero TG when the RF talkgroup hangtimer stops, this effectively disables the promiscuous network Rx of the host for certain conventional operations, and requires RF-based TG steering to change the active talkgroup; cleanup the macros for DMR and NXDN that perform various repeated checks; --- configs/config.example.yml | 4 ++++ src/host/dmr/Control.cpp | 8 +++++++ src/host/dmr/Slot.cpp | 1 + src/host/dmr/Slot.h | 8 +++++++ src/host/dmr/packet/ControlSignaling.cpp | 4 +++- src/host/dmr/packet/Data.cpp | 7 +++++- src/host/dmr/packet/Voice.cpp | 25 +++++++++++++++++---- src/host/nxdn/Control.cpp | 7 ++++++ src/host/nxdn/Control.h | 2 ++ src/host/nxdn/packet/Data.cpp | 11 +++++----- src/host/nxdn/packet/Voice.cpp | 28 +++++++++++++++++++----- src/host/p25/Control.cpp | 5 +++++ src/host/p25/Control.h | 2 ++ src/host/p25/packet/Voice.cpp | 11 ++++++++++ 14 files changed, 107 insertions(+), 16 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index 469d5a900..fc11b8c1f 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -482,6 +482,10 @@ system: pSuperGroup: FFFE # Announcment Talkgroup Group. announcementGroup: FFFE + # Default Talkgroup for idle network traffic. + # NOTE: TGID 0 is used here to indicate "promiscuous" mode, where first-come-first-serve traffic from the network + # is allowed. + defaultNetIdleTalkgroup: 0 # DMR network ID. dmrNetId: 1 diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index 7d7b8af36..81c609226 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -143,6 +143,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_enableTSCC = enableTSCC; yaml::Node rfssConfig = systemConf["config"]; + uint32_t defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + m_slot1->setDefaultNetIdleTG(defaultNetIdleTalkgroup); + m_slot2->setDefaultNetIdleTG(defaultNetIdleTalkgroup); + yaml::Node controlCh = rfssConfig["controlCh"]; bool notifyCC = controlCh["notifyEnable"].as(false); m_slot1->setNotifyCC(notifyCC); @@ -222,6 +226,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa LogInfo(" Disable Network Grants: yes"); } + if (defaultNetIdleTalkgroup != 0U) { + LogInfo(" Default Network Idle Talkgroup: $%04X", defaultNetIdleTalkgroup); + } + LogInfo(" Ignore Affiliation Check: %s", ignoreAffiliationCheck ? "yes" : "no"); LogInfo(" Legacy Group Registration: %s", legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", notifyCC ? "yes" : "no"); diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 44e1a4853..e4fbf27cd 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -155,6 +155,7 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_disableNetworkGrant(false), m_convNetGrantDemand(false), m_legacyGroupReg(false), + m_defaultNetIdleTalkgroup(0U), m_tsccPayloadDstId(0U), m_tsccPayloadSrcId(0U), m_tsccPayloadGroup(false), diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index 3c6fb3d7c..04e71a92a 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -279,6 +279,12 @@ namespace dmr */ void setFrameLossThreshold(uint32_t threshold) { m_frameLossThreshold = threshold; } + /** + * @brief Helper to set the default network idle talkgroup. + * @param tg Talkgroup ID. + */ + void setDefaultNetIdleTG(uint32_t tg) { m_defaultNetIdleTalkgroup = tg; } + /** * @brief Helper to get the last transmitted destination ID. * @returns uint32_t Last transmitted Destination ID. @@ -418,6 +424,8 @@ namespace dmr bool m_convNetGrantDemand; bool m_legacyGroupReg; + uint32_t m_defaultNetIdleTalkgroup; + uint32_t m_tsccPayloadDstId; uint32_t m_tsccPayloadSrcId; bool m_tsccPayloadGroup; diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 90cc29912..18d27067c 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -33,13 +33,15 @@ using namespace dmr::packet; // Macros // --------------------------------------------------------------------------- -// Don't process RF frames if the network isn't in a idle state. +// Helper macro to perform RF traffic collision checking. #define CHECK_TRAFFIC_COLLISION(_DST_ID) \ + /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ return false; \ } +// Helper macro to check if the RF talkgroup hang timer is running and the destination ID matches. #define CHECK_TG_HANG(_DST_ID) \ if (m_slot->m_rfLastDstId != 0U) { \ if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ diff --git a/src/host/dmr/packet/Data.cpp b/src/host/dmr/packet/Data.cpp index 0a79aabc8..e8afad828 100644 --- a/src/host/dmr/packet/Data.cpp +++ b/src/host/dmr/packet/Data.cpp @@ -35,6 +35,7 @@ using namespace dmr::packet; // Macros // --------------------------------------------------------------------------- +// Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_AUTHORITATIVE(_DST_ID) \ if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { \ if (!g_disableNonAuthoritativeLogging) \ @@ -43,18 +44,21 @@ using namespace dmr::packet; return false; \ } +// Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_NET_AUTHORITATIVE(_DST_ID) \ if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { \ return; \ } -// Don't process RF frames if the network isn't in a idle state. +// Helper macro to perform RF traffic collision checking. #define CHECK_TRAFFIC_COLLISION(_DST_ID) \ + /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ m_slot->m_rfState = RS_RF_LISTENING; \ return false; \ } \ + \ if (m_slot->m_enableTSCC && _DST_ID == m_slot->m_netLastDstId) { \ if (m_slot->m_affiliations->isNetGranted(_DST_ID)) { \ LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); \ @@ -63,6 +67,7 @@ using namespace dmr::packet; } \ } +// Helper macro to check if the RF talkgroup hang timer is running and the destination ID matches. #define CHECK_TG_HANG(_DST_ID) \ if (m_slot->m_rfLastDstId != 0U) { \ if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index 69f2eab5b..696a8f6e1 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -33,6 +33,7 @@ using namespace dmr::packet; // Macros // --------------------------------------------------------------------------- +// Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_AUTHORITATIVE(_DST_ID) \ if (!m_slot->m_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ if (!g_disableNonAuthoritativeLogging) \ @@ -41,17 +42,21 @@ using namespace dmr::packet; return false; \ } +// Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_NET_AUTHORITATIVE(_DST_ID) \ if (!m_slot->m_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ return; \ } +// Helper macro to perform RF traffic collision checking. #define CHECK_TRAFFIC_COLLISION(_DST_ID) \ + /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ m_slot->m_rfState = RS_RF_LISTENING; \ return false; \ } \ + \ if (m_slot->m_enableTSCC && _DST_ID == m_slot->m_netLastDstId) { \ if (m_slot->m_affiliations->isNetGranted(_DST_ID)) { \ LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); \ @@ -60,15 +65,29 @@ using namespace dmr::packet; } \ } -// Don't process network frames if the destination ID's don't match and the network TG hang -// timer is running, and don't process network frames if the RF modem isn't in a listening state +// Helper macro to perform network traffic collision checking. #define CHECK_NET_TRAFFIC_COLLISION(_DST_ID) \ + /* don't process network frames if the destination ID's don't match and the RF TG hang timer is running */ \ if (m_slot->m_rfLastDstId != 0U) { \ if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ return; \ } \ } \ \ + /* bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* */ \ + /* the RF TG hangtimer is running */ \ + if (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired()) { \ + m_slot->m_rfTGHang.stop(); \ + } \ + \ + /* don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and */ \ + /* the destination ID doesn't match the default net idle talkgroup */ \ + if (m_slot->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_slot->m_rfTGHang.isRunning()) { \ + if (m_slot->m_defaultNetIdleTalkgroup != dstId) { \ + return; \ + } \ + } \ + \ if (m_slot->m_netLastDstId != 0U) { \ if (m_slot->m_netLastDstId != _DST_ID && (m_slot->m_netTGHang.isRunning() && !m_slot->m_netTGHang.hasExpired())) { \ return; \ @@ -696,8 +715,6 @@ void Voice::processNetwork(const data::NetData& dmrData) if (m_slot->m_netState == RS_NET_AUDIO) return; - - lc::FullLC fullLC; std::unique_ptr lc = fullLC.decode(data + 2U, DataType::VOICE_LC_HEADER); if (lc == nullptr) { diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index 9a94207b3..a22a2dcef 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -72,6 +72,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_dedicatedControl(false), m_ignoreAffiliationCheck(false), m_legacyGroupReg(false), + m_defaultNetIdleTalkgroup(0U), m_rfLastLICH(), m_rfLC(), m_netLC(), @@ -221,6 +222,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_legacyGroupReg = nxdnProtocol["legacyGroupReg"].as(false); yaml::Node rfssConfig = systemConf["config"]; + m_defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + yaml::Node controlCh = rfssConfig["controlCh"]; m_notifyCC = controlCh["notifyEnable"].as(false); @@ -328,6 +331,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogMessage(LOG_DMR, "Host is configured to operate as a NXDN control channel, site controller mode."); } + if (m_defaultNetIdleTalkgroup != 0U) { + LogInfo(" Default Network Idle Talkgroup: $%04X", m_defaultNetIdleTalkgroup); + } + LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); LogInfo(" Legacy Group Registration: %s", m_legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", m_notifyCC ? "yes" : "no"); diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index 4dd8cf815..37bd23c83 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -280,6 +280,8 @@ namespace nxdn bool m_ignoreAffiliationCheck; bool m_legacyGroupReg; + uint32_t m_defaultNetIdleTalkgroup; + channel::LICH m_rfLastLICH; lc::RTCH m_rfLC; lc::RTCH m_netLC; diff --git a/src/host/nxdn/packet/Data.cpp b/src/host/nxdn/packet/Data.cpp index fe3d63709..76cb3c7e1 100644 --- a/src/host/nxdn/packet/Data.cpp +++ b/src/host/nxdn/packet/Data.cpp @@ -28,10 +28,9 @@ using namespace nxdn::packet; // Macros // --------------------------------------------------------------------------- -// Don't process RF frames if the network isn't in a idle state and the RF destination -// is the network destination and stop network frames from processing -- RF wants to -// transmit on a different talkgroup +// Helper macro to perform RF traffic collision checking. #define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ + /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ resetRF(); \ @@ -39,6 +38,7 @@ using namespace nxdn::packet; return false; \ } \ \ + /* stop network frames from processing -- RF wants to transmit on a different talkgroup */ \ if (m_nxdn->m_netState != RS_NET_IDLE) { \ if (m_nxdn->m_netLC.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, \ @@ -54,9 +54,9 @@ using namespace nxdn::packet; } \ } -// Don't process network frames if the destination ID's don't match and the network TG hang -// timer is running, and don't process network frames if the RF modem isn't in a listening state +// Helper macro to perform network traffic collision checking. #define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ + /* don't process network frames if the destination ID's don't match and the RF TG hang timer is running */ \ if (m_nxdn->m_rfLastDstId != 0U) { \ if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ resetNet(); \ @@ -68,6 +68,7 @@ using namespace nxdn::packet; } \ } \ \ + /* don't process network frames if the RF modem isn't in a listening state */ \ if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ if (_LAYER3.getSrcId() == srcId && _LAYER3.getDstId() == dstId) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 834d5a7d9..0d7855085 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -31,10 +31,9 @@ using namespace nxdn::packet; // Macros // --------------------------------------------------------------------------- -// Don't process RF frames if the network isn't in a idle state and the RF destination -// is the network destination and stop network frames from processing -- RF wants to -// transmit on a different talkgroup +// Helper macro to perform RF traffic collision checking. #define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ + /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ resetRF(); \ @@ -42,6 +41,7 @@ using namespace nxdn::packet; return false; \ } \ \ + /* stop network frames from processing -- RF wants to transmit on a different talkgroup */ \ if (m_nxdn->m_netState != RS_NET_IDLE) { \ if (m_nxdn->m_netLC.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _SRC_ID, _DST_ID, \ @@ -58,6 +58,7 @@ using namespace nxdn::packet; m_nxdn->m_network->resetNXDN(); \ } \ \ + /* is control is enabled, and the group was granted by network already ignore RF traffic */ \ if (m_nxdn->m_enableControl && _DST_ID == m_nxdn->m_netLastDstId) { \ if (m_nxdn->m_affiliations->isNetGranted(_DST_ID)) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _SRC_ID, _DST_ID, \ @@ -69,9 +70,9 @@ using namespace nxdn::packet; } \ } -// Don't process network frames if the destination ID's don't match and the network TG hang -// timer is running, and don't process network frames if the RF modem isn't in a listening state +// Helper macro to perform network traffic collision checking. #define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ + /* don't process network frames if the destination ID's don't match and the RF TG hang timer is running */ \ if (m_nxdn->m_rfLastDstId != 0U) { \ if (m_nxdn->m_rfLastDstId != _DST_ID && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ resetNet(); \ @@ -83,6 +84,21 @@ using namespace nxdn::packet; } \ } \ \ + /* bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* */ \ + /* the RF TG hangtimer is running */ \ + if (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired()) { \ + m_nxdn->m_rfTGHang.stop(); \ + } \ + \ + /* don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and */ \ + /* the destination ID doesn't match the default net idle talkgroup */ \ + if (m_nxdn->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_nxdn->m_rfTGHang.isRunning()) { \ + if (m_nxdn->m_defaultNetIdleTalkgroup != dstId) { \ + return false; \ + } \ + } \ + \ + /* perform authoritative network TG hangtimer and traffic preemption */ \ if (m_nxdn->m_authoritative) { \ if (m_nxdn->m_netLastDstId != 0U) { \ if (m_nxdn->m_netLastDstId != _DST_ID && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { \ @@ -94,6 +110,7 @@ using namespace nxdn::packet; } \ } \ \ + /* don't process network frames if the RF modem isn't in a listening state */ \ if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ if (_LAYER3.getSrcId() == _SRC_ID && _LAYER3.getDstId() == _DST_ID) { \ LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ @@ -114,6 +131,7 @@ using namespace nxdn::packet; } \ } \ \ + /* don't process network frames if this modem isn't authoritative */ \ if (!m_nxdn->m_authoritative && m_nxdn->m_permittedDstId != _DST_ID) { \ if (!g_disableNonAuthoritativeLogging) \ LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", _DST_ID); \ diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 5e75b23f0..a84720b3e 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -82,6 +82,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_demandUnitRegForRefusedAff(true), m_dfsiFDX(false), m_forceAllowTG0(false), + m_defaultNetIdleTalkgroup(0U), m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), @@ -246,6 +247,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw yaml::Node rfssConfig = systemConf["config"]; m_control->m_patchSuperGroup = (uint32_t)::strtoul(rfssConfig["pSuperGroup"].as("FFFE").c_str(), NULL, 16); m_control->m_announcementGroup = (uint32_t)::strtoul(rfssConfig["announcementGroup"].as("FFFE").c_str(), NULL, 16); + m_defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); yaml::Node secureConfig = rfssConfig["secure"]; std::string key = secureConfig["key"].as(); @@ -514,6 +516,9 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Patch Super Group: $%04X", m_control->m_patchSuperGroup); LogInfo(" Announcement Group: $%04X", m_control->m_announcementGroup); + if (m_defaultNetIdleTalkgroup != 0U) { + LogInfo(" Default Network Idle Talkgroup: $%04X", m_defaultNetIdleTalkgroup); + } LogInfo(" Notify Control: %s", m_notifyCC ? "yes" : "no"); if (m_disableNetworkHDU) { diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 5e9673d4f..4daf06d15 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -314,6 +314,8 @@ namespace p25 bool m_dfsiFDX; bool m_forceAllowTG0; + uint32_t m_defaultNetIdleTalkgroup; + ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; ::lookups::TalkgroupRulesLookup* m_tidLookup; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 31940862d..2175319a4 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1216,6 +1216,17 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L m_p25->m_rfTGHang.stop(); } + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_p25->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_p25->m_rfTGHang.isRunning()) { + if (m_p25->m_defaultNetIdleTalkgroup != dstId) { + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return false; + } + } + // perform authoritative network TG hangtimer and traffic preemption if (m_p25->m_authoritative) { // don't process network frames if the destination ID's don't match and the network TG hang timer is running From 867af440936596e47572567773bb2dff02694953 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 4 Aug 2025 22:27:34 -0400 Subject: [PATCH 072/133] major refactor for V.24 support; --- src/common/p25/dfsi/DFSIDefines.h | 38 +-- src/common/p25/dfsi/frames/FrameDefines.h | 60 +--- src/common/p25/dfsi/frames/Frames.h | 7 +- src/common/p25/dfsi/frames/FullRateVoice.cpp | 4 +- .../p25/dfsi/frames/MotFullRateVoice.cpp | 46 ++- src/common/p25/dfsi/frames/MotFullRateVoice.h | 12 +- src/common/p25/dfsi/frames/MotPDUFrame.cpp | 108 ------- src/common/p25/dfsi/frames/MotPDUFrame.h | 93 ------ .../p25/dfsi/frames/MotStartOfStream.cpp | 43 +-- src/common/p25/dfsi/frames/MotStartOfStream.h | 105 +++++-- .../p25/dfsi/frames/MotStartVoiceFrame.cpp | 42 +-- .../p25/dfsi/frames/MotStartVoiceFrame.h | 35 +-- src/common/p25/dfsi/frames/MotTSBKFrame.cpp | 22 +- src/common/p25/dfsi/frames/MotTSBKFrame.h | 18 +- .../p25/dfsi/frames/MotVoiceHeader1.cpp | 129 -------- src/common/p25/dfsi/frames/MotVoiceHeader1.h | 116 ------- .../p25/dfsi/frames/MotVoiceHeader2.cpp | 87 ------ src/common/p25/dfsi/frames/MotVoiceHeader2.h | 100 ------ src/host/modem/ModemV24.cpp | 289 +++++++++--------- src/host/modem/ModemV24.h | 10 +- 20 files changed, 372 insertions(+), 992 deletions(-) delete mode 100644 src/common/p25/dfsi/frames/MotPDUFrame.cpp delete mode 100644 src/common/p25/dfsi/frames/MotPDUFrame.h delete mode 100644 src/common/p25/dfsi/frames/MotVoiceHeader1.cpp delete mode 100644 src/common/p25/dfsi/frames/MotVoiceHeader1.h delete mode 100644 src/common/p25/dfsi/frames/MotVoiceHeader2.cpp delete mode 100644 src/common/p25/dfsi/frames/MotVoiceHeader2.h diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index 9fa6b7c1c..7097f0e53 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -43,8 +43,15 @@ namespace p25 const uint32_t DFSI_VHDR_RAW_LEN = 36U; const uint32_t DFSI_VHDR_LEN = 27U; + const uint8_t DFSI_MOT_START_LEN = 9U; + const uint8_t DFSI_MOT_VHDR_1_LEN = 30U; + const uint8_t DFSI_MOT_VHDR_2_LEN = 22U; + const uint8_t DFSI_MOT_TSBK_LEN = 24U; + const uint32_t DFSI_TIA_VHDR_LEN = 22U; + const uint32_t DFSI_MOT_ICW_LENGTH = 6U; + const uint32_t DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES = 22U; const uint32_t DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES = 14U; const uint32_t DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES = 17U; @@ -70,28 +77,25 @@ namespace p25 * @{ */ - const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! - - const uint8_t DFSI_STATUS_NO_ERROR = 0x00U; //! - const uint8_t DFSI_STATUS_ERASE = 0x02U; //! - - const uint8_t DFSI_RT_ENABLED = 0x02U; //! - const uint8_t DFSI_RT_DISABLED = 0x04U; //! + const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! - const uint8_t DFSI_START_FLAG = 0x0CU; //! - const uint8_t DFSI_STOP_FLAG = 0x25U; //! + const uint8_t DFSI_MOT_ICW_FMT_TYPE3 = 0x02U; //! - const uint8_t DFSI_TYPE_DATA_PAYLOAD = 0x06U; //! - const uint8_t DFSI_TYPE_VOICE = 0x0BU; //! + const uint8_t DFSI_MOT_ICW_PARM_NOP = 0x00U; //! No Operation + const uint8_t DSFI_MOT_ICW_PARM_PAYLOAD = 0x0CU; //! Stream Payload + const uint8_t DFSI_MOT_ICW_PARM_RSSI = 0x1AU; //! RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //! Stop Stream - const uint8_t DFSI_DEF_ICW_SOURCE = 0x00U; //! Infrastructure Source - Default Source - const uint8_t DFSI_DEF_SOURCE = 0x00U; //! + const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //! Talkaround + const uint8_t DFSI_BUSY_BITS_BUSY = 0x01U; //! Busy + const uint8_t DFSI_BUSY_BITS_INBOUND = 0x02U; //! Inbound + const uint8_t DFSI_BUSY_BITS_IDLE = 0x03U; //! Idle /** @brief DFSI Frame Type */ namespace DFSIFrameType { /** @brief DFSI Frame Type */ enum E : uint8_t { - MOT_START_STOP = 0x00U, // Motorola Start/Stop + MOT_START_STOP = 0x00U, // Motorola Start/Stop Stream MOT_VHDR_1 = 0x60U, // Motorola Voice Header 1 MOT_VHDR_2 = 0x61U, // Motorola Voice Header 2 @@ -116,8 +120,8 @@ namespace p25 LDU2_VOICE17 = 0x72U, // IMBE LDU2 - Voice 17 + Encryption Sync LDU2_VOICE18 = 0x73U, // IMBE LDU2 - Voice 18 + Low Speed Data - PDU = 0x87U, // PDU - TSBK = 0xA1U // TSBK + MOT_PDU_SINGLE = 0x87U, // Motorola PDU (Single Block) + MOT_TSBK = 0xA1U // Motorola TSBK (Single Block) }; } diff --git a/src/common/p25/dfsi/frames/FrameDefines.h b/src/common/p25/dfsi/frames/FrameDefines.h index a2db0b9ef..eb8c01bf3 100644 --- a/src/common/p25/dfsi/frames/FrameDefines.h +++ b/src/common/p25/dfsi/frames/FrameDefines.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -91,57 +91,23 @@ namespace p25 }; } - /** @brief RT/RT Flag */ - namespace RTFlag { - /** @brief RT/RT Flag */ + /** @brief Motorola Start of Stream Operation */ + namespace MotStartStreamOpcode { + /** @brief Motorola Start of Stream Operation */ enum E : uint8_t { - ENABLED = 0x02U, //! RT/RT Enabled - DISABLED = 0x04U //! RT/RT Disabled + TRANSMIT = 0x02U, //! Transmit + RECEIVE = 0x04U, //! Receive }; } - /** @brief Start/Stop Flag */ - namespace StartStopFlag { - /** @brief Start/Stop Flag */ + /** @brief Motorola Stream Payload */ + namespace MotStreamPayload { + /** @brief Motorola Stream Payload */ enum E : uint8_t { - START = 0x0CU, //! Start - STOP = 0x25U //! Stop - }; - } - - /** @brief V.24 Data Stream Type */ - namespace StreamTypeFlag { - /** @brief V.24 Data Stream Type */ - enum E : uint8_t { - VOICE = 0x0BU, //! Voice - TSBK = 0x0FU //! TSBK - }; - } - - /** @brief RSSI Data Validity */ - namespace RssiValidityFlag { - /** @brief RSSI Data Validity */ - enum E : uint8_t { - INVALID = 0x00U, //! Invalid - VALID = 0x1A //! Valid - }; - } - - /** @brief V.24 Data Source */ - namespace SourceFlag { - /** @brief V.24 Data Source */ - enum E : uint8_t { - DIU = 0x00U, //! DIU - QUANTAR = 0x02U //! Quantar - }; - } - - /** @brief */ - namespace ICWFlag { - /** @brief */ - enum E : uint8_t { - DIU = 0x00U, //! DIU - QUANTAR = 0x1B //! Quantar + VOICE = 0x0BU, //! P25 Voice + DATA = 0x0CU, //! P25 Data + TERM_LC = 0x0EU, //! P25 Termination Link Control + TSBK = 0x0FU //! P25 TSBK }; } /** @} */ diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index 7779b8d14..0931628ed 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #if !defined(__DFSI_FRAMES_H__) @@ -20,13 +20,10 @@ #include "common/p25/dfsi/frames/FullRateVoice.h" // "The" Manufacturer -#include "common/p25/dfsi/frames/MotFullRateVoice.h" #include "common/p25/dfsi/frames/MotStartOfStream.h" #include "common/p25/dfsi/frames/MotStartVoiceFrame.h" -#include "common/p25/dfsi/frames/MotVoiceHeader1.h" -#include "common/p25/dfsi/frames/MotVoiceHeader2.h" +#include "common/p25/dfsi/frames/MotFullRateVoice.h" #include "common/p25/dfsi/frames/MotTSBKFrame.h" -#include "common/p25/dfsi/frames/MotPDUFrame.h" // FSC #include "common/p25/dfsi/frames/fsc/FSCMessage.h" diff --git a/src/common/p25/dfsi/frames/FullRateVoice.cpp b/src/common/p25/dfsi/frames/FullRateVoice.cpp index d334e84f4..b9c677073 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/FullRateVoice.cpp @@ -34,7 +34,7 @@ FullRateVoice::FullRateVoice() : m_muteFrame(false), m_lostFrame(false), m_superframeCnt(0U), - m_busy(0U) + m_busy(DFSI_BUSY_BITS_TALKAROUND) { imbeData = new uint8_t[IMBE_BUF_LEN]; ::memset(imbeData, 0x00U, IMBE_BUF_LEN); @@ -52,7 +52,7 @@ FullRateVoice::FullRateVoice(uint8_t* data) : m_muteFrame(false), m_lostFrame(false), m_superframeCnt(0U), - m_busy(0U) + m_busy(DFSI_BUSY_BITS_TALKAROUND) { decode(data); } diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.cpp b/src/common/p25/dfsi/frames/MotFullRateVoice.cpp index 542ff3273..552795586 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/MotFullRateVoice.h" @@ -33,7 +33,8 @@ MotFullRateVoice::MotFullRateVoice() : imbeData(nullptr), additionalData(nullptr), m_frameType(DFSIFrameType::LDU1_VOICE1), - m_source(SourceFlag::QUANTAR) + m_totalErrors(0U), + m_busy(DFSI_BUSY_BITS_TALKAROUND) { imbeData = new uint8_t[RAW_IMBE_LENGTH_BYTES]; ::memset(imbeData, 0x00U, RAW_IMBE_LENGTH_BYTES); @@ -41,11 +42,17 @@ MotFullRateVoice::MotFullRateVoice() : /* Initializes a instance of the MotFullRateVoice class. */ -MotFullRateVoice::MotFullRateVoice(uint8_t* data) +MotFullRateVoice::MotFullRateVoice(uint8_t* data) : + imbeData(nullptr), + additionalData(nullptr), + m_frameType(DFSIFrameType::LDU1_VOICE1), + m_totalErrors(0U), + m_busy(DFSI_BUSY_BITS_TALKAROUND) { // set our pointers to null since it doesn't get initialized otherwise imbeData = nullptr; additionalData = nullptr; + // decode decode(data); } @@ -100,11 +107,14 @@ bool MotFullRateVoice::decode(const uint8_t* data, bool shortened) if (shortened) { ::memcpy(imbeData, data + 1U, RAW_IMBE_LENGTH_BYTES); - m_source = (SourceFlag::E)data[12U]; - // Forgot to set this originally and left additionalData uninitialized, whoops! + + m_totalErrors = (uint8_t)((data[12U] >> 3) & 0x0FU); // Total Errors + m_busy = (uint8_t)(data[13U] & 0x03U); // Busy Status + + // forgot to set this originally and left additionalData uninitialized, whoops! additionalData = nullptr; } else { - // Frames 0x6A and 0x73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier + // frames $6A and $73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier uint8_t imbeStart = 5U; if (isVoice9or18()) { imbeStart = 4U; @@ -119,7 +129,13 @@ bool MotFullRateVoice::decode(const uint8_t* data, bool shortened) // copy IMBE data based on our imbe start position ::memcpy(imbeData, data + imbeStart, RAW_IMBE_LENGTH_BYTES); - m_source = (SourceFlag::E)data[RAW_IMBE_LENGTH_BYTES + imbeStart]; + if (isVoice9or18()) { + m_totalErrors = 0U; // these frames don't have total errors + m_busy = (uint8_t)(data[3U] & 0x03U); // Busy Status + } else { + m_totalErrors = (uint8_t)((data[imbeStart + RAW_IMBE_LENGTH_BYTES] >> 2) & 0x0FU); // Total Errors + m_busy = (uint8_t)(data[imbeStart + RAW_IMBE_LENGTH_BYTES] & 0x03U); // Busy Status + } } return true; @@ -141,26 +157,28 @@ void MotFullRateVoice::encode(uint8_t* data, bool shortened) // copy based on shortened frame or not if (shortened) { ::memcpy(data + 1U, imbeData, RAW_IMBE_LENGTH_BYTES); - data[12U] = (uint8_t)m_source; - } + data[13U] = (uint8_t)(m_busy & 0x03U); // Busy Status + } // if not shortened, our IMBE data start position depends on frame type else { - // Starting index for the IMBE data + // starting index for the IMBE data uint8_t imbeStart = 5U; if (isVoice9or18()) { imbeStart = 4U; } - // Check if we have additional data + // check if we have additional data if (additionalData != nullptr) { ::memcpy(data + 1U, additionalData, ADDITIONAL_LENGTH); } - // Copy rest of data ::memcpy(data + imbeStart, imbeData, RAW_IMBE_LENGTH_BYTES); - // Source byte at the end - data[11U + imbeStart] = (uint8_t)m_source; + if (isVoice9or18()) { + data[3U] = (uint8_t)(m_busy & 0x03U); // Busy Status + } else { + data[imbeStart + RAW_IMBE_LENGTH_BYTES] = (uint8_t)(m_busy & 0x03U); // Busy Status + } } } diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.h b/src/common/p25/dfsi/frames/MotFullRateVoice.h index 51da71522..18b4ffe13 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.h +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -48,7 +48,7 @@ namespace p25 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Src Flag | + * | Busy Bits | * +=+=+=+=+=+=+=+=+ * \endcode * @ingroup dfsi_frames @@ -99,9 +99,13 @@ namespace p25 */ DECLARE_PROPERTY(defines::DFSIFrameType::E, frameType, FrameType); /** - * @brief V.24 Data Source. + * @brief Total errors detected in the frame. */ - DECLARE_PROPERTY(SourceFlag::E, source, Source); + DECLARE_PROPERTY(uint8_t, totalErrors, TotalErrors); + /** + * @brief Busy Status. + */ + DECLARE_PROPERTY(uint8_t, busy, Busy); private: /** diff --git a/src/common/p25/dfsi/frames/MotPDUFrame.cpp b/src/common/p25/dfsi/frames/MotPDUFrame.cpp deleted file mode 100644 index eb79ee9fa..000000000 --- a/src/common/p25/dfsi/frames/MotPDUFrame.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/P25Defines.h" -#include "common/p25/dfsi/frames/MotPDUFrame.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25; -using namespace p25::defines; -using namespace p25::dfsi; -using namespace p25::dfsi::defines; -using namespace p25::dfsi::frames; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the MotPDUFrame class. */ - -MotPDUFrame::MotPDUFrame() : - startOfStream(nullptr), - pduHeaderData(nullptr) -{ - pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES]; - ::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); - - startOfStream = new MotStartOfStream(); -} - -/* Initializes a instance of the MotPDUFrame class. */ - -MotPDUFrame::MotPDUFrame(uint8_t* data) : - startOfStream(nullptr), - pduHeaderData(nullptr) -{ - pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES]; - ::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); - - decode(data); -} - -/* Finalizes a instance of the MotPDUFrame class. */ - -MotPDUFrame::~MotPDUFrame() -{ - if (startOfStream != nullptr) - delete startOfStream; - if (pduHeaderData != nullptr) - delete[] pduHeaderData; -} - -/* Decode a PDU frame. (only the PDU data header...) */ - -bool MotPDUFrame::decode(const uint8_t* data) -{ - assert(data != nullptr); - - // create a new start of stream - if (startOfStream != nullptr) - delete startOfStream; - startOfStream = new MotStartOfStream(); - - // create a buffer to decode the start record skipping the 10th byte (adjMM) - uint8_t startBuffer[MotStartOfStream::LENGTH]; - ::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); - ::memcpy(startBuffer + 1U, data, 4U); - - // decode start of stream - startOfStream->decode(startBuffer); - - ::memcpy(pduHeaderData, data + 9U, P25_PDU_HEADER_LENGTH_BYTES); - - return true; -} - -/* Encode a PDU frame. (only the PDU data header...) */ - -void MotPDUFrame::encode(uint8_t* data) -{ - assert(data != nullptr); - assert(startOfStream != nullptr); - - // encode start of stream - scope is intentional - { - uint8_t buffer[MotStartOfStream::LENGTH]; - startOfStream->encode(buffer); - - // copy to data array (skipping first and last bytes) - ::memcpy(data + 1U, buffer + 1U, 4U); - } - - // encode PDU - scope is intentional - { - data[0U] = DFSIFrameType::PDU; - ::memcpy(data + 9U, pduHeaderData, P25_PDU_HEADER_LENGTH_BYTES); - } -} diff --git a/src/common/p25/dfsi/frames/MotPDUFrame.h b/src/common/p25/dfsi/frames/MotPDUFrame.h deleted file mode 100644 index 792143a61..000000000 --- a/src/common/p25/dfsi/frames/MotPDUFrame.h +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file MotPDUFrame.h - * @ingroup dfsi_frames - * @file MotPDUFrame.cpp - * @ingroup dfsi_frames - */ -#if !defined(__MOT_PDU_FRAME_H__) -#define __MOT_PDU_FRAME_H__ - -#include "Defines.h" -#include "common/Defines.h" -#include "common/Log.h" -#include "common/Utils.h" -#include "common/p25/dfsi/frames/FrameDefines.h" -#include "common/p25/dfsi/frames/MotStartOfStream.h" -#include "common/p25/dfsi/frames/MotFullRateVoice.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Implements a P25 Motorola PDU frame. - * \code{.unparsed} - * Byte 0 1 2 3 - * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved ? | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | PDU Header | - * + + - * | | - * + + - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * \endcode - * @ingroup dfsi_frames - */ - class HOST_SW_API MotPDUFrame { - public: - static const uint8_t LENGTH = 20U; - - /** - * @brief Initializes a copy instance of the MotPDUFrame class. - */ - MotPDUFrame(); - /** - * @brief Initializes a copy instance of the MotPDUFrame class. - * @param data Buffer to containing MotPDUFrame to decode. - */ - MotPDUFrame(uint8_t* data); - /** - * @brief Finalizes a instance of the MotPDUFrame class. - */ - ~MotPDUFrame(); - - /** - * @brief Decode a PDU frame. (only the PDU data header...) - * @param[in] data Buffer to containing MotPDUFrame to decode. - */ - bool decode(const uint8_t* data); - /** - * @brief Encode a PDU frame. (only the PDU data header...) - * @param[out] data Buffer to encode a MotPDUFrame. - */ - void encode(uint8_t* data); - - public: - MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters - uint8_t* pduHeaderData; // ?? - this should probably be private with getters/setters - }; - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __MOT_PDU_FRAME_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.cpp b/src/common/p25/dfsi/frames/MotStartOfStream.cpp index 0ce7e6cd5..edabe4be4 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.cpp +++ b/src/common/p25/dfsi/frames/MotStartOfStream.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/MotStartOfStream.h" @@ -28,34 +28,44 @@ using namespace p25::dfsi::frames; /* Initializes a instance of the MotStartOfStream class. */ MotStartOfStream::MotStartOfStream() : - m_marker(FIXED_MARKER), - m_rt(RTFlag::DISABLED), - m_startStop(StartStopFlag::START), - m_streamType(StreamTypeFlag::VOICE) + m_format(DFSI_MOT_ICW_FMT_TYPE3), + m_opcode(MotStartStreamOpcode::TRANSMIT), + icw(nullptr) { - /* stub */ + icw = new uint8_t[DFSI_MOT_ICW_LENGTH]; + ::memset(icw, 0x00U, DFSI_MOT_ICW_LENGTH); } /* Initializes a instance of the MotStartOfStream class. */ MotStartOfStream::MotStartOfStream(uint8_t* data) : - m_marker(FIXED_MARKER), - m_rt(RTFlag::DISABLED), - m_startStop(StartStopFlag::START), - m_streamType(StreamTypeFlag::VOICE) + m_format(DFSI_MOT_ICW_FMT_TYPE3), + m_opcode(MotStartStreamOpcode::TRANSMIT), + icw(nullptr) { + icw = new uint8_t[DFSI_MOT_ICW_LENGTH]; + ::memset(icw, 0x00U, DFSI_MOT_ICW_LENGTH); + decode(data); } +/* Finalizes a instance of the MotStartOfStream class. */ + +MotStartOfStream::~MotStartOfStream() +{ + if (icw != nullptr) + delete icw; +} + /* Decode a start of stream frame. */ bool MotStartOfStream::decode(const uint8_t* data) { assert(data != nullptr); - m_rt = (RTFlag::E)data[2U]; - m_startStop = (StartStopFlag::E)data[3U]; - m_streamType = (StreamTypeFlag::E)data[4U]; + m_format = data[1U] & 0x3FU; + m_opcode = (MotStartStreamOpcode::E)data[2U]; + ::memcpy(icw, data + 3U, DFSI_MOT_ICW_LENGTH); return true; } @@ -67,8 +77,7 @@ void MotStartOfStream::encode(uint8_t* data) assert(data != nullptr); data[0U] = DFSIFrameType::MOT_START_STOP; - data[1U] = FIXED_MARKER; - data[2U] = m_rt; - data[3U] = m_startStop; - data[4U] = m_streamType; + data[1U] = m_format & 0x3FU; + data[2U] = m_opcode; + ::memcpy(data + 3U, icw, DFSI_MOT_ICW_LENGTH); } diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.h b/src/common/p25/dfsi/frames/MotStartOfStream.h index 14f4897b0..52e37a3e7 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.h +++ b/src/common/p25/dfsi/frames/MotStartOfStream.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -39,20 +39,17 @@ namespace p25 * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Fixed Mark | RT Mode Flag | Start/Stop | Type Flag | + * | FT | ICW Format | Opcode | Param 1 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | | + * | Argment 1 | Param 2 | Argument 2 | Param 3 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Argment 3 | * +-+-+-+-+-+-+-+-+ * \endcode * @ingroup dfsi_frames */ class HOST_SW_API MotStartOfStream { public: - static const uint8_t LENGTH = 10U; - static const uint8_t FIXED_MARKER = 0x02U; - /** * @brief Initializes a copy instance of the MotStartOfStream class. */ @@ -62,6 +59,10 @@ namespace p25 * @param data Buffer to containing MotStartOfStream to decode. */ MotStartOfStream(uint8_t* data); + /** + * @brief Finalizes a instance of the MotStartOfStream class. + */ + ~MotStartOfStream(); /** * @brief Decode a start of stream frame. @@ -73,24 +74,92 @@ namespace p25 * @param[out] data Buffer to encode a MotStartOfStream. */ void encode(uint8_t* data); - - public: + + /** @name Start of Stream Type 3 control word parameters. */ + /** + * @brief Helper to get parameter 1 of the control word. + * @return uint8_t Parameter 1 of the control word. + */ + uint8_t getParam1() const { return icw[0U]; } /** - * @brief + * @brief Helper to get the argument for parameter 1 of the control word. + * @return uint8_t Argument 1 for parameter 1 of the control word. */ - DECLARE_PROPERTY(uint8_t, marker, Marker); + uint8_t getArgument1() const { return icw[1U]; } /** - * @brief RT/RT Flag. + * @brief Helper to set parameter 1 of the control word. + * @param value Parameter 1 of the control word. */ - DECLARE_PROPERTY(RTFlag::E, rt, RT); + void setParam1(uint8_t value) { icw[0U] = value; } /** - * @brief Start/Stop. + * @brief Helper to set the argument for parameter 1 of the control word. + * @param value Argument 1 for parameter 1 of the control word. */ - DECLARE_PROPERTY(StartStopFlag::E, startStop, StartStop); + void setArgument1(uint8_t value) { icw[1U] = value; } + /** - * @brief Stream Type. + * @brief Helper to get parameter 2 of the control word. + * @return uint8_t Parameter 2 of the control word. */ - DECLARE_PROPERTY(StreamTypeFlag::E, streamType, StreamType); + uint8_t getParam2() const { return icw[2U]; } + /** + * @brief Helper to get the argument for parameter 2 of the control word. + * @return uint8_t Argument 2 for parameter 2 of the control word. + */ + uint8_t getArgument2() const { return icw[3U]; } + /** + * @brief Helper to set parameter 1 of the control word. + * @param value Parameter 1 of the control word. + */ + void setParam2(uint8_t value) { icw[2U] = value; } + /** + * @brief Helper to set the argument for parameter 2 of the control word. + * @param value Argument for parameter 2 of the control word. + */ + void setArgument2(uint8_t value) { icw[3U] = value; } + + /** + * @brief Helper to get parameter 3 of the control word. + * @return uint8_t Parameter 3 of the control word. + */ + uint8_t getParam3() const { return icw[4U]; } + /** + * @brief Helper to get argument 3 for parameter 3 of the control word. + * @return uint8_t Argument for parameter 3 of the control word. + */ + uint8_t getArgument3() const { return icw[5U]; } + /** + * @brief Helper to set parameter 3 of the control word. + * @param value Parameter 3 of the control word. + */ + void setParam3(uint8_t value) { icw[4U] = value; } + /** + * @brief Helper to set the argument for parameter 3 of the control word. + * @param value Argument for parameter 3 of the control word. + */ + void setArgument3(uint8_t value) { icw[5U] = value; } + /** @} */ + + /** + * @brief Get the raw ICW parameter/argument buffer. + * @return uint8_t* Raw ICW buffer. + * @note The buffer is 6 bytes long and contains the parameters and arguments for the + * start of stream control word. + */ + uint8_t* getICW() const { return icw; } + + public: + /** + * @brief Format. + */ + DECLARE_PROPERTY(uint8_t, format, Format); + /** + * @brief Opcode. + */ + DECLARE_PROPERTY(MotStartStreamOpcode::E, opcode, Opcode); + + private: + uint8_t* icw; }; } // namespace frames } // namespace dfsi diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp b/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp index d037f99f0..5a899ec5d 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/MotStartVoiceFrame.h" @@ -30,10 +30,7 @@ using namespace p25::dfsi::frames; MotStartVoiceFrame::MotStartVoiceFrame() : startOfStream(nullptr), fullRateVoice(nullptr), - m_icw(ICWFlag::DIU), - m_rssiValidity(RssiValidityFlag::INVALID), - m_rssi(0U), - m_adjMM(0U) + m_totalErrors(0U) { startOfStream = new MotStartOfStream(); fullRateVoice = new MotFullRateVoice(); @@ -44,10 +41,7 @@ MotStartVoiceFrame::MotStartVoiceFrame() : MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data) : startOfStream(nullptr), fullRateVoice(nullptr), - m_icw(ICWFlag::DIU), - m_rssiValidity(RssiValidityFlag::INVALID), - m_rssi(0U), - m_adjMM(0U) + m_totalErrors(0U) { decode(data); } @@ -74,9 +68,9 @@ bool MotStartVoiceFrame::decode(const uint8_t* data) startOfStream = new MotStartOfStream(); // create a buffer to decode the start record skipping the 10th byte (adjMM) - uint8_t startBuffer[MotStartOfStream::LENGTH]; - ::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); - ::memcpy(startBuffer, data, 9U); + uint8_t startBuffer[DFSI_MOT_START_LEN]; + ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN); + ::memcpy(startBuffer, data, DFSI_MOT_START_LEN); // decode start of stream startOfStream->decode(startBuffer); @@ -92,15 +86,6 @@ bool MotStartVoiceFrame::decode(const uint8_t* data) ::memcpy(voiceBuffer + 1U, data + 10U, MotFullRateVoice::SHORTENED_LENGTH - 1); fullRateVoice->decode(voiceBuffer, true); - // get rest of data - m_icw = (ICWFlag::E)data[5U]; // this field is dubious and questionable - //data[6U]; // unknown -- based on testing this is not related to RSSI - m_rssiValidity = (RssiValidityFlag::E)data[7U]; // this field is dubious and questionable - - m_rssi = data[8U]; - - m_adjMM = data[9U]; // this field is dubious and questionable - return true; } @@ -114,11 +99,11 @@ void MotStartVoiceFrame::encode(uint8_t* data) // encode start of stream - scope is intentional { - uint8_t buffer[MotStartOfStream::LENGTH]; + uint8_t buffer[DFSI_MOT_START_LEN]; startOfStream->encode(buffer); - // copy to data array (skipping first and last bytes) - ::memcpy(data + 1U, buffer + 1U, MotStartOfStream::LENGTH - 2); + // copy to data array (skipping first byte which is frame type) + ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U); } // encode full rate voice - scope is intentional @@ -129,13 +114,4 @@ void MotStartVoiceFrame::encode(uint8_t* data) data[0U] = fullRateVoice->getFrameType(); ::memcpy(data + 10U, buffer + 1U, MotFullRateVoice::SHORTENED_LENGTH - 1); } - - // Copy the rest - data[5U] = m_icw; // this field is dubious and questionable - data[6U] = 0U; // unknown -- based on testing this is not related to RSSI - data[7U] = m_rssiValidity; // this field is dubious and questionable - - data[8U] = m_rssi; - - data[9U] = m_adjMM; // this field is dubious and questionable } diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h index 966af5897..237e72a07 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -41,16 +41,16 @@ namespace p25 * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | + * | FT | Encoded Motorola Start of Stream | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+ + * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | ICW Flag ? | RSSI | RSSI Valid | RSSI | + * | | Full Rate Voice Frame | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Adj MM ? | Full Rate Voice Frame | - * +-+-+-+-+-+-+-+-+ + * | | - * + + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +=+=+=+=+=+=+=+=+ * \endcode @@ -90,26 +90,9 @@ namespace p25 MotFullRateVoice* fullRateVoice; // ?? - this should probably be private with getters/setters /** - * @brief - * @note bryanb: I doubt the authenticity of this field. - */ - DECLARE_PROPERTY(ICWFlag::E, icw, ICW); - /** - * @brief Flag indicating whether or not the RSSI field is valid. - * @note bryanb: I doubt the authenticity of this field. - */ - DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity); - - /** - * @brief RSSI Value. - */ - DECLARE_PROPERTY(uint8_t, rssi, RSSI); - - /** - * @brief - * @note bryanb: I doubt the authenticity of this field. + * @brief Total errors detected in the frame. */ - DECLARE_PROPERTY(uint8_t, adjMM, AdjMM); + DECLARE_PROPERTY(uint8_t, totalErrors, TotalErrors); }; } // namespace frames } // namespace dfsi diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.cpp b/src/common/p25/dfsi/frames/MotTSBKFrame.cpp index 61fe0ed20..1ac6875ba 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.cpp +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/P25Defines.h" @@ -71,15 +71,15 @@ bool MotTSBKFrame::decode(const uint8_t* data) delete startOfStream; startOfStream = new MotStartOfStream(); - // create a buffer to decode the start record skipping the 10th byte (adjMM) - uint8_t startBuffer[MotStartOfStream::LENGTH]; - ::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); - ::memcpy(startBuffer + 1U, data, 4U); + // create a buffer to decode the start record + uint8_t startBuffer[DFSI_MOT_START_LEN]; + ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN); + ::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U); // decode start of stream startOfStream->decode(startBuffer); - ::memcpy(tsbkData, data + 9U, P25_TSBK_LENGTH_BYTES); + ::memcpy(tsbkData, data + DFSI_MOT_START_LEN, P25_TSBK_LENGTH_BYTES); return true; } @@ -93,16 +93,16 @@ void MotTSBKFrame::encode(uint8_t* data) // encode start of stream - scope is intentional { - uint8_t buffer[MotStartOfStream::LENGTH]; + uint8_t buffer[DFSI_MOT_START_LEN]; startOfStream->encode(buffer); - // copy to data array (skipping first and last bytes) - ::memcpy(data + 1U, buffer + 1U, 4U); + // copy to data array + ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U); } // encode TSBK - scope is intentional { - data[0U] = DFSIFrameType::TSBK; - ::memcpy(data + 9U, tsbkData, P25_TSBK_LENGTH_BYTES); + data[0U] = DFSIFrameType::MOT_TSBK; + ::memcpy(data + DFSI_MOT_START_LEN, tsbkData, P25_TSBK_LENGTH_BYTES); } } diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.h b/src/common/p25/dfsi/frames/MotTSBKFrame.h index 654f26409..c7aab058b 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.h +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -40,25 +40,23 @@ namespace p25 * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | + * | FT | Encoded Motorola Start of Stream | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved ? | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | TSBK | - * + + * | | - * + + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | TSBK | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Unknown ? | + * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \endcode * @ingroup dfsi_frames */ class HOST_SW_API MotTSBKFrame { public: - static const uint8_t LENGTH = 24U; - /** * @brief Initializes a copy instance of the MotTSBKFrame class. */ diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp b/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp deleted file mode 100644 index 4ab21afc7..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/dfsi/frames/MotVoiceHeader1.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25; -using namespace p25::dfsi; -using namespace p25::dfsi::defines; -using namespace p25::dfsi::frames; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the MotVoiceHeader1 class. */ - -MotVoiceHeader1::MotVoiceHeader1() : - header(nullptr), - startOfStream(nullptr), - m_icw(ICWFlag::DIU), - m_rssiValidity(RssiValidityFlag::INVALID), - m_rssi(0U) -{ - startOfStream = new MotStartOfStream(); - - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); -} - -/* Initializes a instance of the MotVoiceHeader1 class. */ - -MotVoiceHeader1::MotVoiceHeader1(uint8_t* data) : - header(nullptr), - startOfStream(nullptr), - m_icw(ICWFlag::DIU), - m_rssiValidity(RssiValidityFlag::INVALID), - m_rssi(0U) -{ - decode(data); -} - -/* Finalizes a instance of the MotVoiceHeader1 class. */ - -MotVoiceHeader1::~MotVoiceHeader1() -{ - if (startOfStream != nullptr) - delete startOfStream; - if (header != nullptr) - delete[] header; -} - -/* Decode a voice header 1 frame. */ - -bool MotVoiceHeader1::decode(const uint8_t* data) -{ - assert(data != nullptr); - - // create a start of stream - if (startOfStream != nullptr) - delete startOfStream; - startOfStream = new MotStartOfStream(); - - uint8_t buffer[MotStartOfStream::LENGTH]; - ::memset(buffer, 0x00U, MotStartOfStream::LENGTH); - - // we copy the bytes from [1:4] - ::memcpy(buffer + 1U, data + 1U, 4); - startOfStream->decode(buffer); - - // decode the other stuff - m_icw = (ICWFlag::E)data[5U]; // this field is dubious and questionable - //data[6U]; // unknown -- based on testing this is not related to RSSI - m_rssiValidity = (RssiValidityFlag::E)data[7U]; // this field is dubious and questionable - - m_rssi = data[8U]; - - // our header includes the trailing source and check bytes - if (header != nullptr) - delete[] header; - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); - ::memcpy(header, data + 9U, HCW_LENGTH); - - return true; -} - -/* Encode a voice header 1 frame. */ - -void MotVoiceHeader1::encode(uint8_t* data) -{ - assert(data != nullptr); - assert(startOfStream != nullptr); - - data[0U] = DFSIFrameType::MOT_VHDR_1; - - // scope is intentional - { - uint8_t buffer[MotStartOfStream::LENGTH]; - ::memset(buffer, 0x00U, MotStartOfStream::LENGTH); - startOfStream->encode(buffer); - - // copy the 4 start record bytes from the start of stream frame - ::memcpy(data + 1U, buffer + 1U, 4U); - } - - data[5U] = m_icw; // this field is dubious and questionable - data[6U] = 0U; // unknown -- based on testing this is not related to RSSI - data[7U] = m_rssiValidity; // this field is dubious and questionable - - data[8U] = m_rssi; - - // our header includes the trailing source and check bytes - if (header != nullptr) { - ::memcpy(data + 9U, header, HCW_LENGTH); - } -} diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.h b/src/common/p25/dfsi/frames/MotVoiceHeader1.h deleted file mode 100644 index a4e5d807e..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.h +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file MotVoiceHeader1.h - * @ingroup dfsi_frames - * @file MotVoiceHeader1.cpp - * @ingroup dfsi_frames - */ -#if !defined(__MOT_VOICE_HEADER_1_H__) -#define __MOT_VOICE_HEADER_1_H__ - -#include "Defines.h" -#include "common/Defines.h" -#include "common/Log.h" -#include "common/Utils.h" -#include "common/p25/dfsi/frames/FrameDefines.h" -#include "common/p25/dfsi/frames/MotStartOfStream.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Implements a P25 Motorola voice header frame 1. - * \code{.unparsed} - * Byte 0 1 2 3 - * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | ICW Flag ? | RSSI | RSSI Valid | RSSI | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Header Control Word | - * + + - * | | - * + + - * | | - * + + - * | | - * + + - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Src Flag | - * +-+-+-+-+-+-+-+-+ - * \endcode - * @ingroup dfsi_frames - */ - class HOST_SW_API MotVoiceHeader1 { - public: - static const uint8_t LENGTH = 30U; - static const uint8_t HCW_LENGTH = 21U; - - /** - * @brief Initializes a copy instance of the MotVoiceHeader1 class. - */ - MotVoiceHeader1(); - /** - * @brief Initializes a copy instance of the MotVoiceHeader1 class. - * @param data Buffer to containing MotVoiceHeader1 to decode. - */ - MotVoiceHeader1(uint8_t* data); - /** - * @brief Finalizes a instance of the MotVoiceHeader1 class. - */ - ~MotVoiceHeader1(); - - /** - * @brief Decode a voice header 1 frame. - * @param[in] data Buffer to containing MotVoiceHeader1 to decode. - */ - bool decode(const uint8_t* data); - /** - * @brief Encode a voice header 1 frame. - * @param[out] data Buffer to encode a MotVoiceHeader1. - */ - void encode(uint8_t* data); - - public: - uint8_t* header; // ?? - this should probably be private with getters/setters - MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters - - /** - * @brief - * @note bryanb: I doubt the authenticity of this field. - */ - DECLARE_PROPERTY(ICWFlag::E, icw, ICW); - /** - * @brief Flag indicating whether or not the RSSI field is valid. - * @note bryanb: I doubt the authenticity of this field. - */ - DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity); - - /** - * @brief RSSI Value. - */ - DECLARE_PROPERTY(uint8_t, rssi, RSSI); - }; - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __MOT_VOICE_HEADER_1_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader2.cpp b/src/common/p25/dfsi/frames/MotVoiceHeader2.cpp deleted file mode 100644 index 0bfd227be..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader2.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/dfsi/frames/MotVoiceHeader2.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25; -using namespace p25::dfsi; -using namespace p25::dfsi::defines; -using namespace p25::dfsi::frames; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the MotVoiceHeader2 class. */ - -MotVoiceHeader2::MotVoiceHeader2() : - header(nullptr), - m_source(SourceFlag::QUANTAR) -{ - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); -} - -/* Initializes a instance of the MotVoiceHeader2 class. */ - -MotVoiceHeader2::MotVoiceHeader2(uint8_t* data) : - header(nullptr), - m_source(SourceFlag::QUANTAR) -{ - decode(data); -} - -/* Finalizes a instance of the MotVoiceHeader2 class. */ - -MotVoiceHeader2::~MotVoiceHeader2() -{ - if (header != nullptr) - delete[] header; -} - -/* Decode a voice header 2 frame. */ - -bool MotVoiceHeader2::decode(const uint8_t* data) -{ - assert(data != nullptr); - - m_source = (SourceFlag::E)data[21]; - - if (header != nullptr) { - delete[] header; - } - - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); - ::memcpy(header, data + 1U, HCW_LENGTH); - - return true; -} - -/* Encode a voice header 2 frame. */ - -void MotVoiceHeader2::encode(uint8_t* data) -{ - assert(data != nullptr); - - data[0U] = DFSIFrameType::MOT_VHDR_2; - - if (header != nullptr) { - ::memcpy(data + 1U, header, HCW_LENGTH); - } - - data[LENGTH - 1U] = (uint8_t)m_source; -} diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader2.h b/src/common/p25/dfsi/frames/MotVoiceHeader2.h deleted file mode 100644 index a1918a77c..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader2.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file MotVoiceHeader2.h - * @ingroup dfsi_frames - * @file MotVoiceHeader2.cpp - * @ingroup dfsi_frames - */ -#if !defined(__MOT_VOICE_HEADER_2_H__) -#define __MOT_VOICE_HEADER_2_H__ - -#include "Defines.h" -#include "common/Defines.h" -#include "common/Log.h" -#include "common/Utils.h" -#include "common/p25/dfsi/frames/FrameDefines.h" -#include "common/p25/dfsi/frames/MotStartOfStream.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Implements a P25 Motorola voice header frame 2. - * \code{.unparsed} - * Byte 0 1 2 3 - * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Header Control Word | - * + + - * | | - * + + - * | | - * + + - * | | - * + + - * | | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | | Reserved | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * \endcode - * @ingroup dfsi_frames - */ - class HOST_SW_API MotVoiceHeader2 { - public: - static const uint8_t LENGTH = 22U; - static const uint8_t HCW_LENGTH = 20U; - - /** - * @brief Initializes a copy instance of the MotVoiceHeader2 class. - */ - MotVoiceHeader2(); - /** - * @brief Initializes a copy instance of the MotVoiceHeader2 class. - * @param data Buffer to containing MotVoiceHeader2 to decode. - */ - MotVoiceHeader2(uint8_t* data); - /** - * @brief Finalizes a instance of the MotVoiceHeader2 class. - */ - ~MotVoiceHeader2(); - - /** - * @brief Decode a voice header 2 frame. - * @param[in] data Buffer to containing MotVoiceHeader2 to decode. - */ - bool decode(const uint8_t* data); - /** - * @brief Encode a voice header 2 frame. - * @param[out] data Buffer to encode a MotVoiceHeader2. - */ - void encode(uint8_t* data); - - public: - uint8_t* header; // ?? - this should probably be a private with getters/setters - - /** - * @brief V.24 Data Source. - */ - DECLARE_PROPERTY(SourceFlag::E, source, Source); - }; - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __MOT_VOICE_HEADER_2_H__ \ No newline at end of file diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 1be8a3812..a8863052e 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -598,18 +598,18 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::MOT_START_STOP: { MotStartOfStream start = MotStartOfStream(dfsiData); - if (start.getStartStop() == StartStopFlag::START) { + if (start.getParam1() == DSFI_MOT_ICW_PARM_PAYLOAD) { m_rxCall->resetCallData(); m_rxCallInProgress = true; if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW START, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } } else { if (m_rxCallInProgress) { m_rxCall->resetCallData(); m_rxCallInProgress = false; if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW STOP, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } // generate a TDU create_TDU(buffer); @@ -621,32 +621,38 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::MOT_VHDR_1: { - MotVoiceHeader1 vhdr1 = MotVoiceHeader1(dfsiData); + MotStartOfStream start = MotStartOfStream(dfsiData); // copy to call data VHDR1 - ::memset(m_rxCall->VHDR1, 0x00U, MotVoiceHeader1::HCW_LENGTH); - ::memcpy(m_rxCall->VHDR1, vhdr1.header, MotVoiceHeader1::HCW_LENGTH); + ::memset(m_rxCall->VHDR1, 0x00U, DFSI_MOT_VHDR_1_LEN); + ::memcpy(m_rxCall->VHDR1, dfsiData + DFSI_MOT_START_LEN, DFSI_MOT_VHDR_1_LEN - DFSI_MOT_START_LEN); + + // Utils::dump("V.24 RX VHDR1", m_rxCall->VHDR1, DFSI_MOT_VHDR_1_LEN); } break; case DFSIFrameType::MOT_VHDR_2: { - MotVoiceHeader2 vhdr2 = MotVoiceHeader2(dfsiData); + MotStartOfStream start = MotStartOfStream(dfsiData); // copy to call data VHDR2 - ::memset(m_rxCall->VHDR2, 0x00U, MotVoiceHeader2::HCW_LENGTH); - ::memcpy(m_rxCall->VHDR2, vhdr2.header, MotVoiceHeader2::HCW_LENGTH); + ::memset(m_rxCall->VHDR2, 0x00U, DFSI_MOT_VHDR_2_LEN); + ::memcpy(m_rxCall->VHDR2, dfsiData + 1U, DFSI_MOT_VHDR_2_LEN); + + // Utils::dump("V.24 RX VHDR2", m_rxCall->VHDR2, DFSI_MOT_VHDR_2_LEN); // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; ::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN); - ::memcpy(raw, m_rxCall->VHDR1, 8U); - ::memcpy(raw + 8U, m_rxCall->VHDR1 + 9U, 8U); - ::memcpy(raw + 16U, m_rxCall->VHDR1 + 18U, 2U); + ::memcpy(raw + 0U, m_rxCall->VHDR1, 8U); + ::memcpy(raw + 8U, m_rxCall->VHDR1 + 9U, 8U); + ::memcpy(raw + 16U, m_rxCall->VHDR1 + 18U, 2U); - ::memcpy(raw + 18U, m_rxCall->VHDR2, 8U); - ::memcpy(raw + 26U, m_rxCall->VHDR2 + 9U, 8U); - ::memcpy(raw + 34U, m_rxCall->VHDR2 + 18U, 2U); + ::memcpy(raw + 18U, m_rxCall->VHDR2, 8U); + ::memcpy(raw + 26U, m_rxCall->VHDR2 + 9U, 8U); + ::memcpy(raw + 34U, m_rxCall->VHDR2 + 18U, 2U); + + // Utils::dump("V.24 RX VHDR raw", raw, DFSI_VHDR_RAW_LEN); // buffer for decoded VHDR data uint8_t vhdr[DFSI_VHDR_LEN]; @@ -655,6 +661,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) Utils::hex2Bin(raw[i], vhdr, offset); + // Utils::dump("V.24 RX VHDR vhdr", vhdr, DFSI_VHDR_LEN); + // try to decode the RS data try { bool ret = m_rs.decode362017(vhdr); @@ -669,7 +677,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX VHDR late entry, resetting call data"); } - uint32_t dstId = GET_UINT32(vhdr, 13U); + uint32_t dstId = GET_UINT16(vhdr, 13U); if (m_rxCallInProgress && dstId == 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; @@ -723,8 +731,32 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + // process start of stream ICW for the voice call + uint8_t* icw = svf.startOfStream->getICW(); + uint8_t payloadType = MotStreamPayload::VOICE; + uint8_t rssi = 20U; + + if (icw != nullptr) { + for (uint8_t i = 0U; i < 6U; i += 2U) { + uint8_t param = icw[i]; + uint8_t value = icw[i + 1U]; + + switch (param) { + case DSFI_MOT_ICW_PARM_PAYLOAD: + payloadType = value; + break; + case DFSI_MOT_ICW_PARM_RSSI: + rssi = value; + break; + default: + LogWarning(LOG_MODEM, "ModemV24::convertToAir() unknown ICW parameter $%02X with value %u", param, value); + break; + } + } + } + if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU1, ICW = $%02X, RSSIValid = $%02X, RSSI = $%02X, AdjMM = $%02X", svf.getICW(), svf.getRSSIValidity(), svf.getRSSI(), svf.getAdjMM()); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU1, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } m_rxCall->n++; @@ -736,80 +768,39 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); - if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU2, ICW = $%02X, RSSIValid = $%02X, RSSI = $%02X, AdjMM = $%02X", svf.getICW(), svf.getRSSIValidity(), svf.getRSSI(), svf.getAdjMM()); - } - - m_rxCall->n++; - } - break; - - case DFSIFrameType::PDU: - { - // bryanb: this is gonna be a complete clusterfuck... - MotPDUFrame pf = MotPDUFrame(dfsiData); - data::DataHeader dataHeader = data::DataHeader(); - if (!dataHeader.decode(pf.pduHeaderData, true)) { - LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode PDU FEC"); - } else { - uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; - uint32_t offset = P25_PREAMBLE_LENGTH_BITS; - - DECLARE_UINT8_ARRAY(data, (bitLength / 8U) + 1U); - - uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + // process start of stream ICW for the voice call + uint8_t* icw = svf.startOfStream->getICW(); + uint8_t payloadType = MotStreamPayload::VOICE; + uint8_t rssi = 20U; - if (blocksToFollow > 0U) { - uint32_t dataOffset = MotPDUFrame::LENGTH + 1U; + if (icw != nullptr) { + for (uint8_t i = 0U; i < 6U; i += 2U) { + uint8_t param = icw[i]; + uint8_t value = icw[i + 1U]; - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - data::DataBlock dataBlock = data::DataBlock(); - dataBlock.setFormat(dataHeader); - dataBlock.setSerialNo(i); - dataBlock.setData(dfsiData + dataOffset); - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataBlock.encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES) + 1U; + switch (param) { + case DSFI_MOT_ICW_PARM_PAYLOAD: + payloadType = value; + break; + case DFSI_MOT_ICW_PARM_RSSI: + rssi = value; + break; + default: + LogWarning(LOG_MODEM, "ModemV24::convertToAir() unknown ICW parameter $%02X with value %u", param, value); + break; } } + } - uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - - buffer[0U] = modem::TAG_DATA; - buffer[1U] = 0x00U; - - // add the data - uint32_t newBitLength = P25Utils::encodeByLength(data, buffer + 2U, bitLength); - uint32_t newByteLength = newBitLength / 8U; - if ((newBitLength % 8U) > 0U) - newByteLength++; - - // generate Sync - Sync::addP25Sync(buffer + 2U); - - // generate NID - m_nid->encode(buffer + 2U, DUID::PDU); - - // add status bits - P25Utils::addStatusBits(buffer + 2U, newBitLength, false, false); - P25Utils::addIdleStatusBits(buffer + 2U, newBitLength); - P25Utils::setStatusBitsStartIdle(buffer + 2U); - - storeConvertedRx(buffer, P25_PDU_FRAME_LENGTH_BYTES + 2U); + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU2, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } + + m_rxCall->n++; } break; - case DFSIFrameType::TSBK: + case DFSIFrameType::MOT_TSBK: { MotTSBKFrame tf = MotTSBKFrame(dfsiData); lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); @@ -848,7 +839,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) MotFullRateVoice voice = MotFullRateVoice(dfsiData); if (m_debug) { - LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "Full Rate Voice, frameType = %x, source = %u", voice.getFrameType(), voice.getSource()); + LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "Full Rate Voice, frameType = $%02X, errors = %u, busy = %u", voice.getFrameType(), voice.getTotalErrors(), voice.getBusy()); Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } @@ -1365,7 +1356,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } - uint32_t dstId = GET_UINT32(vhdr, 13U); + uint32_t dstId = GET_UINT16(vhdr, 13U); if (m_rxCallInProgress && dstId == 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; @@ -1419,7 +1410,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) voice.decode(dfsiData + dataOffs); if (m_debug) { - LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Full Rate Voice, frameType = %x, busy = %u, lostFrame = %u, muteFrame = %u, superFrameCnt = %u", voice.getFrameType(), voice.getBusy(), voice.getLostFrame(), voice.getMuteFrame(), voice.getSuperframeCnt(), voice.getTotalErrors()); + LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Full Rate Voice, frameType = $%02X, busy = %u, lostFrame = %u, muteFrame = %u, superFrameCnt = %u, errors = %u", voice.getFrameType(), voice.getBusy(), voice.getLostFrame(), voice.getMuteFrame(), voice.getSuperframeCnt(), voice.getTotalErrors()); Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } @@ -1904,18 +1895,19 @@ void ModemV24::startOfStream(const p25::lc::LC& control) m_txCallInProgress = true; MotStartOfStream start = MotStartOfStream(); - start.setStartStop(StartStopFlag::START); - start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::VOICE); // create buffer for bytes and encode - uint8_t startBuf[start.LENGTH]; - ::memset(startBuf, 0x00U, start.LENGTH); + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH); + Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, DFSI_MOT_START_LEN); - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); @@ -1942,40 +1934,36 @@ void ModemV24::startOfStream(const p25::lc::LC& control) } // prepare VHDR1 - MotVoiceHeader1 vhdr1 = MotVoiceHeader1(); - vhdr1.startOfStream->setStartStop(StartStopFlag::START); - vhdr1.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - vhdr1.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + uint8_t vhdr1Buf[DFSI_MOT_VHDR_1_LEN]; + ::memset(vhdr1Buf, 0x00U, DFSI_MOT_VHDR_1_LEN); + start.encode(vhdr1Buf); - ::memcpy(vhdr1.header, raw, 8U); - ::memcpy(vhdr1.header + 9U, raw + 8U, 8U); - ::memcpy(vhdr1.header + 18U, raw + 16U, 2U); + ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 0U, raw, 8U); + ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 9U, raw + 8U, 8U); + ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 18U, raw + 16U, 2U); - // encode VHDR1 and send - uint8_t vhdr1Buf[vhdr1.LENGTH]; - ::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH); - vhdr1.encode(vhdr1Buf); + vhdr1Buf[20U + DFSI_MOT_START_LEN] = DFSI_BUSY_BITS_INBOUND; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH); + Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); - queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE); + queueP25Frame(vhdr1Buf, DFSI_MOT_VHDR_1_LEN, STT_NON_IMBE); // prepare VHDR2 - MotVoiceHeader2 vhdr2 = MotVoiceHeader2(); - ::memcpy(vhdr2.header, raw + 18U, 8U); - ::memcpy(vhdr2.header + 9U, raw + 26U, 8U); - ::memcpy(vhdr2.header + 18U, raw + 34U, 2U); + uint8_t vhdr2Buf[DFSI_MOT_VHDR_2_LEN]; + ::memset(vhdr2Buf, 0x00U, DFSI_MOT_VHDR_2_LEN); + + ::memcpy(vhdr2Buf + 0U, raw + 18U, 8U); + ::memcpy(vhdr2Buf + 9U, raw + 26U, 8U); + ::memcpy(vhdr2Buf + 18U, raw + 34U, 2U); - // encode VHDR2 and send - uint8_t vhdr2Buf[vhdr2.LENGTH]; - ::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH); - vhdr2.encode(vhdr2Buf); + vhdr2Buf[21U] = DFSI_BUSY_BITS_INBOUND; + // send VHDR2 if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH); + Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); - queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE); + queueP25Frame(vhdr2Buf, DFSI_MOT_VHDR_2_LEN, STT_NON_IMBE); } /* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ @@ -1983,17 +1971,19 @@ void ModemV24::startOfStream(const p25::lc::LC& control) void ModemV24::endOfStream() { MotStartOfStream end = MotStartOfStream(); - end.setStartStop(StartStopFlag::STOP); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::VOICE); // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); if (m_trace) - Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH); + Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, DFSI_MOT_START_LEN); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); m_txCallInProgress = false; } @@ -2295,22 +2285,25 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) return; } - MotStartOfStream startOfStream = MotStartOfStream(); - startOfStream.setStartStop(StartStopFlag::START); - startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - startOfStream.setStreamType(StreamTypeFlag::TSBK); + MotStartOfStream start = MotStartOfStream(); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::VOICE); - // create buffer and encode - uint8_t startBuf[MotStartOfStream::LENGTH]; - ::memset(startBuf, 0x00U, MotStartOfStream::LENGTH); - startOfStream.encode(startBuf); + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAir() TSBK MotStartOfStream", startBuf, DFSI_MOT_START_LEN); - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); MotTSBKFrame tf = MotTSBKFrame(); - tf.startOfStream->setStartStop(StartStopFlag::START); - tf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - tf.startOfStream->setStreamType(StreamTypeFlag::TSBK); + tf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + tf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + tf.startOfStream->setArgument1(MotStreamPayload::TSBK); delete[] tf.tsbkData; tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; @@ -2318,27 +2311,27 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); // create buffer and encode - uint8_t tsbkBuf[MotTSBKFrame::LENGTH]; - ::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH); + uint8_t tsbkBuf[DFSI_MOT_TSBK_LEN]; + ::memset(tsbkBuf, 0x00U, DFSI_MOT_TSBK_LEN); tf.encode(tsbkBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH); + Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); - queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(tsbkBuf, DFSI_MOT_TSBK_LEN, STT_NON_IMBE_NO_JITTER); - MotStartOfStream endOfStream = MotStartOfStream(); - endOfStream.setStartStop(StartStopFlag::STOP); - endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - endOfStream.setStreamType(StreamTypeFlag::TSBK); + MotStartOfStream end = MotStartOfStream(); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::TSBK); // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); - endOfStream.encode(endBuf); + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); + end.encode(endBuf); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); } break; @@ -2393,11 +2386,10 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); MotStartVoiceFrame svf = MotStartVoiceFrame(); - svf.startOfStream->setStartStop(StartStopFlag::START); - svf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + svf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + svf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + svf.startOfStream->setArgument1(MotStreamPayload::VOICE); svf.fullRateVoice->setFrameType(voice.getFrameType()); - svf.fullRateVoice->setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); - svf.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); ::memcpy(svf.fullRateVoice->imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); @@ -2410,7 +2402,6 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) case 1: // VOICE2/11 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); - voice.setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); } break; diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 1ed18b3be..0a9be5cbd 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -18,8 +18,6 @@ #include "Defines.h" #include "common/edac/RS634717.h" -#include "common/p25/dfsi/frames/MotVoiceHeader1.h" -#include "common/p25/dfsi/frames/MotVoiceHeader2.h" #include "common/p25/lc/LC.h" #include "common/p25/Audio.h" #include "common/p25/NID.h" @@ -83,8 +81,8 @@ namespace modem netLDU2(nullptr) { MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; - VHDR1 = new uint8_t[p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH]; - VHDR2 = new uint8_t[p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH]; + VHDR1 = new uint8_t[P25DFSIDEF::DFSI_TIA_VHDR_LEN]; + VHDR2 = new uint8_t[P25DFSIDEF::DFSI_TIA_VHDR_LEN]; LDULC = new uint8_t[P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES]; netLDU1 = new uint8_t[9U * 25U]; @@ -135,9 +133,9 @@ namespace modem kId = 0U; if (VHDR1 != nullptr) - ::memset(VHDR1, 0x00U, p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH); + ::memset(VHDR1, 0x00U, P25DFSIDEF::DFSI_TIA_VHDR_LEN); if (VHDR2 != nullptr) - ::memset(VHDR2, 0x00U, p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH); + ::memset(VHDR2, 0x00U, P25DFSIDEF::DFSI_TIA_VHDR_LEN); if (LDULC != nullptr) ::memset(LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); From 76b3deec60464245e975847dff16795058003219 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 4 Aug 2025 22:54:23 -0400 Subject: [PATCH 073/133] hide RSSI2 ICW errors; report on any voice frames reporting more then 0 errors; --- src/common/p25/dfsi/DFSIDefines.h | 3 ++- src/host/modem/ModemV24.cpp | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index 7097f0e53..bde6a1767 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -83,7 +83,8 @@ namespace p25 const uint8_t DFSI_MOT_ICW_PARM_NOP = 0x00U; //! No Operation const uint8_t DSFI_MOT_ICW_PARM_PAYLOAD = 0x0CU; //! Stream Payload - const uint8_t DFSI_MOT_ICW_PARM_RSSI = 0x1AU; //! RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_RSSI1 = 0x1AU; //! RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_RSSI2 = 0x1BU; //! RSSI Data const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //! Stop Stream const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //! Talkaround diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index a8863052e..8187b2f45 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -745,9 +745,12 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DSFI_MOT_ICW_PARM_PAYLOAD: payloadType = value; break; - case DFSI_MOT_ICW_PARM_RSSI: + case DFSI_MOT_ICW_PARM_RSSI1: rssi = value; break; + case DFSI_MOT_ICW_PARM_RSSI2: + // don't do anything with this RSSI + break; default: LogWarning(LOG_MODEM, "ModemV24::convertToAir() unknown ICW parameter $%02X with value %u", param, value); break; @@ -759,6 +762,10 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU1, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } + if (svf.fullRateVoice->getTotalErrors() > 0U) { + LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + } + m_rxCall->n++; } break; @@ -782,9 +789,12 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DSFI_MOT_ICW_PARM_PAYLOAD: payloadType = value; break; - case DFSI_MOT_ICW_PARM_RSSI: + case DFSI_MOT_ICW_PARM_RSSI1: rssi = value; break; + case DFSI_MOT_ICW_PARM_RSSI2: + // don't do anything with this RSSI + break; default: LogWarning(LOG_MODEM, "ModemV24::convertToAir() unknown ICW parameter $%02X with value %u", param, value); break; @@ -796,6 +806,10 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU2, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } + if (svf.fullRateVoice->getTotalErrors() > 0U) { + LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + } + m_rxCall->n++; } break; @@ -843,6 +857,10 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } + if (voice.getTotalErrors() > 0U) { + LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + } + switch (frameType) { case DFSIFrameType::LDU1_VOICE2: { From 0f8493647461db022a8cdc890b4189db95d512df Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 4 Aug 2025 23:04:10 -0400 Subject: [PATCH 074/133] properly sent VHDR1 and VHDR2 with proper opcodes (whoops); --- src/host/modem/ModemV24.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 8187b2f45..df6224aa8 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1960,6 +1960,7 @@ void ModemV24::startOfStream(const p25::lc::LC& control) ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 9U, raw + 8U, 8U); ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 18U, raw + 16U, 2U); + vhdr1Buf[0U] = DFSIFrameType::MOT_VHDR_1; vhdr1Buf[20U + DFSI_MOT_START_LEN] = DFSI_BUSY_BITS_INBOUND; if (m_trace) @@ -1971,10 +1972,11 @@ void ModemV24::startOfStream(const p25::lc::LC& control) uint8_t vhdr2Buf[DFSI_MOT_VHDR_2_LEN]; ::memset(vhdr2Buf, 0x00U, DFSI_MOT_VHDR_2_LEN); - ::memcpy(vhdr2Buf + 0U, raw + 18U, 8U); - ::memcpy(vhdr2Buf + 9U, raw + 26U, 8U); - ::memcpy(vhdr2Buf + 18U, raw + 34U, 2U); + ::memcpy(vhdr2Buf + 1U, raw + 18U, 8U); + ::memcpy(vhdr2Buf + 10U, raw + 26U, 8U); + ::memcpy(vhdr2Buf + 19U, raw + 34U, 2U); + vhdr2Buf[0U] = DFSIFrameType::MOT_VHDR_2; vhdr2Buf[21U] = DFSI_BUSY_BITS_INBOUND; // send VHDR2 From 3cec2cf3c615162b1c330e76b057854e54f05fb3 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 5 Aug 2025 10:03:35 -0400 Subject: [PATCH 075/133] add experimental support for TDULC over V.24; add some documentation for the V.24 and TIA voice header byte layout; --- src/common/p25/dfsi/DFSIDefines.h | 20 +- src/common/p25/dfsi/frames/Frames.h | 1 + src/common/p25/dfsi/frames/MotFullRateVoice.h | 2 +- src/common/p25/dfsi/frames/MotStartOfStream.h | 2 +- .../p25/dfsi/frames/MotStartVoiceFrame.h | 4 +- src/common/p25/dfsi/frames/MotTDULCFrame.cpp | 136 ++++++++++++ src/common/p25/dfsi/frames/MotTDULCFrame.h | 91 ++++++++ src/common/p25/dfsi/frames/MotTSBKFrame.h | 4 +- src/common/p25/lc/TDULC.cpp | 118 ++++++---- src/common/p25/lc/TDULC.h | 18 +- src/common/p25/lc/TSBK.cpp | 5 +- src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp | 101 +++++++++ src/common/p25/lc/tdulc/LC_TDULC_RAW.h | 86 +++++++ src/common/p25/lc/tdulc/TDULCFactory.h | 1 + src/host/modem/ModemV24.cpp | 154 ++++++++++--- src/host/modem/ModemV24.h | 209 +++++++++++++++++- 16 files changed, 852 insertions(+), 100 deletions(-) create mode 100644 src/common/p25/dfsi/frames/MotTDULCFrame.cpp create mode 100644 src/common/p25/dfsi/frames/MotTDULCFrame.h create mode 100644 src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp create mode 100644 src/common/p25/lc/tdulc/LC_TDULC_RAW.h diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index bde6a1767..86a26bd77 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -43,10 +43,11 @@ namespace p25 const uint32_t DFSI_VHDR_RAW_LEN = 36U; const uint32_t DFSI_VHDR_LEN = 27U; - const uint8_t DFSI_MOT_START_LEN = 9U; - const uint8_t DFSI_MOT_VHDR_1_LEN = 30U; - const uint8_t DFSI_MOT_VHDR_2_LEN = 22U; - const uint8_t DFSI_MOT_TSBK_LEN = 24U; + const uint32_t DFSI_MOT_START_LEN = 9U; + const uint32_t DFSI_MOT_VHDR_1_LEN = 30U; + const uint32_t DFSI_MOT_VHDR_2_LEN = 22U; + const uint32_t DFSI_MOT_TSBK_LEN = 24U; + const uint32_t DFSI_MOT_TDULC_LEN = 21U; const uint32_t DFSI_TIA_VHDR_LEN = 22U; @@ -96,10 +97,10 @@ namespace p25 namespace DFSIFrameType { /** @brief DFSI Frame Type */ enum E : uint8_t { - MOT_START_STOP = 0x00U, // Motorola Start/Stop Stream + MOT_START_STOP = 0x00U, // Motorola/V.24 Start/Stop Stream - MOT_VHDR_1 = 0x60U, // Motorola Voice Header 1 - MOT_VHDR_2 = 0x61U, // Motorola Voice Header 2 + MOT_VHDR_1 = 0x60U, // Motorola/V.24 Voice Header 1 + MOT_VHDR_2 = 0x61U, // Motorola/V.24 Voice Header 2 LDU1_VOICE1 = 0x62U, // IMBE LDU1 - Voice 1 LDU1_VOICE2 = 0x63U, // IMBE LDU1 - Voice 2 @@ -121,8 +122,9 @@ namespace p25 LDU2_VOICE17 = 0x72U, // IMBE LDU2 - Voice 17 + Encryption Sync LDU2_VOICE18 = 0x73U, // IMBE LDU2 - Voice 18 + Low Speed Data - MOT_PDU_SINGLE = 0x87U, // Motorola PDU (Single Block) - MOT_TSBK = 0xA1U // Motorola TSBK (Single Block) + MOT_TDULC = 0x74U, // Motorola/V.24 TDULC + MOT_PDU_SINGLE = 0x87U, // Motorola/V.24 PDU (Single Block) + MOT_TSBK = 0xA1U // Motorola/V.24 TSBK (Single Block) }; } diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index 0931628ed..498546e4d 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -23,6 +23,7 @@ #include "common/p25/dfsi/frames/MotStartOfStream.h" #include "common/p25/dfsi/frames/MotStartVoiceFrame.h" #include "common/p25/dfsi/frames/MotFullRateVoice.h" +#include "common/p25/dfsi/frames/MotTDULCFrame.h" #include "common/p25/dfsi/frames/MotTSBKFrame.h" // FSC diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.h b/src/common/p25/dfsi/frames/MotFullRateVoice.h index 18b4ffe13..06c43616e 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.h +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.h @@ -35,7 +35,7 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola full rate voice packet. + * @brief Implements a P25 Motorola/V.24 full rate voice packet. * \code{.unparsed} * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.h b/src/common/p25/dfsi/frames/MotStartOfStream.h index 52e37a3e7..2d23868ca 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.h +++ b/src/common/p25/dfsi/frames/MotStartOfStream.h @@ -34,7 +34,7 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola start of stream packet. + * @brief Implements a P25 Motorola/V.24 start of stream packet. * \code{.unparsed} * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h index 237e72a07..66df6bf98 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h @@ -36,12 +36,12 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola voice frame 1/10 start. + * @brief Implements a P25 Motorola/V.24 voice frame 1/10 start. * \code{.unparsed} * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | FT | Encoded Motorola Start of Stream | + * | FT | Encoded V.24 Start of Stream | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ diff --git a/src/common/p25/dfsi/frames/MotTDULCFrame.cpp b/src/common/p25/dfsi/frames/MotTDULCFrame.cpp new file mode 100644 index 000000000..a022bbeff --- /dev/null +++ b/src/common/p25/dfsi/frames/MotTDULCFrame.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "common/p25/P25Defines.h" +#include "common/p25/dfsi/frames/MotTDULCFrame.h" +#include "common/p25/dfsi/DFSIDefines.h" +#include "common/Utils.h" +#include "common/Log.h" + +#include +#include + +using namespace p25; +using namespace p25::defines; +using namespace p25::dfsi; +using namespace p25::dfsi::defines; +using namespace p25::dfsi::frames; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a instance of the MotTDULCFrame class. */ + +MotTDULCFrame::MotTDULCFrame() : + startOfStream(nullptr), + tdulcData(nullptr) +{ + tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES]; + ::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + + startOfStream = new MotStartOfStream(); +} + +/* Initializes a instance of the MotTDULCFrame class. */ + +MotTDULCFrame::MotTDULCFrame(uint8_t* data) : + startOfStream(nullptr), + tdulcData(nullptr) +{ + tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES]; + ::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + + decode(data); +} + +/* Finalizes a instance of the MotTDULCFrame class. */ + +MotTDULCFrame::~MotTDULCFrame() +{ + if (startOfStream != nullptr) + delete startOfStream; + if (tdulcData != nullptr) + delete[] tdulcData; +} + +/* Decode a TDULC frame. */ + +bool MotTDULCFrame::decode(const uint8_t* data) +{ + assert(data != nullptr); + + // create a new start of stream + if (startOfStream != nullptr) + delete startOfStream; + startOfStream = new MotStartOfStream(); + + // create a buffer to decode the start record + uint8_t startBuffer[DFSI_MOT_START_LEN]; + ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN); + ::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U); + + // decode start of stream + startOfStream->decode(startBuffer); + + uint8_t tdulcBuffer[9U]; + ::memcpy(tdulcBuffer, data + DFSI_MOT_START_LEN, 9U); + + ::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + tdulcData[0U] = (uint8_t)((tdulcBuffer[0U] >> 3) & 0x3FU); + tdulcData[1U] = (uint8_t)(((tdulcBuffer[0U] & 0x07U) << 3) | ((tdulcBuffer[1U] >> 4) & 0x07U)); + tdulcData[2U] = (uint8_t)(((tdulcBuffer[1U] & 0x0FU) << 2) | ((tdulcBuffer[2U] >> 5) & 0x03U)); + tdulcData[3U] = (uint8_t)(tdulcBuffer[2U] & 0x1FU); + tdulcData[4U] = (uint8_t)((tdulcBuffer[3U] >> 3) & 0x3FU); + tdulcData[5U] = (uint8_t)(((tdulcBuffer[3U] & 0x07U) << 3) | ((tdulcBuffer[4U] >> 4) & 0x07U)); + tdulcData[6U] = (uint8_t)(((tdulcBuffer[4U] & 0x0FU) << 2) | ((tdulcBuffer[5U] >> 5) & 0x03U)); + tdulcData[7U] = (uint8_t)(tdulcBuffer[5U] & 0x1FU); + tdulcData[8U] = (uint8_t)((tdulcBuffer[6U] >> 3) & 0x3FU); + tdulcData[9U] = (uint8_t)(((tdulcBuffer[6U] & 0x07U) << 3) | ((tdulcBuffer[7U] >> 4) & 0x07U)); + tdulcData[10U] = (uint8_t)(((tdulcBuffer[7U] & 0x0FU) << 2) | ((tdulcBuffer[8U] >> 5) & 0x03U)); + tdulcData[11U] = (uint8_t)(tdulcBuffer[8U] & 0x1FU); + + return true; +} + +/* Encode a TDULC frame. */ + +void MotTDULCFrame::encode(uint8_t* data) +{ + assert(data != nullptr); + assert(startOfStream != nullptr); + + // encode start of stream - scope is intentional + { + uint8_t buffer[DFSI_MOT_START_LEN]; + startOfStream->encode(buffer); + + // copy to data array + ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U); + } + + // encode TDULC - scope is intentional + { + data[0U] = DFSIFrameType::MOT_TDULC; + + data[DFSI_MOT_START_LEN + 1U] = (uint8_t)((tdulcData[0U] & 0x3FU) << 3) | ((tdulcData[1U] >> 3) & 0x07U); + data[DFSI_MOT_START_LEN + 2U] = (uint8_t)((tdulcData[1U] & 0x0FU) << 4) | ((tdulcData[2U] >> 2) & 0x0FU); + data[DFSI_MOT_START_LEN + 3U] = (uint8_t)((tdulcData[2U] & 0x03U)) | (tdulcData[3U] & 0x3FU); + + data[DFSI_MOT_START_LEN + 4U] = (uint8_t)((tdulcData[4U] & 0x3FU) << 3) | ((tdulcData[5U] >> 3) & 0x07U); + data[DFSI_MOT_START_LEN + 5U] = (uint8_t)((tdulcData[5U] & 0x0FU) << 4) | ((tdulcData[6U] >> 2) & 0x0FU); + data[DFSI_MOT_START_LEN + 6U] = (uint8_t)((tdulcData[6U] & 0x03U)) | (tdulcData[7U] & 0x3FU); + + data[DFSI_MOT_START_LEN + 7U] = (uint8_t)((tdulcData[8U] & 0x3FU) << 3) | ((tdulcData[9U] >> 3) & 0x07U); + data[DFSI_MOT_START_LEN + 8U] = (uint8_t)((tdulcData[9U] & 0x0FU) << 4) | ((tdulcData[10U] >> 2) & 0x0FU); + data[DFSI_MOT_START_LEN + 9U] = (uint8_t)((tdulcData[10U] & 0x03U)) | (tdulcData[11U] & 0x3FU); + + data[DFSI_MOT_START_LEN + 11U] = DFSI_BUSY_BITS_IDLE; + } +} diff --git a/src/common/p25/dfsi/frames/MotTDULCFrame.h b/src/common/p25/dfsi/frames/MotTDULCFrame.h new file mode 100644 index 000000000..66a308383 --- /dev/null +++ b/src/common/p25/dfsi/frames/MotTDULCFrame.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file MotTDULCFrame.h + * @ingroup dfsi_frames + * @file MotTDULCFrame.cpp + * @ingroup dfsi_frames + */ +#if !defined(__MOT_TDULC_FRAME_H__) +#define __MOT_TDULC_FRAME_H__ + +#include "Defines.h" +#include "common/Defines.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "common/p25/dfsi/frames/FrameDefines.h" +#include "common/p25/dfsi/frames/MotStartOfStream.h" +#include "common/p25/dfsi/frames/MotFullRateVoice.h" + +namespace p25 +{ + namespace dfsi + { + namespace frames + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a P25 Motorola/V.24 TDULC frame. + * \code{.unparsed} + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | FT | Encoded V.24 Start of Stream | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | TDULC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode + * @ingroup dfsi_frames + */ + class HOST_SW_API MotTDULCFrame { + public: + /** + * @brief Initializes a copy instance of the MotTDULCFrame class. + */ + MotTDULCFrame(); + /** + * @brief Initializes a copy instance of the MotTDULCFrame class. + * @param data Buffer to containing MotTDULCFrame to decode. + */ + MotTDULCFrame(uint8_t* data); + /** + * @brief Finalizes a instance of the MotTDULCFrame class. + */ + ~MotTDULCFrame(); + + /** + * @brief Decode a TDULC frame. + * @param[in] data Buffer to containing MotTDULCFrame to decode. + */ + bool decode(const uint8_t* data); + /** + * @brief Encode a TDULC frame. + * @param[out] data Buffer to encode a MotTDULCFrame. + */ + void encode(uint8_t* data); + + public: + MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters + uint8_t* tdulcData; // ?? - this should probably be private with getters/setters + }; + } // namespace frames + } // namespace dfsi +} // namespace p25 + +#endif // __MOT_TDULC_FRAME_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.h b/src/common/p25/dfsi/frames/MotTSBKFrame.h index c7aab058b..2ffee559c 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.h +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.h @@ -35,12 +35,12 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola TSBK frame. + * @brief Implements a P25 Motorola/V.24 TSBK frame. * \code{.unparsed} * Byte 0 1 2 3 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | FT | Encoded Motorola Start of Stream | + * | FT | Encoded V.24 Start of Stream | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ diff --git a/src/common/p25/lc/TDULC.cpp b/src/common/p25/lc/TDULC.cpp index 9fec40f42..5883fbc8b 100644 --- a/src/common/p25/lc/TDULC.cpp +++ b/src/common/p25/lc/TDULC.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -79,7 +79,8 @@ TDULC::TDULC() : m_siteIdenEntry(lookups::IdenTable()), m_rs(), m_implicit(false), - m_callTimer(0U) + m_callTimer(0U), + m_raw(nullptr) { m_grpVchNo = m_siteData.channelNo(); } @@ -88,7 +89,15 @@ TDULC::TDULC() : TDULC::~TDULC() { - /* stub */ + if (m_raw != nullptr) + delete[] m_raw; +} + +/* Returns a copy of the raw decoded TDULC bytes. */ + +uint8_t* TDULC::getDecodedRaw() const +{ + return m_raw; } // --------------------------------------------------------------------------- @@ -138,84 +147,99 @@ UInt8Array TDULC::fromValue(const ulong64_t value) /* Internal helper to decode a terminator data unit w/ link control. */ -bool TDULC::decode(const uint8_t* data, uint8_t* payload) +bool TDULC::decode(const uint8_t* data, uint8_t* payload, bool rawTDULC) { assert(data != nullptr); assert(payload != nullptr); - uint8_t rs[P25_TDULC_LENGTH_BYTES]; - ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + if (rawTDULC) { + ::memcpy(payload, data, P25_TDULC_PAYLOAD_LENGTH_BYTES); + return true; + } else { + uint8_t rs[P25_TDULC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); - // deinterleave - uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; - P25Utils::decode(data, raw, 114U, 410U); + // deinterleave + uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; + P25Utils::decode(data, raw, 114U, 410U); - // decode Golay (24,12,8) FEC - edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); + // decode Golay (24,12,8) FEC + edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "TDULC::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif - // decode RS (24,12,13) FEC - try { - bool ret = m_rs.decode241213(rs); - if (!ret) { - LogError(LOG_P25, "TDULC::decode(), failed to decode RS (24,12,13) FEC"); + // decode RS (24,12,13) FEC + try { + bool ret = m_rs.decode241213(rs); + if (!ret) { + LogError(LOG_P25, "TDULC::decode(), failed to decode RS (24,12,13) FEC"); + return false; + } + } + catch (...) { + Utils::dump(2U, "P25, RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); return false; } - } - catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); - return false; - } - if (m_verbose) { - Utils::dump(2U, "TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); - } + if (m_verbose) { + Utils::dump(2U, "TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); + } - ::memcpy(payload, rs + 1U, P25_TDULC_PAYLOAD_LENGTH_BYTES); - return true; + if (m_raw != nullptr) + delete[] m_raw; + m_raw = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; + ::memcpy(m_raw, rs + 1U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + + ::memcpy(payload, rs + 1U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + return true; + } } /* Internal helper to encode a terminator data unit w/ link control. */ -void TDULC::encode(uint8_t* data, const uint8_t* payload) +void TDULC::encode(uint8_t* data, const uint8_t* payload, bool rawTDULC) { assert(data != nullptr); assert(payload != nullptr); - uint8_t rs[P25_TDULC_LENGTH_BYTES]; - ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); - ::memcpy(rs + 1U, payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); + if (rawTDULC) { + ::memcpy(data, payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); + return; + } else { + uint8_t rs[P25_TDULC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + ::memcpy(rs + 1U, payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); - rs[0U] = m_lco; // LCO - if (m_implicit) - rs[0U] |= 0x40U; // Implicit Operation + rs[0U] = m_lco; // LCO + if (m_implicit) + rs[0U] |= 0x40U; // Implicit Operation - if (m_verbose) { - Utils::dump(2U, "TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); - } + if (m_verbose) { + Utils::dump(2U, "TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); + } - // encode RS (24,12,13) FEC - m_rs.encode241213(rs); + // encode RS (24,12,13) FEC + m_rs.encode241213(rs); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::encode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "TDULC::encode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif - uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; - ::memset(raw, 0x00U, P25_TDULC_FEC_LENGTH_BYTES + 1U); + uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; + ::memset(raw, 0x00U, P25_TDULC_FEC_LENGTH_BYTES + 1U); - // encode Golay (24,12,8) FEC - edac::Golay24128::encode24128(raw, rs, P25_TDULC_LENGTH_BYTES); + // encode Golay (24,12,8) FEC + edac::Golay24128::encode24128(raw, rs, P25_TDULC_LENGTH_BYTES); - // interleave - P25Utils::encode(raw, data, 114U, 410U); + // interleave + P25Utils::encode(raw, data, 114U, 410U); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::encode(), TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "TDULC::encode(), TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif + } } /* Internal helper to copy the the class. */ diff --git a/src/common/p25/lc/TDULC.h b/src/common/p25/lc/TDULC.h index 163afa89d..7ef6128c3 100644 --- a/src/common/p25/lc/TDULC.h +++ b/src/common/p25/lc/TDULC.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2017-2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -79,6 +79,13 @@ namespace p25 */ virtual void encode(uint8_t* data) = 0; + /** + * @brief Returns a copy of the raw decoded TDULC bytes. + * This will only return data for a *decoded* TDULC, not a created or copied TDULC. + * @returns uint8_t* Raw decoded TDULC bytes. + */ + uint8_t* getDecodedRaw() const; + /** * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. @@ -184,16 +191,21 @@ namespace p25 * @brief Internal helper to decode terminator data unit w/ link control. * @param[in] data Raw data. * @param[out] payload TDULC payload buffer. + * @param rawTDULC Flag indicating whether or not the passed buffer is raw. */ - bool decode(const uint8_t* data, uint8_t* payload); + bool decode(const uint8_t* data, uint8_t* payload, bool rawTDULC = false); /** * @brief Internal helper to encode terminator data unit w/ link control. * @param[out] data Raw data. * @param[in] payload TDULC payload buffer. + * @param rawTDULC Flag indicating whether or not the passed buffer is raw. */ - void encode(uint8_t* data, const uint8_t* payload); + void encode(uint8_t* data, const uint8_t* payload, bool rawTDULC = false); DECLARE_PROTECTED_COPY(TDULC); + + private: + uint8_t* m_raw; }; } // namespace lc } // namespace p25 diff --git a/src/common/p25/lc/TSBK.cpp b/src/common/p25/lc/TSBK.cpp index 0e5c0aa14..47e300d76 100644 --- a/src/common/p25/lc/TSBK.cpp +++ b/src/common/p25/lc/TSBK.cpp @@ -196,8 +196,7 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) uint8_t tsbk[P25_TSBK_LENGTH_BYTES]; ::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES); - if (rawTSBK) - { + if (rawTSBK) { ::memcpy(tsbk, data, P25_TSBK_LENGTH_BYTES); bool ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); @@ -252,6 +251,8 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) Utils::dump(2U, "TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } + if (m_raw != nullptr) + delete[] m_raw; m_raw = new uint8_t[P25_TSBK_LENGTH_BYTES]; ::memcpy(m_raw, tsbk, P25_TSBK_LENGTH_BYTES); diff --git a/src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp b/src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp new file mode 100644 index 000000000..e27fae283 --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/lc/tdulc/LC_TDULC_RAW.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::lc; +using namespace p25::lc::tdulc; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the LC_TDULC_RAW class. */ + +LC_TDULC_RAW::LC_TDULC_RAW() : TDULC(), + m_tdulc(nullptr) +{ + m_lco = LCO::GROUP; +} + +/* Finalizes a new instance of the LC_TDULC_RAW class. */ + +LC_TDULC_RAW::~LC_TDULC_RAW() +{ + if (m_tdulc != nullptr) { + delete m_tdulc; + } +} + +/* Decode a terminator data unit w/ link control. */ + +bool LC_TDULC_RAW::decode(const uint8_t* data) +{ + assert(data != nullptr); + + return decode(data, false); +} + +/* Encode a terminator data unit w/ link control. */ + +void LC_TDULC_RAW::encode(uint8_t* data) +{ + assert(data != nullptr); + assert(m_tdulc != nullptr); + + encode(data, false); +} + +/* Decode a terminator data unit w/ link control. */ + +bool LC_TDULC_RAW::decode(const uint8_t* data, bool rawTDULC) +{ + assert(data != nullptr); + + if (m_tdulc != nullptr) + delete[] m_tdulc; + + m_tdulc = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES + 1U]; + ::memset(m_tdulc, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + + bool ret = TDULC::decode(data, m_tdulc, rawTDULC); + if (!ret) + return false; + + return true; +} + +/* Encode a terminator data unit w/ link control. */ + +void LC_TDULC_RAW::encode(uint8_t* data, bool rawTDULC) +{ + assert(data != nullptr); + assert(m_tdulc != nullptr); + + /* stub */ + + TDULC::encode(data, m_tdulc, rawTDULC); +} + +/* Sets the TDULC to encode. */ + +void LC_TDULC_RAW::setTDULC(const uint8_t* tdulc) +{ + assert(tdulc != nullptr); + + m_tdulc = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; + ::memset(m_tdulc, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + + ::memcpy(m_tdulc, tdulc, P25_TDULC_PAYLOAD_LENGTH_BYTES); +} diff --git a/src/common/p25/lc/tdulc/LC_TDULC_RAW.h b/src/common/p25/lc/tdulc/LC_TDULC_RAW.h new file mode 100644 index 000000000..e7cf5ce2d --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_TDULC_RAW.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file LC_TDULC_RAW.h + * @ingroup p25_lc + * @file LC_TDULC_RAW.cpp + * @ingroup p25_lc + */ +#if !defined(__P25_LC_TDULC__RAW_H__) +#define __P25_LC_TDULC__RAW_H__ + +#include "common/Defines.h" +#include "common/p25/lc/TDULC.h" + +namespace p25 +{ + namespace lc + { + namespace tdulc + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a mechanism to generate raw TDULC data from bytes. + * @ingroup p25_lc + */ + class HOST_SW_API LC_TDULC_RAW : public TDULC { + public: + /** + * @brief Initializes a new instance of the LC_TDULC_RAW class. + */ + LC_TDULC_RAW(); + /** + * @brief Finalizes a instance of the LC_TDULC_RAW class. + */ + ~LC_TDULC_RAW(); + + /** + * @brief Decode a terminator data unit w/ link control. + * @param[in] data Buffer containing a TDULC to decode. + * @returns bool True, if TDULC decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a terminator data unit w/ link control. + * @param[out] data Buffer to encode a TDULC. + */ + void encode(uint8_t* data) override; + + /** + * @brief Decode a terminator data unit w/ link control. + * @param[in] data Buffer containing a TDULC to decode. + * @param rawTDULC Flag indicating whether or not the passed buffer is raw. + * @returns bool True, if TDULC decoded, otherwise false. + */ + bool decode(const uint8_t* data, bool rawTDULC); + /** + * @brief Encode a terminator data unit w/ link control. + * @param rawTDULC Flag indicating whether or not the output buffer is raw. + * @param[out] data Buffer to encode a TDULC. + */ + void encode(uint8_t* data, bool rawTDULC); + + /** + * @brief Sets the TDULC to encode. + * @param[in] tdulc Buffer containing TDULC to encode. + */ + void setTDULC(const uint8_t* tdulc); + + private: + uint8_t* m_tdulc; + }; + } // namespace tdulc + } // namespace lc +} // namespace p25 + +#endif // __P25_LC_TDULC__RAW_H__ diff --git a/src/common/p25/lc/tdulc/TDULCFactory.h b/src/common/p25/lc/tdulc/TDULCFactory.h index d8704c9da..b3789c4a8 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.h +++ b/src/common/p25/lc/tdulc/TDULCFactory.h @@ -31,6 +31,7 @@ #include "common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h" #include "common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h" #include "common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h" +#include "common/p25/lc/tdulc/LC_TDULC_RAW.h" #include "common/p25/lc/tdulc/LC_FAILSOFT.h" diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index df6224aa8..4fcaf9193 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -16,6 +16,7 @@ #include "common/p25/dfsi/LC.h" #include "common/p25/dfsi/frames/Frames.h" #include "common/p25/lc/LC.h" +#include "common/p25/lc/tdulc/TDULCFactory.h" #include "common/p25/lc/tsbk/TSBKFactory.h" #include "common/p25/NID.h" #include "common/p25/P25Utils.h" @@ -197,7 +198,7 @@ void ModemV24::clock(uint32_t ms) if (m_useTIAFormat) convertToAirTIA(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); else - convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + convertToAirV24(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); if (m_trace) Utils::dump(1U, "ModemV24::clock() RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } @@ -447,7 +448,7 @@ int ModemV24::write(const uint8_t* data, uint32_t length) if (m_useTIAFormat) convertFromAirTIA(buffer, length); else - convertFromAir(buffer, length); + convertFromAirV24(buffer, length); return length; } else { return Modem::write(data, length); @@ -575,7 +576,7 @@ void ModemV24::create_TDU(uint8_t* buffer) /* Internal helper to convert from V.24/DFSI to TIA-102 air interface. */ -void ModemV24::convertToAir(const uint8_t *data, uint32_t length) +void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) { assert(data != nullptr); assert(length > 0U); @@ -602,14 +603,14 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->resetCallData(); m_rxCallInProgress = true; if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } } else { if (m_rxCallInProgress) { m_rxCall->resetCallData(); m_rxCallInProgress = false; if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } // generate a TDU create_TDU(buffer); @@ -674,7 +675,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCallInProgress = true; m_rxCall->resetCallData(); if (m_debug) - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX VHDR late entry, resetting call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX VHDR late entry, resetting call data"); } uint32_t dstId = GET_UINT16(vhdr, 13U); @@ -752,14 +753,14 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) // don't do anything with this RSSI break; default: - LogWarning(LOG_MODEM, "ModemV24::convertToAir() unknown ICW parameter $%02X with value %u", param, value); + LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); break; } } } if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU1, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, Start of Voice LDU1, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } if (svf.fullRateVoice->getTotalErrors() > 0U) { @@ -796,14 +797,14 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) // don't do anything with this RSSI break; default: - LogWarning(LOG_MODEM, "ModemV24::convertToAir() unknown ICW parameter $%02X with value %u", param, value); + LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); break; } } } if (m_debug) { - ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "V.24 RX, Start of Voice LDU2, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, Start of Voice LDU2, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } if (svf.fullRateVoice->getTotalErrors() > 0U) { @@ -814,6 +815,41 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } break; + case DFSIFrameType::MOT_TDULC: + { + MotTDULCFrame tf = MotTDULCFrame(dfsiData); + lc::tdulc::LC_TDULC_RAW tdulc = lc::tdulc::LC_TDULC_RAW(); + if (!tdulc.decode(tf.tdulcData, true)) { + LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode TDULC FEC"); + } else { + uint8_t buffer[P25_TDULC_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // generate NID + m_nid->encode(buffer + 2U, DUID::TDULC); + + // regenerate TDULC Data + tdulc.encode(buffer + 2U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES, false, true); + P25Utils::addIdleStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES); + P25Utils::setStatusBitsStartIdle(buffer + 2U); + + storeConvertedRx(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U); + } + } + break; + + case DFSIFrameType::MOT_PDU_SINGLE: + break; + case DFSIFrameType::MOT_TSBK: { MotTSBKFrame tf = MotTSBKFrame(dfsiData); @@ -853,7 +889,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) MotFullRateVoice voice = MotFullRateVoice(dfsiData); if (m_debug) { - LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "Full Rate Voice, frameType = $%02X, errors = %u, busy = %u", voice.getFrameType(), voice.getTotalErrors(), voice.getBusy()); + LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "Full Rate Voice, frameType = $%02X, errors = %u, busy = %u", voice.getFrameType(), voice.getTotalErrors(), voice.getBusy()); Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } @@ -1908,7 +1944,7 @@ void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType /* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ -void ModemV24::startOfStream(const p25::lc::LC& control) +void ModemV24::startOfStreamV24(const p25::lc::LC& control) { m_txCallInProgress = true; @@ -1923,7 +1959,7 @@ void ModemV24::startOfStream(const p25::lc::LC& control) start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::startOfStreamV24() StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); @@ -1964,7 +2000,7 @@ void ModemV24::startOfStream(const p25::lc::LC& control) vhdr1Buf[20U + DFSI_MOT_START_LEN] = DFSI_BUSY_BITS_INBOUND; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); + Utils::dump(1U, "ModemV24::startOfStreamV24() VoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); queueP25Frame(vhdr1Buf, DFSI_MOT_VHDR_1_LEN, STT_NON_IMBE); @@ -1981,14 +2017,14 @@ void ModemV24::startOfStream(const p25::lc::LC& control) // send VHDR2 if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); + Utils::dump(1U, "ModemV24::startOfStreamV24() VoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); queueP25Frame(vhdr2Buf, DFSI_MOT_VHDR_2_LEN, STT_NON_IMBE); } /* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ -void ModemV24::endOfStream() +void ModemV24::endOfStreamV24() { MotStartOfStream end = MotStartOfStream(); end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); @@ -2001,7 +2037,7 @@ void ModemV24::endOfStream() end.encode(endBuf); if (m_trace) - Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::endOfStreamV24() StartOfStream", endBuf, DFSI_MOT_START_LEN); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); @@ -2204,13 +2240,13 @@ void ModemV24::ackStartOfStreamTIA() /* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ -void ModemV24::convertFromAir(uint8_t* data, uint32_t length) +void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) { assert(data != nullptr); assert(length > 0U); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() data", data, length); + Utils::dump(1U, "ModemV24::convertFromAirV24() data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); @@ -2233,7 +2269,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); } - startOfStream(lc); + startOfStreamV24(lc); } break; case DUID::LDU1: @@ -2248,9 +2284,9 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) // late entry? if (!m_txCallInProgress) { - startOfStream(lc); + startOfStreamV24(lc); if (m_debug) - ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAir()", "V.24 TX VHDR late entry, resetting TX call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "V.24 TX VHDR late entry, resetting TX call data"); } // generate audio @@ -2289,11 +2325,71 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) break; case DUID::TDU: - case DUID::TDULC: if (m_txCallInProgress) - endOfStream(); + endOfStreamV24(); break; + case DUID::TDULC: + { + if (m_txCallInProgress) + endOfStreamV24(); + + lc::tdulc::LC_TDULC_RAW tdulc = lc::tdulc::LC_TDULC_RAW(); + if (!tdulc.decode(data + 2U)) { + LogWarning(LOG_MODEM, P25_TDULC_STR ", undecodable LC"); + return; + } + + MotStartOfStream start = MotStartOfStream(); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::TERM_LC); + + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24() TDULC MotStartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + + MotTDULCFrame tf = MotTDULCFrame(); + tf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + tf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + tf.startOfStream->setArgument1(MotStreamPayload::TERM_LC); + delete[] tf.tdulcData; + + tf.tdulcData = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; + ::memset(tf.tdulcData, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + ::memcpy(tf.tdulcData, tdulc.getDecodedRaw(), P25_TDULC_PAYLOAD_LENGTH_BYTES); + + // create buffer and encode + uint8_t tdulcBuf[DFSI_MOT_TDULC_LEN]; + ::memset(tdulcBuf, 0x00U, DFSI_MOT_TDULC_LEN); + tf.encode(tdulcBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24() MotTDULCFrame", tdulcBuf, DFSI_MOT_TDULC_LEN); + + queueP25Frame(tdulcBuf, DFSI_MOT_TDULC_LEN, STT_NON_IMBE_NO_JITTER); + + MotStartOfStream end = MotStartOfStream(); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::TERM_LC); + + // create buffer and encode + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); + end.encode(endBuf); + + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + } + break; + case DUID::PDU: break; @@ -2308,7 +2404,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) MotStartOfStream start = MotStartOfStream(); start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); - start.setArgument1(MotStreamPayload::VOICE); + start.setArgument1(MotStreamPayload::TSBK); // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; @@ -2316,7 +2412,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() TSBK MotStartOfStream", startBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::convertFromAirV24() TSBK StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); @@ -2336,7 +2432,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) tf.encode(tsbkBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); + Utils::dump(1U, "ModemV24::convertFromAirV24() MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); queueP25Frame(tsbkBuf, DFSI_MOT_TSBK_LEN, STT_NON_IMBE_NO_JITTER); @@ -2364,7 +2460,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); if (duid == DUID::LDU1) { - rs[0U] = lc.getLCO(); // LCO + rs[0U] = lc.getLCO(); // LCO // split ulong64_t (8 byte) value into bytes rs[1U] = (uint8_t)((lc.getRS() >> 56) & 0xFFU); @@ -2529,7 +2625,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAir() Encoded V.24 Voice Frame Data", buffer, bufferSize); + Utils::dump("ModemV24::convertFromAirV24() Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 0a9be5cbd..9a178bbb2 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -235,6 +235,207 @@ namespace modem /** * @brief Implements the core interface to the V.24 modem hardware. + * \code{.unparsed} + * This is the format of the Motorola V.24 DFSI Voice Headers: + * + * Voice Header 1 (VHDR1): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * | Start of Strm | 1 - 9 + * +-+-+-+-+-+-+-+-+ + * |S0 | G0 | 10 + * +-+-+-+-+-+-+-+-+ + * |S1 | G1 | 11 + * +-+-+-+-+-+-+-+-+ + * |S2 | G2 | 12 + * +-+-+-+-+-+-+-+-+ + * |S3 | G3 | 13 + * +-+-+-+-+-+-+-+-+ + * |S4 | G4 | 14 + * +-+-+-+-+-+-+-+-+ + * |S5 | G5 | 15 + * +-+-+-+-+-+-+-+-+ + * |S6 | G6 | 16 + * +-+-+-+-+-+-+-+-+ + * |S7 | G7 | 17 + * +-+-+-+-+-+-+-+-+ + * | S7...S0 | 18 + * +-+-+-+-+-+-+-+-+ + * |S8 | G8 | 19 + * +-+-+-+-+-+-+-+-+ + * |S9 | G9 | 20 + * +-+-+-+-+-+-+-+-+ + * |S10| G10 | 21 + * +-+-+-+-+-+-+-+-+ + * |S11| G11 | 22 + * +-+-+-+-+-+-+-+-+ + * |S12| G12 | 23 + * +-+-+-+-+-+-+-+-+ + * |S13| G13 | 24 + * +-+-+-+-+-+-+-+-+ + * |S14| G14 | 25 + * +-+-+-+-+-+-+-+-+ + * |S15| G15 | 26 + * +-+-+-+-+-+-+-+-+ + * | S15...S8 | 27 + * +-+-+-+-+-+-+-+-+ + * |S16| G16 | 26 + * +-+-+-+-+-+-+-+-+ + * |S17| G17 | 28 + * +-+-+-+-+-+-+-+-+ + * |7|6| Rsvd |Bsy| 29 + * +-+-+-+-+-+-+-+-+ + * | Rsvd | 30 + * +-+-+-+-+-+-+-+-+ + * + * Voice Header 2 (VHDR2): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * |S18| G18 | 1 + * +-+-+-+-+-+-+-+-+ + * |S19| G19 | 2 + * +-+-+-+-+-+-+-+-+ + * |S20| G20 | 3 + * +-+-+-+-+-+-+-+-+ + * |S21| G21 | 4 + * +-+-+-+-+-+-+-+-+ + * |S22| G22 | 5 + * +-+-+-+-+-+-+-+-+ + * |S23| G23 | 6 + * +-+-+-+-+-+-+-+-+ + * |S24| G24 | 7 + * +-+-+-+-+-+-+-+-+ + * |S25| G25 | 8 + * +-+-+-+-+-+-+-+-+ + * | S25...S18 | 9 + * +-+-+-+-+-+-+-+-+ + * |S26| G26 | 10 + * +-+-+-+-+-+-+-+-+ + * |S27| G27 | 11 + * +-+-+-+-+-+-+-+-+ + * |S28| G28 | 12 + * +-+-+-+-+-+-+-+-+ + * |S29| G29 | 13 + * +-+-+-+-+-+-+-+-+ + * |S30| G30 | 14 + * +-+-+-+-+-+-+-+-+ + * |S31| G31 | 15 + * +-+-+-+-+-+-+-+-+ + * |S32| G32 | 16 + * +-+-+-+-+-+-+-+-+ + * |S33| G33 | 17 + * +-+-+-+-+-+-+-+-+ + * | S33...S26 | 18 + * +-+-+-+-+-+-+-+-+ + * |S34| G34 | 19 + * +-+-+-+-+-+-+-+-+ + * |S35| G35 | 20 + * +-+-+-+-+-+-+-+-+ + * |5|4| Rsvd |Bsy| 21 + * +-+-+-+-+-+-+-+-+ + * + * This is the format of the TIA-102.BAHA DFSI Voice Headers: + * + * TIA-102.BAHA Voice Header 1 (VHDR1): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * |S0 | G0 | 1 + * +-+-+-+-+-+-+-+-+ + * |S1 | G1 | 2 + * +-+-+-+-+-+-+-+-+ + * |S2 | G2 | 3 + * +-+-+-+-+-+-+-+-+ + * |S3 | G3 | 4 + * +-+-+-+-+-+-+-+-+ + * |S4 | G4 | 5 + * +-+-+-+-+-+-+-+-+ + * |S5 | G5 | 6 + * +-+-+-+-+-+-+-+-+ + * |S6 | G6 | 7 + * +-+-+-+-+-+-+-+-+ + * |S7 | G7 | 8 + * +-+-+-+-+-+-+-+-+ + * |S8 | G8 | 9 + * +-+-+-+-+-+-+-+-+ + * |S9 | G9 | 10 + * +-+-+-+-+-+-+-+-+ + * |S10| G10 | 11 + * +-+-+-+-+-+-+-+-+ + * |S11| G11 | 12 + * +-+-+-+-+-+-+-+-+ + * |S12| G12 | 13 + * +-+-+-+-+-+-+-+-+ + * |S13| G13 | 14 + * +-+-+-+-+-+-+-+-+ + * |S14| G14 | 15 + * +-+-+-+-+-+-+-+-+ + * |S15| G15 | 16 + * +-+-+-+-+-+-+-+-+ + * |S16| G16 | 17 + * +-+-+-+-+-+-+-+-+ + * |S17| G17 | 18 + * +-+-+-+-+-+-+-+-+ + * | Rsvd |7|6| 19 + * +-+-+-+-+-+-+-+-+ + * | S15...S0 | 20 + * +-+-+-+-+-+-+-+-+ + * | S15...S0 | 21 + * +-+-+-+-+-+-+-+-+ + * + * TIA-102.BAHA Voice Header 2 (VHDR2): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * |S18| G18 | 1 + * +-+-+-+-+-+-+-+-+ + * |S19| G19 | 2 + * +-+-+-+-+-+-+-+-+ + * |S20| G20 | 3 + * +-+-+-+-+-+-+-+-+ + * |S21| G21 | 4 + * +-+-+-+-+-+-+-+-+ + * |S22| G22 | 5 + * +-+-+-+-+-+-+-+-+ + * |S23| G23 | 6 + * +-+-+-+-+-+-+-+-+ + * |S24| G24 | 7 + * +-+-+-+-+-+-+-+-+ + * |S25| G25 | 8 + * +-+-+-+-+-+-+-+-+ + * |S26| G26 | 9 + * +-+-+-+-+-+-+-+-+ + * |S27| G27 | 10 + * +-+-+-+-+-+-+-+-+ + * |S28| G28 | 11 + * +-+-+-+-+-+-+-+-+ + * |S29| G29 | 12 + * +-+-+-+-+-+-+-+-+ + * |S30| G30 | 13 + * +-+-+-+-+-+-+-+-+ + * |S31| G31 | 14 + * +-+-+-+-+-+-+-+-+ + * |S32| G32 | 15 + * +-+-+-+-+-+-+-+-+ + * |S33| G33 | 16 + * +-+-+-+-+-+-+-+-+ + * |S34| G34 | 17 + * +-+-+-+-+-+-+-+-+ + * |S35| G35 | 18 + * +-+-+-+-+-+-+-+-+ + * | Rsvd |5|4| 19 + * +-+-+-+-+-+-+-+-+ + * | S33...S18 | 20 + * +-+-+-+-+-+-+-+-+ + * | S33...S18 | 21 + * +-+-+-+-+-+-+-+-+ + * \endcode * @ingroup modem */ class HOST_SW_API ModemV24 : public Modem { @@ -358,7 +559,7 @@ namespace modem * @param data Buffer containing data to convert. * @param length Length of buffer. */ - void convertToAir(const uint8_t *data, uint32_t length); + void convertToAirV24(const uint8_t *data, uint32_t length); /** * @brief Internal helper to convert from TIA-102 DFSI to TIA-102 air interface. * @param data Buffer containing data to convert. @@ -378,11 +579,11 @@ namespace modem * @brief Send a start of stream sequence (HDU, etc) to the connected serial V24 device. * @param[in] control Instance of p25::lc::LC containing link control data. */ - void startOfStream(const p25::lc::LC& control); + void startOfStreamV24(const p25::lc::LC& control); /** * @brief Send an end of stream sequence (TDU, etc) to the connected serial V24 device. */ - void endOfStream(); + void endOfStreamV24(); /** * @brief Helper to generate the NID value. @@ -410,7 +611,7 @@ namespace modem * @param data Buffer containing data to convert. * @param length Length of buffer. */ - void convertFromAir(uint8_t* data, uint32_t length); + void convertFromAirV24(uint8_t* data, uint32_t length); /** * @brief Internal helper to convert from TIA-102 air interface to TIA-102 DFSI. * @param data Buffer containing data to convert. From 7b3eede41cbc3b656bc2b06a40f94babf395e85a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 5 Aug 2025 10:58:21 -0400 Subject: [PATCH 076/133] experimental support for transporting V.24 over IP; --- src/common/p25/dfsi/DFSIDefines.h | 4 +++ .../modem/port/specialized/V24UDPPort.cpp | 27 ++++++++++++++++++- src/host/modem/port/specialized/V24UDPPort.h | 6 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index 86a26bd77..8d6aa03a1 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -79,6 +79,10 @@ namespace p25 */ const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! + const uint8_t DFSI_RTP_MOT_PAYLOAD_TYPE = 0x5DU; //! + + const uint8_t DFSI_RTP_SEQ_HANDSHAKE = 0x00U; //! + const uint8_t DFSI_RTP_SEQ_STARTSTOP = 0x01U; //! const uint8_t DFSI_MOT_ICW_FMT_TYPE3 = 0x02U; //! diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index e06f04f22..552b7313e 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -83,6 +83,7 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_tx(false), m_ctrlThreadPool(MAX_THREAD_CNT, "v24cc"), m_vcThreadPool(MAX_THREAD_CNT, "v24vc"), + m_tiaMode(true), m_debug(debug) { assert(peerId > 0U); @@ -827,6 +828,9 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui } uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + length; + if (!m_tiaMode) + bufferLen += 8U; + uint8_t* buffer = new uint8_t[bufferLen]; ::memset(buffer, 0x00U, bufferLen); @@ -838,6 +842,11 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui header.setSequence(rtpSeq); header.setSSRC(ssrc); + if (!m_tiaMode) { + header.setExtension(true); + header.setPayloadType(DFSI_RTP_MOT_PAYLOAD_TYPE); + } + header.encode(buffer); if (streamId != 0U && timestamp == INVALID_TS && rtpSeq != RTP_END_OF_CALL_SEQ) { @@ -852,7 +861,23 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui LogDebugEx(LOG_NET, "V24UDPPort::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq); } - ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, message, length); + if (!m_tiaMode) { + uint8_t extBuffer[8U]; + ::memset(extBuffer, 0x00U, 8U); + + extBuffer[0U] = 0x02U; + extBuffer[3U] = 0x01U; + + extBuffer[4U] = 0x7FU; // 127 + extBuffer[5U] = 0x00U; // 0 + extBuffer[6U] = 0x00U; // 0 + extBuffer[7U] = 0x01U; // 1 + + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, extBuffer, 8U); + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + 8U, message, length); + } else { + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, message, length); + } if (m_debug) Utils::dump(1U, "[V24UDPPort::generateMessage()] Buffered Message", buffer, bufferLen); diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 76788fca6..8cbea5212 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -87,6 +87,11 @@ namespace modem * @brief Helper to set and configure the heartbeat interval for FSC connections. */ void setHeartbeatInterval(uint32_t interval); + /** + * @brief Helper to set TIA mode. + * @param mode True to enable TIA mode, false to disable. + */ + void setTIAMode(bool mode) { m_tiaMode = mode; } /** * @brief Updates the timer by the passed number of milliseconds. @@ -185,6 +190,7 @@ namespace modem ThreadPool m_ctrlThreadPool; ThreadPool m_vcThreadPool; + bool m_tiaMode; bool m_debug; static std::mutex m_bufferMutex; From b9b0fa169eebde0803dc0e37a8038ffd7ec0bb45 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 5 Aug 2025 14:51:12 -0400 Subject: [PATCH 077/133] initial super frame should start at 1; make sure to use proper constants instead of magic numbers; --- src/host/modem/ModemV24.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 4fcaf9193..ff9527b87 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -2063,7 +2063,7 @@ uint16_t ModemV24::generateNID(DUID::E duid) void ModemV24::startOfStreamTIA(const p25::lc::LC& control) { m_txCallInProgress = true; - m_superFrameCnt = 0U; + m_superFrameCnt = 1U; p25::lc::LC lc = p25::lc::LC(control); @@ -2186,7 +2186,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) void ModemV24::endOfStreamTIA() { - m_superFrameCnt = 0U; + m_superFrameCnt = 1U; uint16_t length = 0U; uint8_t buffer[2U]; @@ -2495,6 +2495,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) uint8_t* buffer = nullptr; uint16_t bufferSize = 0; MotFullRateVoice voice = MotFullRateVoice(); + voice.setBusy(DFSI_BUSY_BITS_INBOUND); switch (n) { case 0: // VOICE1/10 @@ -2777,6 +2778,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) uint8_t* buffer = nullptr; uint16_t bufferSize = 0; FullRateVoice voice = FullRateVoice(); + voice.setBusy(DFSI_BUSY_BITS_BUSY); switch (n) { case 0: // VOICE1/10 @@ -2904,7 +2906,6 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) bufferSize += BlockHeader::LENGTH; voice.setSuperframeCnt(m_superFrameCnt); - voice.setBusy(1U); // Inbound Channel is Busy voice.encode(buffer + bufferSize); bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type @@ -2927,7 +2928,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) // bryanb: this is a naive way of incrementing the superframe counter, we basically just increment it after // processing and LDU2 if (duid == DUID::LDU2) { - if (m_superFrameCnt == 255U) + if (m_superFrameCnt == 3U) m_superFrameCnt = 0U; else m_superFrameCnt++; From acb0827ef25b891c60e9f8f77c9f171c63f466f4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 5 Aug 2025 14:56:50 -0400 Subject: [PATCH 078/133] reorganize code slightly; --- src/host/modem/ModemV24.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index ff9527b87..b51306f1c 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -2779,6 +2779,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) uint16_t bufferSize = 0; FullRateVoice voice = FullRateVoice(); voice.setBusy(DFSI_BUSY_BITS_BUSY); + voice.setSuperframeCnt(m_superFrameCnt); switch (n) { case 0: // VOICE1/10 @@ -2905,11 +2906,11 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) hdr.encode(buffer + 2U); bufferSize += BlockHeader::LENGTH; - voice.setSuperframeCnt(m_superFrameCnt); + // encode voice frame voice.encode(buffer + bufferSize); bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type - // generate start of stream + // generate start of stream and encode StartOfStream start = StartOfStream(); start.setNID(generateNID(duid)); start.encode(buffer + bufferSize); From a12734b5d37dcb1cf7b19ed0b7be484ea55df0e5 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 5 Aug 2025 21:28:03 -0400 Subject: [PATCH 079/133] cleanup format for, and make slightly more precise trace and error dumping log messaging; --- src/bridge/HostBridge.cpp | 20 ++--- src/common/dmr/lc/CSBK.cpp | 4 +- src/common/network/BaseNetwork.cpp | 22 ++--- src/common/network/FrameQueue.cpp | 4 +- src/common/network/Network.cpp | 26 +++--- src/common/network/PacketBuffer.cpp | 2 +- src/common/network/RawFrameQueue.cpp | 6 +- src/common/network/rest/http/HTTPPayload.cpp | 2 +- .../rest/http/SecureServerConnection.h | 8 +- .../network/rest/http/ServerConnection.h | 8 +- src/common/network/sip/SIPPayload.cpp | 2 +- src/common/network/udp/Socket.cpp | 8 +- src/common/nxdn/channel/CAC.cpp | 14 ++-- src/common/nxdn/channel/FACCH1.cpp | 8 +- src/common/nxdn/channel/SACCH.cpp | 4 +- src/common/nxdn/channel/UDCH.cpp | 8 +- src/common/nxdn/lc/RCCH.cpp | 4 +- src/common/nxdn/lc/RTCH.cpp | 4 +- src/common/p25/data/DataBlock.cpp | 2 +- src/common/p25/data/LowSpeedData.cpp | 2 +- src/common/p25/dfsi/LC.cpp | 8 +- src/common/p25/lc/AMBT.cpp | 2 +- src/common/p25/lc/LC.cpp | 42 +++++----- src/common/p25/lc/TDULC.cpp | 12 +-- src/common/p25/lc/TSBK.cpp | 8 +- src/common/p25/lc/tdulc/TDULCFactory.cpp | 8 +- src/common/p25/lc/tsbk/TSBKFactory.cpp | 2 +- src/fne/network/DiagNetwork.cpp | 6 +- src/fne/network/FNENetwork.cpp | 14 ++-- src/fne/network/PeerNetwork.cpp | 6 +- src/fne/network/RESTAPI.cpp | 4 +- .../callhandler/packetdata/DMRPacketData.cpp | 4 +- .../callhandler/packetdata/P25PacketData.cpp | 20 ++--- src/host/Host.cpp | 6 +- src/host/dmr/Slot.cpp | 8 +- src/host/dmr/packet/Data.cpp | 4 +- src/host/modem/Modem.cpp | 50 ++++++------ src/host/modem/ModemV24.cpp | 80 ++++++++++--------- .../modem/port/specialized/V24UDPPort.cpp | 4 +- src/host/network/RESTAPI.cpp | 6 +- src/host/p25/Control.cpp | 2 +- src/host/p25/packet/Data.cpp | 24 +++--- src/host/p25/packet/Voice.cpp | 4 +- src/host/setup/HostSetup.cpp | 10 +-- src/patch/HostPatch.cpp | 18 ++--- src/patch/mmdvm/P25Network.cpp | 44 +++++----- src/patch/network/PeerNetwork.cpp | 6 +- src/sysview/network/PeerNetwork.cpp | 4 +- 48 files changed, 285 insertions(+), 279 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index a62566756..9e94e828e 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -1210,7 +1210,7 @@ void HostBridge::processUDPAudio() if (length > 0) { if (m_trace) - Utils::dump(1U, "HostBridge()::processUDPAudio() Audio Network Packet", buffer, length); + Utils::dump(1U, "HostBridge()::processUDPAudio(), Audio Network Packet", buffer, length); uint32_t pcmLength = 0; if (m_udpNoIncludeLength) { @@ -1255,7 +1255,7 @@ void HostBridge::processUDPAudio() delete[] usrpHeader; } - // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); + // Utils::dump(1U, "HostBridge::processUDPAudio(), PCM RECV BYTE BUFFER", pcm, pcmLength); NetPacketRequest* req = new NetPacketRequest(); req->pcm = new uint8_t[pcmLength]; @@ -1552,7 +1552,7 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst } if (m_trace) - Utils::dump(1U, "HostBridge()::decodeDMRAudioFrame() Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::decodeDMRAudioFrame(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { @@ -1784,7 +1784,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ } #endif // defined(_WIN32) - // Utils::dump(1U, "Encoded AMBE", ambe, RAW_AMBE_LENGTH_BYTES); + // Utils::dump(1U, "HostBridge::encodeDMRAudioFrame(), Encoded AMBE", ambe, RAW_AMBE_LENGTH_BYTES); ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), ambe, RAW_AMBE_LENGTH_BYTES); m_ambeCount++; @@ -2157,7 +2157,7 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI break; } - // Utils::dump(1U, "IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + // Utils::dump(1U, "HostBridge::decodeP25AudioFrame(), IMBE", imbe, RAW_IMBE_LENGTH_BYTES); if (m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { switch (m_tekAlgoId) { @@ -2205,7 +2205,7 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI } if (m_trace) - Utils::dump(1U, "HostBridge()::decodeP25AudioFrame() Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::decodeP25AudioFrame(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { @@ -2331,7 +2331,7 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ } #endif // defined(_WIN32) - // Utils::dump(1U, "Encoded IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + // Utils::dump(1U, "HostBridge::encodeP25AudioFrame(), Encoded IMBE", imbe, RAW_IMBE_LENGTH_BYTES); if (m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { // generate initial MI for the HDU @@ -2582,7 +2582,7 @@ void HostBridge::processAnalogNetwork(uint8_t* buffer, uint32_t length) } if (m_trace) - Utils::dump(1U, "HostBridge()::processAnalogNetwork() Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::processAnalogNetwork(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { @@ -2729,7 +2729,7 @@ void HostBridge::encodeAnalogAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint } if (m_trace) - Utils::dump(1U, "HostBridge()::encodeAnalogAudioFrame() Encoded uLaw Audio", outPcm, AUDIO_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::encodeAnalogAudioFrame(), Encoded uLaw Audio", outPcm, AUDIO_SAMPLES_LENGTH); analogData.setAudio(outPcm); @@ -3196,7 +3196,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) short samples[AUDIO_SAMPLES_LENGTH]; if (bridge->m_udpUseULaw) { if (bridge->m_trace) - Utils::dump(1U, "HostBridge()::threadUDPAudioProcess() uLaw Audio", req->pcm, AUDIO_SAMPLES_LENGTH * 2U); + Utils::dump(1U, "HostBridge()::threadUDPAudioProcess(), uLaw Audio", req->pcm, AUDIO_SAMPLES_LENGTH * 2U); for (uint32_t pcmIdx = 0; pcmIdx < AUDIO_SAMPLES_LENGTH; pcmIdx++) { samples[smpIdx] = AnalogAudio::decodeMuLaw(req->pcm[pcmIdx]); diff --git a/src/common/dmr/lc/CSBK.cpp b/src/common/dmr/lc/CSBK.cpp index ca54cf2ad..9c8a655dd 100644 --- a/src/common/dmr/lc/CSBK.cpp +++ b/src/common/dmr/lc/CSBK.cpp @@ -272,7 +272,7 @@ bool CSBK::decode(const uint8_t* data, uint8_t* payload) } if (m_verbose) { - Utils::dump(2U, "Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); + Utils::dump(2U, "CSBK::decode(), Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } m_raw = new uint8_t[DMR_CSBK_LENGTH_BYTES]; @@ -340,7 +340,7 @@ void CSBK::encode(uint8_t* data, const uint8_t* payload) } if (m_verbose) { - Utils::dump(2U, "Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); + Utils::dump(2U, "CSBK::encode(), Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } // encode BPTC (196,96) FEC diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index e0cf509a7..2e7cebe57 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -143,7 +143,7 @@ bool BaseNetwork::writeKeyReq(const uint16_t kId, const uint8_t algId) modifyKeyCmd.encode(buffer + 11U); - //Utils::dump("writeKeyReq", buffer, modifyKeyCmd.length() + 11U); + //Utils::dump("BaseNetwork::writeKeyReq(), KMM Buffer", buffer, modifyKeyCmd.length() + 11U); return writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, buffer, modifyKeyCmd.length() + 11U, RTP_END_OF_CALL_SEQ, 0U); } @@ -1007,7 +1007,7 @@ UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t strea data.getData(buffer + 20U); if (m_debug) - Utils::dump(1U, "Network Message, DMR", buffer, (DMR_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createDMR_Message(), Message", buffer, (DMR_PACKET_LENGTH + PACKET_PAD)); length = (DMR_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1064,7 +1064,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du control.getMI(mi); if (m_debug) { - Utils::dump(1U, "P25 HDU MI written to network", mi, MI_LENGTH_BYTES); + Utils::dump(1U, "BaseNetwork::createP25_Message(), HDU MI", mi, MI_LENGTH_BYTES); } for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { @@ -1142,7 +1142,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_LDU1Message(), Message, LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1217,7 +1217,7 @@ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::L buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_LDU2Message(), Message, LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1238,7 +1238,7 @@ UInt8Array BaseNetwork::createP25_TDUMessage(uint32_t& length, const p25::lc::LC buffer[23U] = MSG_HDR_SIZE; if (m_debug) - Utils::dump(1U, "Network Message, P25 TDU", buffer, (MSG_HDR_SIZE + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_TDUMessage(), Message, TDU", buffer, (MSG_HDR_SIZE + PACKET_PAD)); length = (MSG_HDR_SIZE + PACKET_PAD); return UInt8Array(buffer); @@ -1264,7 +1264,7 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L buffer[23U] = P25_TSDU_FRAME_LENGTH_BYTES; if (m_debug) - Utils::dump(1U, "Network Message, P25 TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_TSDUMessage(), Message, TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD)); length = (P25_TSDU_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1290,7 +1290,7 @@ UInt8Array BaseNetwork::createP25_TDULCMessage(uint32_t& length, const p25::lc:: buffer[23U] = P25_TDULC_FRAME_LENGTH_BYTES; if (m_debug) - Utils::dump(1U, "Network Message, P25 TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_TDULCMessage(), Message, TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); length = (P25_TDULC_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1338,7 +1338,7 @@ UInt8Array BaseNetwork::createP25_PDUMessage(uint32_t& length, const p25::data:: buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 PDU", buffer, (count + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_PDUMessage(), Message, PDU", buffer, (count + PACKET_PAD)); length = (count + PACKET_PAD); return UInt8Array(buffer); @@ -1377,7 +1377,7 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, NXDN", buffer, (NXDN_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createNXDN_Message(), Message", buffer, (NXDN_PACKET_LENGTH + PACKET_PAD)); length = (NXDN_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1412,7 +1412,7 @@ UInt8Array BaseNetwork::createAnalog_Message(uint32_t& length, const uint32_t st data.getAudio(buffer + 20U); if (m_debug) - Utils::dump(1U, "Network Message, Analog", buffer, (ANALOG_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createAnalog_Message(), Message", buffer, (ANALOG_PACKET_LENGTH + PACKET_PAD)); length = (ANALOG_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 38231dba7..0ca7eeabc 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -70,7 +70,7 @@ UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint3 if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Packet", buffer, length); + Utils::dump(1U, "FrameQueue::read(), Network Packet", buffer, length); m_failedReadCnt = 0U; @@ -355,7 +355,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES, message, length); if (m_debug) - Utils::dump(1U, "FrameQueue::generateMessage() Buffered Message", buffer, bufferLen); + Utils::dump(1U, "FrameQueue::generateMessage(), Buffered Message", buffer, bufferLen); if (outBufferLen != nullptr) { *outBufferLen = bufferLen; diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 8bff9828d..ee510f1e3 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -326,7 +326,7 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer.get(), length); if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD)) LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); @@ -378,7 +378,7 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length); if (length > 512) LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); @@ -439,7 +439,7 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer.get(), length); if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD)) LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); @@ -491,7 +491,7 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, Analog", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer.get(), length); if (length < (int)ANALOG_PACKET_LENGTH) { LogError(LOG_NET, "Analog Stream %u, frame too short? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); } else { @@ -525,7 +525,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, WL RID", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, WL RID", buffer.get(), length); if (m_ridLookup != nullptr) { // update RID lists @@ -551,7 +551,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, BL RID", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, BL RID", buffer.get(), length); if (m_ridLookup != nullptr) { // update RID lists @@ -578,7 +578,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, ACTIVE TGS", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, ACTIVE TGS", buffer.get(), length); if (m_tidLookup != nullptr) { // update TGID lists @@ -628,7 +628,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, DEACTIVE TGS", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, DEACTIVE TGS", buffer.get(), length); if (m_tidLookup != nullptr) { // update TGID lists @@ -902,7 +902,7 @@ void Network::clock(uint32_t ms) m_timeoutTimer.start(); if (length >= 14) { if (m_debug) - Utils::dump(1U, "Network Received, PONG", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, PONG", buffer.get(), length); ulong64_t serverNow = 0U; @@ -1025,7 +1025,7 @@ void Network::enable(bool enabled) void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { - Utils::dump("unknown opcode from the master", data, length); + Utils::dump("Unknown opcode from the master", data, length); } /* Writes login request to the network. */ @@ -1041,7 +1041,7 @@ bool Network::writeLogin() SET_UINT32(m_peerId, buffer, 4U); // Peer ID if (m_debug) - Utils::dump(1U, "Network Message, Login", buffer, 8U); + Utils::dump(1U, "Network::writeLogin(), Message, Login", buffer, 8U); m_loginStreamId = createStreamId(); m_remotePeerId = 0U; @@ -1074,7 +1074,7 @@ bool Network::writeAuthorisation() delete[] in; if (m_debug) - Utils::dump(1U, "Network Message, Authorisation", out, 40U); + Utils::dump(1U, "Network::writeAuthorisation(), Message, Authorisation", out, 40U); return writeMaster({ NET_FUNC::RPTK, NET_SUBFUNC::NOP }, out, 40U, pktSeq(), m_loginStreamId); } @@ -1135,7 +1135,7 @@ bool Network::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "Network::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); diff --git a/src/common/network/PacketBuffer.cpp b/src/common/network/PacketBuffer.cpp index d185fcacc..686a34b6c 100644 --- a/src/common/network/PacketBuffer.cpp +++ b/src/common/network/PacketBuffer.cpp @@ -72,7 +72,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL frag->data = new uint8_t[frag->size + 1U]; ::memcpy(frag->data, data + FRAG_HDR_SIZE, FRAG_BLOCK_SIZE); - // Utils::dump(1U, "Block Payload", frag->data, FRAG_BLOCK_SIZE); + // Utils::dump(1U, "PacketBuffer::decode(), Block Payload", frag->data, FRAG_BLOCK_SIZE); fragments.insert(curBlock, frag); } diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index 844cbc12e..efbd3ad5a 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.cpp @@ -71,7 +71,7 @@ UInt8Array RawFrameQueue::read(int& messageLength, sockaddr_storage& address, ui if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Packet", buffer, length); + Utils::dump(1U, "RawFrameQueue::read(), Network Packet", buffer, length); m_failedReadCnt = 0U; @@ -104,7 +104,7 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor ::memcpy(buffer, message, length); if (m_debug) - Utils::dump(1U, "RawFrameQueue::write() Message", buffer, length); + Utils::dump(1U, "RawFrameQueue::write(), Message", buffer, length); // bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about // this message @@ -153,7 +153,7 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock ::memcpy(buffer, message, length); if (m_debug) - Utils::dump(1U, "RawFrameQueue::enqueueMessage() Buffered Message", buffer, length); + Utils::dump(1U, "RawFrameQueue::enqueueMessage(), Buffered Message", buffer, length); udp::UDPDatagram* dgram = new udp::UDPDatagram; dgram->buffer = buffer; diff --git a/src/common/network/rest/http/HTTPPayload.cpp b/src/common/network/rest/http/HTTPPayload.cpp index ca2fb0f00..088db94b2 100644 --- a/src/common/network/rest/http/HTTPPayload.cpp +++ b/src/common/network/rest/http/HTTPPayload.cpp @@ -311,7 +311,7 @@ std::vector HTTPPayload::toBuffers() #if DEBUG_HTTP_PAYLOAD ::LogDebugEx(LOG_REST, "HTTPPayload::toBuffers()", "content = %s", content.c_str()); for (auto buffer : buffers) - Utils::dump("[HTTPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); + Utils::dump("HTTPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size()); #endif return buffers; diff --git a/src/common/network/rest/http/SecureServerConnection.h b/src/common/network/rest/http/SecureServerConnection.h index 3b98033c8..38c22bc6e 100644 --- a/src/common/network/rest/http/SecureServerConnection.h +++ b/src/common/network/rest/http/SecureServerConnection.h @@ -154,7 +154,7 @@ namespace network ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { if (m_debug) { LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } result = HTTPLexer::INDETERMINATE; @@ -163,7 +163,7 @@ namespace network } else { if (m_debug) { LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } if (m_contResult == HTTPLexer::INDETERMINATE) { @@ -180,7 +180,7 @@ namespace network if (result == HTTPLexer::GOOD) { if (m_debug) { - Utils::dump(1U, "HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); + Utils::dump(1U, "SecureServerConnection::read(), HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); } m_continue = false; @@ -188,7 +188,7 @@ namespace network m_requestHandler.handleRequest(m_request, m_reply); if (m_debug) { - Utils::dump(1U, "HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); + Utils::dump(1U, "SecureServerConnection::read(), HTTPS Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); } write(); diff --git a/src/common/network/rest/http/ServerConnection.h b/src/common/network/rest/http/ServerConnection.h index da5f32ad3..c7b932a32 100644 --- a/src/common/network/rest/http/ServerConnection.h +++ b/src/common/network/rest/http/ServerConnection.h @@ -135,7 +135,7 @@ namespace network ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { if (m_debug) { LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } m_contResult = result = HTTPLexer::INDETERMINATE; @@ -144,7 +144,7 @@ namespace network } else { if (m_debug) { LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } if (m_contResult == HTTPLexer::INDETERMINATE) { @@ -161,7 +161,7 @@ namespace network if (result == HTTPLexer::GOOD) { if (m_debug) { - Utils::dump(1U, "HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); + Utils::dump(1U, "ServerConnection::read(), HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); } m_continue = false; @@ -169,7 +169,7 @@ namespace network m_requestHandler.handleRequest(m_request, m_reply); if (m_debug) { - Utils::dump(1U, "HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); + Utils::dump(1U, "ServerConnection::read(), HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); } write(); diff --git a/src/common/network/sip/SIPPayload.cpp b/src/common/network/sip/SIPPayload.cpp index ffc7b012e..29e20bdcc 100644 --- a/src/common/network/sip/SIPPayload.cpp +++ b/src/common/network/sip/SIPPayload.cpp @@ -139,7 +139,7 @@ std::vector SIPPayload::toBuffers() #if DEBUG_SIP_PAYLOAD ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "content = %s", content.c_str()); for (auto buffer : buffers) - Utils::dump("[SIPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); + Utils::dump("SIPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size()); #endif return buffers; diff --git a/src/common/network/udp/Socket.cpp b/src/common/network/udp/Socket.cpp index 0d55a210a..be64fb8d2 100644 --- a/src/common/network/udp/Socket.cpp +++ b/src/common/network/udp/Socket.cpp @@ -255,12 +255,12 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address ::memcpy(cryptoBuffer, buffer + 2U, len - 2U); } - // Utils::dump(1U, "Socket::read() crypted", cryptoBuffer, cryptedLen); + // Utils::dump(1U, "Socket::read(), crypted", cryptoBuffer, cryptedLen); // decrypt uint8_t* decrypted = m_aes->decryptECB(cryptoBuffer, cryptedLen, m_presharedKey); - // Utils::dump(1U, "Socket::read() decrypted", decrypted, cryptedLen); + // Utils::dump(1U, "Socket::read(), decrypted", decrypted, cryptedLen); // finalize, cleanup buffers and replace with new if (decrypted != nullptr) { @@ -340,7 +340,7 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag // encrypt uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); - // Utils::dump(1U, "Socket::write() crypted", crypted, cryptedLen); + // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen); // finalize, cleanup buffers and replace with new out = std::unique_ptr(new uint8_t[cryptedLen + 2U]); @@ -491,7 +491,7 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept continue; } - // Utils::dump(1U, "Socket::write() crypted", crypted, cryptedLen); + // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen); // finalize DECLARE_UINT8_ARRAY(out, cryptedLen + 2U); diff --git a/src/common/nxdn/channel/CAC.cpp b/src/common/nxdn/channel/CAC.cpp index 87422b740..ab86a3757 100644 --- a/src/common/nxdn/channel/CAC.cpp +++ b/src/common/nxdn/channel/CAC.cpp @@ -170,7 +170,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) } #if DEBUG_NXDN_CAC - Utils::dump(2U, "CAC::decode(), CAC Raw", buffer, NXDN_CAC_IN_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::decode(), CAC Raw", buffer, NXDN_CAC_IN_FEC_LENGTH_BYTES); #endif if (longInbound) { @@ -213,7 +213,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) conv.chainback(m_data, NXDN_CAC_LONG_CRC_LENGTH_BITS); #if DEBUG_NXDN_CAC - Utils::dump(2U, "Decoded Long CAC", m_data, (NXDN_CAC_LONG_CRC_LENGTH_BITS / 8U) + 1U); + Utils::dump(2U, "NXDN, CAC::decode(), Decoded Long CAC", m_data, (NXDN_CAC_LONG_CRC_LENGTH_BITS / 8U) + 1U); #endif // check CRC-16 @@ -271,7 +271,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) conv.chainback(m_data, NXDN_CAC_SHORT_CRC_LENGTH_BITS); #if DEBUG_NXDN_CAC - Utils::dump(2U, "Decoded CAC", m_data, (NXDN_CAC_SHORT_CRC_LENGTH_BITS / 8U) + 1U); + Utils::dump(2U, "NXDN, CAC::decode(), Decoded CAC", m_data, (NXDN_CAC_SHORT_CRC_LENGTH_BITS / 8U) + 1U); #endif // check CRC-16 @@ -298,7 +298,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) } #if DEBUG_NXDN_CAC - Utils::dump(2U, "Raw CAC Buffer", m_data, NXDN_CAC_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::decode(), Raw CAC Buffer", m_data, NXDN_CAC_FEC_LENGTH_BYTES); #endif return true; @@ -324,7 +324,7 @@ void CAC::encode(uint8_t* data) const uint16_t crc = edac::CRC::addCRC16(buffer, NXDN_CAC_LENGTH_BITS); #if DEBUG_NXDN_CAC - Utils::dump(2U, "Encoded CAC", buffer, NXDN_CAC_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::encode(), Encoded CAC", buffer, NXDN_CAC_FEC_LENGTH_BYTES); #endif // encode convolution @@ -357,7 +357,7 @@ void CAC::encode(uint8_t* data) const } #if DEBUG_NXDN_CAC - Utils::dump(2U, "CAC::encode(), CAC Puncture and Interleave", data, NXDN_FRAME_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::encode(), CAC Puncture and Interleave", data, NXDN_FRAME_LENGTH_BYTES); #endif // apply control field @@ -380,7 +380,7 @@ void CAC::encode(uint8_t* data) const } #if DEBUG_NXDN_CAC - Utils::dump(2U, "CAC::encode(), CAC + Control", data, NXDN_FRAME_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::encode(), CAC + Control", data, NXDN_FRAME_LENGTH_BYTES); #endif } diff --git a/src/common/nxdn/channel/FACCH1.cpp b/src/common/nxdn/channel/FACCH1.cpp index 94bb717d9..d590d0505 100644 --- a/src/common/nxdn/channel/FACCH1.cpp +++ b/src/common/nxdn/channel/FACCH1.cpp @@ -99,7 +99,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset) } #if DEBUG_NXDN_FACCH1 - Utils::dump(2U, "FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_FEC_LENGTH_BYTES); #endif // depuncture @@ -137,7 +137,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset) conv.chainback(m_data, NXDN_FACCH1_CRC_LENGTH_BITS); #if DEBUG_NXDN_FACCH1 - Utils::dump(2U, "Decoded FACCH1", m_data, NXDN_FACCH1_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::decode(), Decoded FACCH1", m_data, NXDN_FACCH1_CRC_LENGTH_BYTES); #endif // check CRC-12 @@ -163,7 +163,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const edac::CRC::addCRC12(buffer, NXDN_FACCH1_LENGTH_BITS); #if DEBUG_NXDN_FACCH1 - Utils::dump(2U, "Encoded FACCH1", buffer, NXDN_FACCH1_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::encode(), Encoded FACCH1", buffer, NXDN_FACCH1_CRC_LENGTH_BYTES); #endif // encode convolution @@ -196,7 +196,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const } #if DEBUG_NXDN_SACCH - Utils::dump(2U, "FACCH1::encode(), FACCH1 Puncture and Interleave", data, NXDN_FACCH1_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::encode(), FACCH1 Puncture and Interleave", data, NXDN_FACCH1_FEC_LENGTH_BYTES); #endif } diff --git a/src/common/nxdn/channel/SACCH.cpp b/src/common/nxdn/channel/SACCH.cpp index 91f2221d7..6aeb3a768 100644 --- a/src/common/nxdn/channel/SACCH.cpp +++ b/src/common/nxdn/channel/SACCH.cpp @@ -139,7 +139,7 @@ bool SACCH::decode(const uint8_t* data) conv.chainback(m_data, NXDN_SACCH_CRC_LENGTH_BITS); #if DEBUG_NXDN_SACCH - Utils::dump(2U, "Decoded SACCH", m_data, NXDN_SACCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "SACCH::decode(), Decoded SACCH", m_data, NXDN_SACCH_CRC_LENGTH_BYTES); #endif // check CRC-6 @@ -178,7 +178,7 @@ void SACCH::encode(uint8_t* data) const edac::CRC::addCRC6(buffer, NXDN_SACCH_LENGTH_BITS); #if DEBUG_NXDN_SACCH - Utils::dump(2U, "Encoded SACCH", buffer, NXDN_SACCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "SACCH::encode(), Encoded SACCH", buffer, NXDN_SACCH_CRC_LENGTH_BYTES); #endif // encode convolution diff --git a/src/common/nxdn/channel/UDCH.cpp b/src/common/nxdn/channel/UDCH.cpp index c42fc6a69..92aa821ff 100644 --- a/src/common/nxdn/channel/UDCH.cpp +++ b/src/common/nxdn/channel/UDCH.cpp @@ -125,7 +125,7 @@ bool UDCH::decode(const uint8_t* data) } #if DEBUG_NXDN_UDCH - Utils::dump(2U, "UDCH::decode(), UDCH Raw", buffer, NXDN_UDCH_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::decode(), UDCH Raw", buffer, NXDN_UDCH_FEC_LENGTH_BYTES); #endif // depuncture @@ -163,7 +163,7 @@ bool UDCH::decode(const uint8_t* data) conv.chainback(m_data, NXDN_UDCH_CRC_LENGTH_BITS); #if DEBUG_NXDN_UDCH - Utils::dump(2U, "Decoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::decode(), Decoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); #endif // check CRC-15 @@ -193,7 +193,7 @@ void UDCH::encode(uint8_t* data) const edac::CRC::addCRC15(buffer, NXDN_UDCH_LENGTH_BITS); #if DEBUG_NXDN_UDCH - Utils::dump(2U, "Encoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::encode(), Encoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); #endif // encode convolution @@ -226,7 +226,7 @@ void UDCH::encode(uint8_t* data) const } #if DEBUG_NXDN_UDCH - Utils::dump(2U, "UDCH::encode(), UDCH Puncture and Interleave", data, NXDN_UDCH_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::encode(), UDCH Puncture and Interleave", data, NXDN_UDCH_FEC_LENGTH_BYTES); #endif } diff --git a/src/common/nxdn/lc/RCCH.cpp b/src/common/nxdn/lc/RCCH.cpp index f409f42d6..b5a696711 100644 --- a/src/common/nxdn/lc/RCCH.cpp +++ b/src/common/nxdn/lc/RCCH.cpp @@ -115,7 +115,7 @@ void RCCH::decode(const uint8_t* data, uint8_t* rcch, uint32_t length, uint32_t } if (m_verbose) { - Utils::dump(2U, "Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, RCCH::decode(), Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES); } m_messageType = data[0U] & 0x3FU; // Message Type @@ -138,7 +138,7 @@ void RCCH::encode(uint8_t* data, const uint8_t* rcch, uint32_t length, uint32_t } if (m_verbose) { - Utils::dump(2U, "Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, RCCH::encode(), Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES); } } diff --git a/src/common/nxdn/lc/RTCH.cpp b/src/common/nxdn/lc/RTCH.cpp index 01fbd9547..a6758b0df 100644 --- a/src/common/nxdn/lc/RTCH.cpp +++ b/src/common/nxdn/lc/RTCH.cpp @@ -114,7 +114,7 @@ void RTCH::decode(const uint8_t* data, uint32_t length, uint32_t offset) } if (m_verbose) { - Utils::dump(2U, "Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, RTCH::decode(), Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES); } decodeLC(rtch); @@ -137,7 +137,7 @@ void RTCH::encode(uint8_t* data, uint32_t length, uint32_t offset) } if (m_verbose) { - Utils::dump(2U, "Encoded RTCH Data", data, length); + Utils::dump(2U, "NXDN, RTCH::encode(), Encoded RTCH Data", data, length); } } diff --git a/src/common/p25/data/DataBlock.cpp b/src/common/p25/data/DataBlock.cpp index cf6b44bc5..e750ef32b 100644 --- a/src/common/p25/data/DataBlock.cpp +++ b/src/common/p25/data/DataBlock.cpp @@ -128,7 +128,7 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) ::memcpy(m_data, buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data } catch (...) { - Utils::dump(2U, "P25, decoding excepted with input data", data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + Utils::dump(2U, "P25, DataBlock::decode(), decoding excepted with input data", data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); return false; } } diff --git a/src/common/p25/data/LowSpeedData.cpp b/src/common/p25/data/LowSpeedData.cpp index 83891b499..1cd6c111a 100644 --- a/src/common/p25/data/LowSpeedData.cpp +++ b/src/common/p25/data/LowSpeedData.cpp @@ -106,7 +106,7 @@ void LowSpeedData::process(uint8_t* data) } } - // Utils::dump(1U, "P25 Low Speed Data", lsd, 4U); + // Utils::dump(1U, "P25, LowSpeedData::process(), Low Speed Data", lsd, 4U); m_lsd1 = lsd[0U]; m_lsd2 = lsd[2U]; diff --git a/src/common/p25/dfsi/LC.cpp b/src/common/p25/dfsi/LC.cpp index bfd9cfe28..eaa3ee3a5 100644 --- a/src/common/p25/dfsi/LC.cpp +++ b/src/common/p25/dfsi/LC.cpp @@ -404,8 +404,8 @@ void LC::encodeLDU1(uint8_t* data, const uint8_t* imbe) } #if DEBUG_P25_DFSI - LogDebugEx(LOG_P25, "LC::encodeLDU1()", "frameType = $%02X", m_frameType); - Utils::dump(2U, "[LC::encodeLDU1()] DFSI LDU1 Frame", dfsiFrame, frameLength); + LogDebugEx(LOG_P25, "dfsi::LC::encodeLDU1()", "frameType = $%02X", m_frameType); + Utils::dump(2U, "P25, dfsi::LC::encodeLDU1(), DFSI LDU1 Frame", dfsiFrame, frameLength); #endif ::memcpy(data, dfsiFrame, frameLength); @@ -644,8 +644,8 @@ void LC::encodeLDU2(uint8_t* data, const uint8_t* imbe) } #if DEBUG_P25_DFSI - LogDebugEx(LOG_P25, "LC::encodeLDU2()", "frameType = $%02X", m_frameType); - Utils::dump(2U, "[LC::encodeLDU2()] DFSI LDU2 Frame", dfsiFrame, frameLength); + LogDebugEx(LOG_P25, "dfsi::LC::encodeLDU2()", "frameType = $%02X", m_frameType); + Utils::dump(2U, "P25, dfsi::LC::encodeLDU2(), DFSI LDU2 Frame", dfsiFrame, frameLength); #endif ::memcpy(data, dfsiFrame, frameLength); diff --git a/src/common/p25/lc/AMBT.cpp b/src/common/p25/lc/AMBT.cpp index 36fb0f919..54cd15734 100644 --- a/src/common/p25/lc/AMBT.cpp +++ b/src/common/p25/lc/AMBT.cpp @@ -115,7 +115,7 @@ bool AMBT::decode(const data::DataHeader& dataHeader, const data::DataBlock* blo if (m_verbose) { LogDebugEx(LOG_P25, "AMBT::decode()", "mfId = $%02X, lco = $%02X, ambt8 = $%02X, ambt9 = $%02X", m_mfId, m_lco, dataHeader.getAMBTField8(), dataHeader.getAMBTField9()); - Utils::dump(2U, "[AMBT::decode()] pduUserData", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * dataHeader.getBlocksToFollow()); + Utils::dump(2U, "P25, AMBT::decode(), pduUserData", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * dataHeader.getBlocksToFollow()); } return true; diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 4f97d7adf..ca3de7a8b 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -120,14 +120,14 @@ bool LC::decodeHDU(const uint8_t* data, bool rawOnly) P25Utils::decode(data, raw, 114U, 780U); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::decodeHDU(), HDU Raw", raw, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), HDU Raw", raw, P25_HDU_LENGTH_BYTES); #endif // decode Golay (18,6,8) FEC decodeHDUGolay(raw, rs); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::decodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); #endif // decode RS (36,20,17) FEC @@ -139,12 +139,12 @@ bool LC::decodeHDU(const uint8_t* data, bool rawOnly) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), RS excepted with input data", rs, P25_HDU_LENGTH_BYTES); return false; } #if DEBUG_P25_HDU - Utils::dump(2U, "LC::decodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); #endif m_mfId = rs[9U]; // Mfg Id. @@ -204,14 +204,14 @@ void LC::encodeHDU(uint8_t* data, bool rawOnly) rs[14U] = (m_dstId >> 0) & 0xFFU; // ... #if DEBUG_P25_HDU - Utils::dump(2U, "LC::encodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); #endif // encode RS (36,20,17) FEC m_rs.encode362017(rs); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::encodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); #endif uint8_t raw[P25_HDU_LENGTH_BYTES + 1U]; @@ -229,7 +229,7 @@ void LC::encodeHDU(uint8_t* data, bool rawOnly) P25Utils::encode(raw, data, 114U, 780U); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::encodeHDU(), HDU Interleave", data, P25_HDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeHDU(), HDU Interleave", data, P25_HDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } @@ -262,7 +262,7 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly) decodeLDUHamming(raw, rs + 15U); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // decode RS (24,12,13) FEC @@ -274,12 +274,12 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU1(), RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); return false; } #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif return decodeLC(rs, rawOnly); @@ -297,14 +297,14 @@ void LC::encodeLDU1(uint8_t* data) encodeLC(rs); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode RS (24,12,13) FEC m_rs.encode241213(rs); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode Hamming (10,6,3) FEC and interleave for LC data @@ -328,7 +328,7 @@ void LC::encodeLDU1(uint8_t* data) P25Utils::encode(raw, data, 1356U, 1398U); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU1(), LDU1 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } @@ -361,7 +361,7 @@ bool LC::decodeLDU2(const uint8_t* data) decodeLDUHamming(raw, rs + 15U); #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // decode RS (24,16,9) FEC @@ -373,12 +373,12 @@ bool LC::decodeLDU2(const uint8_t* data) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU2(), RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); return false; } #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif m_algId = rs[9U]; // Algorithm ID @@ -429,14 +429,14 @@ void LC::encodeLDU2(uint8_t* data) rs[11U] = (m_kId >> 0) & 0xFFU; // ... #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::encodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode RS (24,16,9) FEC m_rs.encode24169(rs); #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode Hamming (10,6,3) FEC and interleave for LC data @@ -460,7 +460,7 @@ void LC::encodeLDU2(uint8_t* data) P25Utils::encode(raw, data, 1356U, 1398U); #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::encodeLDU2(), LDU2 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU2(), LDU2 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } @@ -619,7 +619,7 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) // non-standard P25 vendor opcodes (these are just detected for passthru, and stored // as the packed RS value) if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { - //Utils::dump(1U, "Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + //Utils::dump(1U, "P25, LC::decodeLC(), Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); if (m_mfId == MFG_HARRIS) { // Harris P25 opcodes switch (m_lco) { @@ -790,7 +790,7 @@ void LC::encodeLC(uint8_t* rs) rs[8U] = (uint8_t)((rsValue >> 0) & 0xFFU); /* if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { - Utils::dump(1U, "Encoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, LC::encodeLC(), Encoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); } */ } diff --git a/src/common/p25/lc/TDULC.cpp b/src/common/p25/lc/TDULC.cpp index 5883fbc8b..e9fdd2696 100644 --- a/src/common/p25/lc/TDULC.cpp +++ b/src/common/p25/lc/TDULC.cpp @@ -167,7 +167,7 @@ bool TDULC::decode(const uint8_t* data, uint8_t* payload, bool rawTDULC) edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif // decode RS (24,12,13) FEC @@ -179,12 +179,12 @@ bool TDULC::decode(const uint8_t* data, uint8_t* payload, bool rawTDULC) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::decode(), RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); return false; } if (m_verbose) { - Utils::dump(2U, "TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); } if (m_raw != nullptr) @@ -217,14 +217,14 @@ void TDULC::encode(uint8_t* data, const uint8_t* payload, bool rawTDULC) rs[0U] |= 0x40U; // Implicit Operation if (m_verbose) { - Utils::dump(2U, "TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); } // encode RS (24,12,13) FEC m_rs.encode241213(rs); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::encode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::encode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; @@ -237,7 +237,7 @@ void TDULC::encode(uint8_t* data, const uint8_t* payload, bool rawTDULC) P25Utils::encode(raw, data, 114U, 410U); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::encode(), TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::encode(), TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } } diff --git a/src/common/p25/lc/TSBK.cpp b/src/common/p25/lc/TSBK.cpp index 47e300d76..da39bc4fa 100644 --- a/src/common/p25/lc/TSBK.cpp +++ b/src/common/p25/lc/TSBK.cpp @@ -242,13 +242,13 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) return false; } catch (...) { - Utils::dump(2U, "P25, decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::decode(), decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); return false; } } if (m_verbose) { - Utils::dump(2U, "TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } if (m_raw != nullptr) @@ -283,7 +283,7 @@ void TSBK::encode(uint8_t* data, const uint8_t* payload, bool rawTSBK, bool noTr edac::CRC::addCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); if (m_verbose) { - Utils::dump(2U, "TSBK::encode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::encode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } uint8_t raw[P25_TSBK_FEC_LENGTH_BYTES]; @@ -306,7 +306,7 @@ void TSBK::encode(uint8_t* data, const uint8_t* payload, bool rawTSBK, bool noTr P25Utils::encode(raw, data, 114U, 318U); #if DEBUG_P25_TSBK - Utils::dump(2U, "TSBK::encode(), TSBK Interleave", data, P25_TSBK_FEC_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::encode(), TSBK Interleave", data, P25_TSBK_FEC_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } } diff --git a/src/common/p25/lc/tdulc/TDULCFactory.cpp b/src/common/p25/lc/tdulc/TDULCFactory.cpp index 4d5a98902..0d22a137b 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.cpp +++ b/src/common/p25/lc/tdulc/TDULCFactory.cpp @@ -53,19 +53,19 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULCFactory::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULCFactory::createTDULC(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif // decode RS (24,12,13) FEC try { bool ret = m_rs.decode241213(rs); if (!ret) { - LogError(LOG_P25, "TDULCFactory::decode(), failed to decode RS (24,12,13) FEC"); + LogError(LOG_P25, "TDULCFactory::createTDULC(), failed to decode RS (24,12,13) FEC"); return nullptr; } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULCFactory::createTDULC(), RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); return nullptr; } @@ -82,7 +82,7 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) case LCO::CALL_TERM: return decode(new LC_CALL_TERM(), data); default: - LogError(LOG_P25, "TDULCFactory::create(), unknown TDULC LCO value, lco = $%02X", lco); + LogError(LOG_P25, "TDULCFactory::createTDULC(), unknown TDULC LCO value, lco = $%02X", lco); break; } diff --git a/src/common/p25/lc/tsbk/TSBKFactory.cpp b/src/common/p25/lc/tsbk/TSBKFactory.cpp index 3f6a596ba..742d7d491 100644 --- a/src/common/p25/lc/tsbk/TSBKFactory.cpp +++ b/src/common/p25/lc/tsbk/TSBKFactory.cpp @@ -101,7 +101,7 @@ std::unique_ptr TSBKFactory::createTSBK(const uint8_t* data, bool rawTSBK) return nullptr; } catch (...) { - Utils::dump(2U, "P25, decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBKFactory::createTSBK(), decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); return nullptr; } } diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 3dcaecf1c..fb5e260ee 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -70,7 +70,7 @@ void DiagNetwork::processNetwork() UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Message", buffer.get(), length); + Utils::dump(1U, "DiagNetwork::processNetwork(), Network Message", buffer.get(), length); uint32_t peerId = fneHeader.getPeerId(); @@ -360,7 +360,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) default: network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length); + Utils::dump("Unknown transfer opcode from the peer", req->buffer, req->length); break; } } @@ -379,7 +379,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) DECLARE_UINT8_ARRAY(rawPayload, req->length); ::memcpy(rawPayload, req->buffer, req->length); - // Utils::dump(1U, "Raw Payload", rawPayload, req->length); + // Utils::dump(1U, "DiagNetwork::taskNetworkRx(), PEER_LINK, Raw Payload", rawPayload, req->length); if (network->m_peerLinkActPkt.find(peerId) == network->m_peerLinkActPkt.end()) { network->m_peerLinkActPkt.insert(peerId, FNENetwork::PacketBufferEntry()); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 2e3158f1b..d3c8c18fc 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -268,7 +268,7 @@ void FNENetwork::processNetwork() UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Message", buffer.get(), length); + Utils::dump(1U, "FNENetwork::processNetwork(), Network Message", buffer.get(), length); uint32_t peerId = fneHeader.getPeerId(); @@ -728,7 +728,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) break; default: - Utils::dump("unknown protocol opcode from peer", req->buffer, req->length); + Utils::dump("Unknown protocol opcode from peer", req->buffer, req->length); break; } } @@ -1159,7 +1159,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) break; default: network->writePeerNAK(peerId, streamId, TAG_REPEATER_GRANT, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("unknown state for grant request from the peer", req->buffer, req->length); + Utils::dump("Unknown state for grant request from the peer", req->buffer, req->length); break; } } @@ -1224,7 +1224,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (network->m_debug) { LogDebugEx(LOG_HOST, "FNENetwork::threadedNetworkRx()", "keyLength = %u", keyLength); - Utils::dump(1U, "Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + Utils::dump(1U, "FNENetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); } LogMessage(LOG_NET, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identity().c_str(), @@ -1552,13 +1552,13 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) break; default: network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("unknown announcement opcode from the peer", req->buffer, req->length); + Utils::dump("Unknown announcement opcode from the peer", req->buffer, req->length); } } break; default: - Utils::dump("unknown opcode from the peer", req->buffer, req->length); + Utils::dump("Unknown opcode from the peer", req->buffer, req->length); break; } } @@ -2447,7 +2447,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin if (m_debug) { LogDebugEx(LOG_HOST, "FNENetwork::processTEKResponse()", "keyLength = %u", keyLength); - Utils::dump(1U, "Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + Utils::dump(1U, "FNENetwork::processTEKResponse(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); } // build response buffer diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 6dd2ae504..e3070e68f 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -387,7 +387,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco break; default: - Utils::dump("unknown opcode from the master", data, length); + Utils::dump("Unknown opcode from the master", data, length); break; } } @@ -449,7 +449,7 @@ bool PeerNetwork::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "PeerNetwork::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, pktSeq(), m_loginStreamId); @@ -518,7 +518,7 @@ void PeerNetwork::taskNetworkRx(PeerPacketRequest* req) break; default: - Utils::dump("unknown protocol opcode from the master", req->buffer, req->length); + Utils::dump("Unknown protocol opcode from the master", req->buffer, req->length); break; } } diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 4e52b9151..f57c36aec 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -527,7 +527,7 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p delete[] in; if (m_debug) { - Utils::dump("REST Password Hash", m_passwordHash, 32U); + Utils::dump("RESTAPI::RESTAPI(), REST Password Hash", m_passwordHash, 32U); } #if defined(ENABLE_SSL) @@ -779,7 +779,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, co } if (m_debug) { - Utils::dump("Password Hash", passwordHash, 32U); + Utils::dump("RESTAPI::restAPI_PutAuth(), Password Hash", passwordHash, 32U); } // compare hashes diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp index dafab7915..89aaa829f 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp @@ -131,7 +131,7 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee bool ret = status->header.decode(frame); if (!ret) { LogError(LOG_NET, "DMR Slot %u, DataType::DATA_HEADER, unable to decode the network data header", status->slotNo); - Utils::dump(1U, "Unfixable PDU Data", frame, DMR_FRAME_LENGTH_BYTES); + Utils::dump(1U, "DMR, Unfixable PDU Data", frame, DMR_FRAME_LENGTH_BYTES); delete status; m_status.erase(peerId); @@ -254,7 +254,7 @@ void DMRPacketData::dispatch(uint32_t peerId, dmr::data::NetData& dmrData, const } if (m_network->m_dumpPacketData) { - Utils::dump(1U, "ISP PDU Packet", status->pduUserData, status->pduDataOffset); + Utils::dump(1U, "DMR, ISP PDU Packet", status->pduUserData, status->pduDataOffset); } } } diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index 667eee8bc..e787e3766 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -140,7 +140,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee bool ret = status->header.decode(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); delete status; m_status.erase(peerId); @@ -234,7 +234,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee bool ret = status->header.decodeExtAddr(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); delete status; m_status.erase(peerId); @@ -306,7 +306,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); if (m_network->m_dumpPacketData) { - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); } } @@ -368,7 +368,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a uint16_t pktLen = Utils::reverseEndian(ipHeader->ip_len); // bryanb: this could be problematic on different endianness #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::processPacketFrame() packet", data, pktLen); + Utils::dump(1U, "P25, P25PacketData::processPacketFrame() packet", data, pktLen); #endif VTUNDataFrame* dataFrame = new VTUNDataFrame(); @@ -471,7 +471,7 @@ void P25PacketData::clock(uint32_t ms) DECLARE_UINT8_ARRAY(pduUserData, pduLength); ::memcpy(pduUserData + 4U, dataFrame->buffer, dataFrame->pktLen); #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::clock() pduUserData", pduUserData, pduLength); + Utils::dump(1U, "P25, P25PacketData::clock(), pduUserData", pduUserData, pduLength); #endif dispatchUserFrameToFNE(rspHeader, true, pduUserData); } @@ -523,7 +523,7 @@ void P25PacketData::dispatch(uint32_t peerId) } if (m_network->m_dumpPacketData && status->dataBlockCnt > 0U) { - Utils::dump(1U, "ISP PDU Packet", status->pduUserData, status->pduUserDataLength); + Utils::dump(1U, "P25, ISP PDU Packet", status->pduUserData, status->pduUserDataLength); } if (status->header.getFormat() == PDUFormatType::RSP) { @@ -666,7 +666,7 @@ void P25PacketData::dispatch(uint32_t peerId) DECLARE_UINT8_ARRAY(ipFrame, pktLen); ::memcpy(ipFrame, status->pduUserData + dataPktOffset, pktLen); #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::dispatch() ipFrame", ipFrame, pktLen); + Utils::dump(1U, "P25, P25PacketData::dispatch(), ipFrame", ipFrame, pktLen); #endif if (!m_network->m_host->m_tun->write(ipFrame, pktLen)) { LogError(LOG_NET, P25_PDU_STR ", failed to write IP frame to virtual tunnel, len %u", pktLen); @@ -914,7 +914,7 @@ void P25PacketData::write_PDU_ARP(uint32_t addr) SET_UINT32(addr, arpPacket, 18U); // Target Protocol Address #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::write_PDU_ARP() arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); + Utils::dump(1U, "P25, P25PacketData::write_PDU_ARP(), arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); #endif LogMessage(LOG_NET, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(addr).c_str(), fneIPv4.c_str(), WUID_FNE); @@ -970,7 +970,7 @@ void P25PacketData::write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorL SET_UINT24(requestorLlid, arpPacket, 15U); // Requestor Hardware Address SET_UINT32(requestorAddr, arpPacket, 18U); // Requestor Protocol Address #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::write_PDU_ARP_Reply() arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); + Utils::dump(1U, "P25, P25PacketData::write_PDU_ARP_Reply(), arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); #endif LogMessage(LOG_NET, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(targetAddr).c_str(), tgtLlid); @@ -1087,7 +1087,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network: } if (m_network->m_dumpPacketData) { - Utils::dump("OSP PDU User Data", pduUserData, packetLength); + Utils::dump("P25, OSP PDU User Data", pduUserData, packetLength); } // generate the PDU data diff --git a/src/host/Host.cpp b/src/host/Host.cpp index bbb8d6162..3dc7ac4b5 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -1536,7 +1536,7 @@ bool Host::rmtPortModemHandler(Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM r if (rspType == RTM_OK && len > 0U) { if (modem->getTrace()) - Utils::dump(1U, "TX Remote Data", buffer, len); + Utils::dump(1U, "Host::rmtPortModemHandler(), TX Remote Data", buffer, len); // never send less then 3 bytes if (len < 3U) @@ -1553,11 +1553,11 @@ bool Host::rmtPortModemHandler(Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM r uint32_t ret = m_modemRemotePort->read(data, BUFFER_LENGTH); if (ret > 0) { if (modem->getTrace()) - Utils::dump(1U, "RX Remote Data", (uint8_t*)data, ret); + Utils::dump(1U, "Host::rmtPortModemHandler(), RX Remote Data", (uint8_t*)data, ret); if (ret < 3U) { LogError(LOG_MODEM, "Illegal length of remote data must be >3 bytes"); - Utils::dump("Buffer dump", data, ret); + Utils::dump("Host::rmtPortModemHandler(), data", data, ret); // handled modem response return true; diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index e4fbf27cd..396656b4c 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -1779,8 +1779,8 @@ void Slot::setShortLC_TSCC(SiteData siteData, uint16_t counter) lc[3U] = (uint8_t)((lcValue >> 0) & 0xFFU); lc[4U] = edac::CRC::crc8(lc, 4U); - //LogDebugEx(LOG_DMR, "Slot::setShortLC_TSCC()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); - //Utils::dump(1U, "[Slot::shortLC_TSCC()]", lc, 5U); + // LogDebugEx(LOG_DMR, "Slot::setShortLC_TSCC()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); + // Utils::dump(1U, "DMR, Slot::shortLC_TSCC(), LC", lc, 5U); uint8_t sLC[9U]; @@ -1839,8 +1839,8 @@ void Slot::setShortLC_Payload(SiteData siteData, uint16_t counter) lc[3U] = (uint8_t)((lcValue >> 0) & 0xFFU); lc[4U] = edac::CRC::crc8(lc, 4U); - //LogDebugEx(LOG_DMR, "Slot::setShortLC_Payload()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); - //Utils::dump(1U, "[Slot::setShortLC_Payload()]", lc, 5U); + // LogDebugEx(LOG_DMR, "Slot::setShortLC_Payload()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); + // Utils::dump(1U, "DMR, Slot::setShortLC_Payload(), LC", lc, 5U); uint8_t sLC[9U]; diff --git a/src/host/dmr/packet/Data.cpp b/src/host/dmr/packet/Data.cpp index e8afad828..236749196 100644 --- a/src/host/dmr/packet/Data.cpp +++ b/src/host/dmr/packet/Data.cpp @@ -319,7 +319,7 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_dumpDataPacket) { - Utils::dump(1U, "PDU Packet", m_pduUserData, m_pduDataOffset); + Utils::dump(1U, "DMR, PDU Packet", m_pduUserData, m_pduDataOffset); } } @@ -559,7 +559,7 @@ void Data::processNetwork(const data::NetData& dmrData) } if (m_dumpDataPacket) { - Utils::dump(1U, "PDU Packet", m_pduUserData, m_pduDataOffset); + Utils::dump(1U, "DMR, PDU Packet", m_pduUserData, m_pduDataOffset); } } diff --git a/src/host/modem/Modem.cpp b/src/host/modem/Modem.cpp index dd5956052..578b24815 100644 --- a/src/host/modem/Modem.cpp +++ b/src/host/modem/Modem.cpp @@ -581,7 +581,7 @@ void Modem::clock(uint32_t ms) m_rxDMRQueue1.addData(m_buffer + 3U, m_length - 3U); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX DMR Data 1", m_buffer + 3U, m_length - 3U); + Utils::dump(1U, "Modem::clock(), RX DMR Data 1", m_buffer + 3U, m_length - 3U); } } break; @@ -607,7 +607,7 @@ void Modem::clock(uint32_t ms) m_rxDMRQueue2.addData(m_buffer + 3U, m_length - 3U); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX DMR Data 2", m_buffer + 3U, m_length - 3U); + Utils::dump(1U, "Modem::clock(), RX DMR Data 2", m_buffer + 3U, m_length - 3U); } } break; @@ -669,7 +669,7 @@ void Modem::clock(uint32_t ms) m_rxP25Queue.addData(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + Utils::dump(1U, "Modem::clock(), RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; @@ -712,7 +712,7 @@ void Modem::clock(uint32_t ms) m_rxNXDNQueue.addData(m_buffer + 3U, m_length - 3U); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX NXDN Data", m_buffer + 3U, m_length - 3U); + Utils::dump(1U, "Modem::clock(), RX NXDN Data", m_buffer + 3U, m_length - 3U); } } break; @@ -889,7 +889,7 @@ void Modem::clock(uint32_t ms) default: LogWarning(LOG_MODEM, "Unknown message, type = %02X", m_buffer[2U]); - Utils::dump("Buffer dump", m_buffer, m_length); + Utils::dump("Modem::clock(), m_buffer", m_buffer, m_length); if (m_rspState != RESP_START) m_rspState = RESP_START; break; @@ -1222,7 +1222,7 @@ void Modem::clearDMRFrame1() buffer[1U] = 3U; buffer[2U] = CMD_DMR_CLEAR1; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearDMRFrame1()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearDMRFrame1(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1238,7 +1238,7 @@ void Modem::clearDMRFrame2() buffer[1U] = 3U; buffer[2U] = CMD_DMR_CLEAR2; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearDMRFrame2()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearDMRFrame2(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1254,7 +1254,7 @@ void Modem::clearP25Frame() buffer[1U] = 3U; buffer[2U] = CMD_P25_CLEAR; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearP25Data()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearP25Data(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1270,7 +1270,7 @@ void Modem::clearNXDNFrame() buffer[1U] = 3U; buffer[2U] = CMD_NXDN_CLEAR; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearNXDNFrame()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearNXDNFrame(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1382,7 +1382,7 @@ bool Modem::writeDMRFrame1(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeDMRFrame1(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "[Modem::writeDMRFrame1()] Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeDMRFrame1(), Attempted Data", data, length); return false; } @@ -1401,7 +1401,7 @@ bool Modem::writeDMRFrame1(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeDMRFrame1()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeDMRFrame1()] Immediate TX DMR Data 1", buffer + 3U, length - 1U); + Utils::dump(1U, "Modem::writeDMRFrame1(), Immediate TX DMR Data 1", buffer + 3U, length - 1U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1436,7 +1436,7 @@ bool Modem::writeDMRFrame2(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeDMRFrame2(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "Modem::writeDMRFrame2(); Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeDMRFrame2(), Attempted Data", data, length); return false; } @@ -1455,7 +1455,7 @@ bool Modem::writeDMRFrame2(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeDMRFrame2()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeDMRFrame2()] Immediate TX DMR Data 2", buffer + 3U, length - 1U); + Utils::dump(1U, "Modem::writeDMRFrame2(), Immediate TX DMR Data 2", buffer + 3U, length - 1U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1493,7 +1493,7 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeP25Frame(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "[Modem::writeP25Frame()] Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeP25Frame(), Attempted Data", data, length); return false; } @@ -1520,7 +1520,7 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeP25Frame()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeP25Frame()] Immediate TX P25 Data", buffer + 3U, length - 3U); + Utils::dump(1U, "Modem::writeP25Frame(), Immediate TX P25 Data", buffer + 3U, length - 3U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1555,7 +1555,7 @@ bool Modem::writeNXDNFrame(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeNXDNFrame(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "[Modem::writeNXDNFrame()] Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeNXDNFrame(), Attempted Data", data, length); return false; } @@ -1574,7 +1574,7 @@ bool Modem::writeNXDNFrame(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeNXDNFrame()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeNXDNFrame()] Immediate TX NXDN Data", buffer + 3U, length - 1U); + Utils::dump(1U, "Modem::writeNXDNFrame(), Immediate TX NXDN Data", buffer + 3U, length - 1U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1612,7 +1612,7 @@ bool Modem::writeDMRStart(bool tx) buffer[2U] = CMD_DMR_START; buffer[3U] = tx ? 0x01U : 0x00U; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::writeDMRStart()] Written", buffer, 4U); + Utils::dump(1U, "Modem::writeDMRStart(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } @@ -1643,7 +1643,7 @@ bool Modem::writeDMRShortLC(const uint8_t* lc) buffer[10U] = lc[7U]; buffer[11U] = lc[8U]; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::writeDMRShortLC()] Written", buffer, 12U); + Utils::dump(1U, "Modem::writeDMRShortLC(), Written", buffer, 12U); #endif return write(buffer, 12U) == 12; } @@ -1664,7 +1664,7 @@ bool Modem::writeDMRAbort(uint32_t slotNo) buffer[2U] = CMD_DMR_ABORT; buffer[3U] = slotNo; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::writeDMRAbort()] Written", buffer, 4U); + Utils::dump(1U, "Modem::writeDMRAbort(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } @@ -1688,7 +1688,7 @@ bool Modem::setDMRIgnoreCACH_AT(uint8_t slotNo) // are we on a protocol version 3 firmware? if (m_protoVer >= 3U) { #if DEBUG_MODEM - Utils::dump(1U, "[Modem::setDMRIgnoreCACH_AT()] Written", buffer, 4U); + Utils::dump(1U, "Modem::setDMRIgnoreCACH_AT(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } else { @@ -1733,7 +1733,7 @@ bool Modem::setState(DVM_STATE state) buffer[2U] = CMD_SET_MODE; buffer[3U] = state; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::setState()] Written", buffer, 4U); + Utils::dump(1U, "Modem::setState(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } @@ -2160,7 +2160,7 @@ bool Modem::readFlash() if (resp == RTM_OK && m_buffer[2U] == CMD_FLSH_READ) { uint8_t len = m_buffer[1U]; if (m_debug) { - Utils::dump(1U, "Modem Flash Contents", m_buffer + 3U, len - 3U); + Utils::dump(1U, "Modem::readFlash(), Modem Flash Contents", m_buffer + 3U, len - 3U); } if (len == 249U) { @@ -2361,7 +2361,7 @@ void Modem::printDebug(const uint8_t* buffer, uint16_t len) ::memset(data, 0x00U, 255U); ::memcpy(data, buffer, len); - Utils::dump(1U, "Modem::printDebug() DSP_FW_API Debug Dump", data, len); + Utils::dump(1U, "Modem::printDebug(), DSP_FW_API Debug Dump", data, len); } } @@ -2502,7 +2502,7 @@ RESP_TYPE_DVM Modem::getResponse() } if (m_respTrace) - Utils::dump(1U, "[Modem::getResponse()] Buffer", m_buffer, m_length); + Utils::dump(1U, "Modem::getResponse(), Buffer", m_buffer, m_length); } m_rspState = RESP_START; diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index b51306f1c..392f492c3 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -200,7 +200,7 @@ void ModemV24::clock(uint32_t ms) else convertToAirV24(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); if (m_trace) - Utils::dump(1U, "ModemV24::clock() RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + Utils::dump(1U, "ModemV24::clock(), RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; @@ -376,7 +376,7 @@ void ModemV24::clock(uint32_t ms) default: LogWarning(LOG_MODEM, "Unknown message, type = %02X", m_buffer[2U]); - Utils::dump("Buffer dump", m_buffer, m_length); + Utils::dump("ModemV24::clock(), m_buffer", m_buffer, m_length); if (m_rspState != RESP_START) m_rspState = RESP_START; break; @@ -546,7 +546,7 @@ void ModemV24::storeConvertedRx(const uint8_t* buffer, uint32_t length) storedLen[1U] = length & 0xFFU; m_rxP25Queue.addData(storedLen, 2U); - //Utils::dump("Storing converted RX data", buffer, length); + // Utils::dump("ModemV24::storeConvertedRx(), Storing Converted Rx Data", buffer, length); m_rxP25Queue.addData(buffer, length); } @@ -589,7 +589,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) - Utils::dump("V.24 RX data from board", dfsiData, length - 1U); + Utils::dump("ModemV24::convertToAirV24(), V.24 RX Data From Modem", dfsiData, length - 1U); DFSIFrameType::E frameType = (DFSIFrameType::E)dfsiData[0U]; m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -628,7 +628,8 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memset(m_rxCall->VHDR1, 0x00U, DFSI_MOT_VHDR_1_LEN); ::memcpy(m_rxCall->VHDR1, dfsiData + DFSI_MOT_START_LEN, DFSI_MOT_VHDR_1_LEN - DFSI_MOT_START_LEN); - // Utils::dump("V.24 RX VHDR1", m_rxCall->VHDR1, DFSI_MOT_VHDR_1_LEN); + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24(), V.24 RX, VHDR1", m_rxCall->VHDR1, DFSI_MOT_VHDR_1_LEN); } break; case DFSIFrameType::MOT_VHDR_2: @@ -639,7 +640,8 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memset(m_rxCall->VHDR2, 0x00U, DFSI_MOT_VHDR_2_LEN); ::memcpy(m_rxCall->VHDR2, dfsiData + 1U, DFSI_MOT_VHDR_2_LEN); - // Utils::dump("V.24 RX VHDR2", m_rxCall->VHDR2, DFSI_MOT_VHDR_2_LEN); + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24(), V.24 RX, VHDR2", m_rxCall->VHDR2, DFSI_MOT_VHDR_2_LEN); // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; @@ -653,7 +655,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memcpy(raw + 26U, m_rxCall->VHDR2 + 9U, 8U); ::memcpy(raw + 34U, m_rxCall->VHDR2 + 18U, 2U); - // Utils::dump("V.24 RX VHDR raw", raw, DFSI_VHDR_RAW_LEN); + // Utils::dump("ModemV24::convertToAirV24(), V.24 RX VHDR, raw", raw, DFSI_VHDR_RAW_LEN); // buffer for decoded VHDR data uint8_t vhdr[DFSI_VHDR_LEN]; @@ -662,7 +664,8 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) Utils::hex2Bin(raw[i], vhdr, offset); - // Utils::dump("V.24 RX VHDR vhdr", vhdr, DFSI_VHDR_LEN); + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24() V.24 RX VHDR, VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); // try to decode the RS data try { @@ -670,6 +673,9 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) if (!ret) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); } else { + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24(), V.24 RX VHDR, VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); + // late entry? if (!m_rxCallInProgress) { m_rxCallInProgress = true; @@ -890,7 +896,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) if (m_debug) { LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "Full Rate Voice, frameType = $%02X, errors = %u, busy = %u", voice.getFrameType(), voice.getTotalErrors(), voice.getBusy()); - Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); + Utils::dump(1U, "ModemV24::converToAirV24(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } if (voice.getTotalErrors() > 0U) { @@ -1128,7 +1134,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "Modem, V.24 LDU1 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } lc::LC lc = lc::LC(); @@ -1220,7 +1226,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "Modem, V.24 LDU2 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } lc::LC lc = lc::LC(); @@ -1281,7 +1287,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) - Utils::dump("DFSI RX data from UDP", dfsiData, length - 1U); + Utils::dump("ModemV24::converToAirTIA(), DFSI RX Data From UDP", dfsiData, length - 1U); ControlOctet ctrl = ControlOctet(); ctrl.decode(dfsiData); @@ -1354,7 +1360,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U); if (m_debug && m_trace) - Utils::dump("ModemV24::convertToAirTIA() VoiceHeader1", m_rxCall->VHDR1, 18U); + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX, VHDR1", m_rxCall->VHDR1, 18U); dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker } @@ -1366,7 +1372,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->VHDR2, dfsiData + dataOffs + 1U, 18U); if (m_debug && m_trace) - Utils::dump("ModemV24::convertToAirTIA() VoiceHeader1", m_rxCall->VHDR1, 18U); + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX, VHDR2", m_rxCall->VHDR1, 18U); dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker @@ -1391,7 +1397,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } if (m_debug && m_trace) - Utils::dump("ModemV24::convertToAirTIA() VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX VHDR, VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); // try to decode the RS data try { @@ -1400,7 +1406,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); } else { if (m_debug && m_trace) - Utils::dump("ModemV24::convertToAirTIA() VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX VHDR, VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); // late entry? if (!m_rxCallInProgress) { @@ -1465,7 +1471,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) if (m_debug) { LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Full Rate Voice, frameType = $%02X, busy = %u, lostFrame = %u, muteFrame = %u, superFrameCnt = %u, errors = %u", voice.getFrameType(), voice.getBusy(), voice.getLostFrame(), voice.getMuteFrame(), voice.getSuperframeCnt(), voice.getTotalErrors()); - Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); + Utils::dump(1U, "ModemV24::convertToAirTIA(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } dataOffs += voice.getLength(); @@ -1723,7 +1729,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "Modem, V.24 LDU1, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } lc::LC lc = lc::LC(); @@ -1815,7 +1821,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "Modem, V.24 LDU2, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } lc::LC lc = lc::LC(); @@ -1871,7 +1877,7 @@ void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType if (m_debug) LogDebugEx(LOG_MODEM, "ModemV24::queueP25Frame()", "msgType = $%02X", msgType); if (m_trace) - Utils::dump(1U, "[ModemV24::queueP25Frame()] data", data, len); + Utils::dump(1U, "ModemV24::queueP25Frame(), data", data, len); // get current time in ms uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -1959,7 +1965,7 @@ void ModemV24::startOfStreamV24(const p25::lc::LC& control) start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamV24() StartOfStream", startBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::startOfStreamV24(), StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); @@ -2000,7 +2006,7 @@ void ModemV24::startOfStreamV24(const p25::lc::LC& control) vhdr1Buf[20U + DFSI_MOT_START_LEN] = DFSI_BUSY_BITS_INBOUND; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamV24() VoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); + Utils::dump(1U, "ModemV24::startOfStreamV24(), VoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); queueP25Frame(vhdr1Buf, DFSI_MOT_VHDR_1_LEN, STT_NON_IMBE); @@ -2017,7 +2023,7 @@ void ModemV24::startOfStreamV24(const p25::lc::LC& control) // send VHDR2 if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamV24() VoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); + Utils::dump(1U, "ModemV24::startOfStreamV24(), VoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); queueP25Frame(vhdr2Buf, DFSI_MOT_VHDR_2_LEN, STT_NON_IMBE); } @@ -2037,7 +2043,7 @@ void ModemV24::endOfStreamV24() end.encode(endBuf); if (m_trace) - Utils::dump(1U, "ModemV24::endOfStreamV24() StartOfStream", endBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::endOfStreamV24(), StartOfStream", endBuf, DFSI_MOT_START_LEN); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); @@ -2090,7 +2096,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) length += StartOfStream::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamTIA() StartOfStream", buffer, length); + Utils::dump(1U, "ModemV24::startOfStreamTIA(), StartOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); @@ -2148,7 +2154,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) length += StartOfStream::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader1", buffer, length); + Utils::dump(1U, "ModemV24::startOfStreamTIA(), VoiceHeader1", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); @@ -2177,7 +2183,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) length += StartOfStream::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader2", buffer, length); + Utils::dump(1U, "ModemV24::startOfStreamTIA(), VoiceHeader2", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); } @@ -2205,7 +2211,7 @@ void ModemV24::endOfStreamTIA() length += BlockHeader::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::endOfStreamTIA() EndOfStream", buffer, length); + Utils::dump(1U, "ModemV24::endOfStreamTIA(), EndOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); @@ -2233,7 +2239,7 @@ void ModemV24::ackStartOfStreamTIA() length += BlockHeader::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::ackStartOfStreamTIA() Ack StartOfStream", buffer, length); + Utils::dump(1U, "ModemV24::ackStartOfStreamTIA(), Ack StartOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE_NO_JITTER); } @@ -2246,7 +2252,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) assert(length > 0U); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirV24() data", data, length); + Utils::dump(1U, "ModemV24::convertFromAirV24(), data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); @@ -2351,7 +2357,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirV24() TDULC MotStartOfStream", startBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::convertFromAirV24(), TDULC MotStartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); @@ -2371,7 +2377,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) tf.encode(tdulcBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirV24() MotTDULCFrame", tdulcBuf, DFSI_MOT_TDULC_LEN); + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotTDULCFrame", tdulcBuf, DFSI_MOT_TDULC_LEN); queueP25Frame(tdulcBuf, DFSI_MOT_TDULC_LEN, STT_NON_IMBE_NO_JITTER); @@ -2412,7 +2418,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirV24() TSBK StartOfStream", startBuf, DFSI_MOT_START_LEN); + Utils::dump(1U, "ModemV24::convertFromAirV24(), TSBK StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); @@ -2432,7 +2438,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) tf.encode(tsbkBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirV24() MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); queueP25Frame(tsbkBuf, DFSI_MOT_TSBK_LEN, STT_NON_IMBE_NO_JITTER); @@ -2626,7 +2632,7 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAirV24() Encoded V.24 Voice Frame Data", buffer, bufferSize); + Utils::dump("ModemV24::convertFromAirV24(), Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); @@ -2644,7 +2650,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) assert(length > 0U); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirTIA() data", data, length); + Utils::dump(1U, "ModemV24::convertFromAirTIA(), data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); @@ -2918,7 +2924,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAirTIA() Encoded V.24 Voice Frame Data", buffer, bufferSize); + Utils::dump("ModemV24::convertFromAirTIA(), Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 552b7313e..2005c9d86 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -363,7 +363,7 @@ void V24UDPPort::processCtrlNetwork() UInt8Array buffer = m_ctrlFrameQueue->read(length, address, addrLen); if (length > 0) { if (m_debug) - Utils::dump(1U, "FSC Control Network Message", buffer.get(), length); + Utils::dump(1U, "V24UDPPort::processCtrlNetwork(), FSC Control Network Message", buffer.get(), length); V24PacketRequest* req = new V24PacketRequest(); req->obj = this; @@ -880,7 +880,7 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui } if (m_debug) - Utils::dump(1U, "[V24UDPPort::generateMessage()] Buffered Message", buffer, bufferLen); + Utils::dump(1U, "V24UDPPort::generateMessage(), Buffered Message", buffer, bufferLen); if (outBufferLen != nullptr) { *outBufferLen = bufferLen; diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index acccc1576..f82f2cae7 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -176,7 +176,7 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p delete[] in; if (m_debug) { - Utils::dump("REST Password Hash", m_passwordHash, 32U); + Utils::dump("RESTAPI::RESTAPI(), REST Password Hash", m_passwordHash, 32U); } #if defined(ENABLE_SSL) @@ -432,7 +432,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, co } if (m_debug) { - Utils::dump("Password Hash", passwordHash, 32U); + Utils::dump("RESTAPI::restAPI_PutAuth(), Password Hash", passwordHash, 32U); } // compare hashes @@ -1611,7 +1611,7 @@ void RESTAPI::restAPI_PutP25RawTSBK(const HTTPPayload& request, HTTPPayload& rep } if (m_debug) { - Utils::dump("Raw TSBK", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump("RESTAPI::restAPI_PutP25RawTSBK(), Raw TSBK", tsbk, P25_TSBK_LENGTH_BYTES); } m_p25->control()->writeRF_TSDU_Raw(tsbk); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index a84720b3e..2d6bb5c58 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1505,7 +1505,7 @@ void Control::processNetwork() if (m_debug) { LogDebug(LOG_NET, "P25, HDU algId = $%02X, kId = $%02X", algId, kid); - Utils::dump(1U, "P25 HDU Network MI", mi, MI_LENGTH_BYTES); + Utils::dump(1U, "P25, HDU Network MI", mi, MI_LENGTH_BYTES); } control.setAlgId(algId); diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index a15b0c501..5c4230561 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -96,7 +96,7 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfPduUserDataLength = 0U; } - //Utils::dump(1U, "Raw PDU ISP", data, len); + //Utils::dump(1U, "P25, Data::process(), Raw PDU ISP", data, len); uint32_t start = m_rfPDUCount * P25_PDU_FRAME_LENGTH_BITS; @@ -113,7 +113,7 @@ bool Data::process(uint8_t* data, uint32_t len) bool ret = m_rfDataHeader.decode(buffer); if (!ret) { LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); m_rfDataHeader.reset(); m_rfExtendedAddress = false; @@ -178,7 +178,7 @@ bool Data::process(uint8_t* data, uint32_t len) bool ret = m_rfDataHeader.decodeExtAddr(buffer); if (!ret) { LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "Unfixable PDU Data", m_rfPDU + offset, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", m_rfPDU + offset, P25_PDU_HEADER_LENGTH_BYTES); m_rfDataHeader.reset(); m_rfPDUCount = 0U; @@ -297,7 +297,7 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_dumpPDUData) { - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); } } @@ -313,7 +313,7 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_dumpPDUData && m_rfDataBlockCnt > 0U) { - Utils::dump(1U, "PDU Packet", m_rfPduUserData, m_rfPduUserDataLength); + Utils::dump(1U, "P25, PDU Packet", m_rfPduUserData, m_rfPduUserDataLength); } if (m_rfDataBlockCnt < blocksToFollow) { @@ -522,7 +522,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) bool ret = m_netDataHeader.decode(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); m_netDataHeader.reset(); m_netDataBlockCnt = 0U; @@ -643,7 +643,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) bool ret = m_netDataHeader.decodeExtAddr(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); m_netDataHeader.reset(); m_netDataBlockCnt = 0U; @@ -719,7 +719,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); if (m_dumpPDUData) { - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); } } @@ -734,7 +734,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } if (m_dumpPDUData && m_netDataBlockCnt > 0U) { - Utils::dump(1U, "PDU Packet", m_netPduUserData, m_netPduUserDataLength); + Utils::dump(1U, "P25, PDU Packet", m_netPduUserData, m_netPduUserDataLength); } if (m_netDataBlockCnt < blocksToFollow) { @@ -1443,7 +1443,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac P25Utils::addStatusBits(data + 2U, newBitLength, m_inbound, true); P25Utils::setStatusBitsStartIdle(data + 2U); - //Utils::dump("Raw PDU OSP", data, newByteLength + 2U); + //Utils::dump("P25, Data::writeRF_PDU(), Raw PDU OSP", data, newByteLength + 2U); if (m_p25->m_duplex) { data[0U] = modem::TAG_DATA; @@ -1520,7 +1520,7 @@ void Data::writeNet_PDU_Buffered() edac::CRC::addCRC32(m_netPduUserData, m_netPduUserDataLength); if (m_dumpPDUData) { - Utils::dump("OSP PDU User Data (NET)", m_netPduUserData, m_netPduUserDataLength); + Utils::dump("P25, OSP PDU User Data (NET)", m_netPduUserData, m_netPduUserDataLength); } // generate the PDU data @@ -1612,7 +1612,7 @@ void Data::writeRF_PDU_Buffered() edac::CRC::addCRC32(m_rfPduUserData, m_rfPduUserDataLength); if (m_dumpPDUData) { - Utils::dump("OSP PDU User Data (RF)", m_rfPduUserData, m_rfPduUserDataLength); + Utils::dump("P25, OSP PDU User Data (RF)", m_rfPduUserData, m_rfPduUserDataLength); } // generate the PDU data diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 2175319a4..efc04c053 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -859,8 +859,8 @@ bool Voice::process(uint8_t* data, uint32_t len) getNextMI(lastMI, nextMI); if (m_verbose && m_debug) { - Utils::dump(1U, "Previous P25 MI", lastMI, MI_LENGTH_BYTES); - Utils::dump(1U, "Calculated next P25 MI", nextMI, MI_LENGTH_BYTES); + Utils::dump(1U, "P25, LDU2, Previous P25 MI", lastMI, MI_LENGTH_BYTES); + Utils::dump(1U, "P25, LDU2, Calculated next P25 MI", nextMI, MI_LENGTH_BYTES); } m_rfLC.setMI(nextMI); diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 3a02939f5..d9742e49a 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -513,7 +513,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp { uint8_t len = buffer[1U]; if (m_debug) { - Utils::dump(1U, "Modem Flash Contents", buffer + 3U, len - 3U); + Utils::dump(1U, "HostSetup::portModemHandler(), Modem Flash Contents", buffer + 3U, len - 3U); } if (len == 249U) { bool ret = edac::CRC::checkCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); @@ -569,7 +569,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp default: LogWarning(LOG_CAL, "Unknown message, type = %02X", buffer[2U]); - Utils::dump("Buffer dump", buffer, len); + Utils::dump("HostSetup::portModemHandler(), buffer", buffer, len); break; } } @@ -1292,7 +1292,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) uint32_t bits = P25Utils::decode(buffer, pduBuffer, 0, P25_PDU_FRAME_LENGTH_BITS); - Utils::dump(1U, "Raw PDU Dump", buffer, P25_PDU_FRAME_LENGTH_BYTES); + Utils::dump(1U, "P25, Raw PDU Dump", buffer, P25_PDU_FRAME_LENGTH_BYTES); uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; ::memset(rfPDU, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); @@ -1304,7 +1304,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) bool ret = dataHeader.decode(pduBuffer); if (!ret) { LogWarning(LOG_CAL, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", pduBuffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", pduBuffer, P25_PDU_FEC_LENGTH_BYTES); } else { LogMessage(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", @@ -1320,7 +1320,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(buffer); - Utils::dump(1U, "Raw TSBK Dump", buffer, P25_TSDU_FRAME_LENGTH_BYTES); + Utils::dump(1U, "P25, Raw TSBK Dump", buffer, P25_TSDU_FRAME_LENGTH_BYTES); if (tsbk == nullptr) { LogWarning(LOG_CAL, P25_TSDU_STR ", undecodable LC"); diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 88faf5425..e0573c7c5 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -1290,7 +1290,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 break; } - // Utils::dump(1U, "IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + // Utils::dump(1U, "P25, HostPatch::cryptP25AudioFrame(), IMBE", imbe, RAW_IMBE_LENGTH_BYTES); // first -- decrypt the IMBE codeword if (tekSrcAlgoId != p25::defines::ALGO_UNENCRYPT && tekSrcKeyId > 0U) { @@ -1303,7 +1303,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); break; } } else { @@ -1316,7 +1316,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); break; } } @@ -1334,7 +1334,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); break; } } else { @@ -1347,7 +1347,7 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: - LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); break; } } @@ -1448,7 +1448,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) LogMessage(LOG_NET, "MMDVM " P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); if (m_debug) - Utils::dump(1U, "MMDVM -> DVM LDU1", m_netLDU1, 9U * 25U); + Utils::dump(1U, "P25, HostPatch::writeNet_LDU1(), MMDVM -> DVM LDU1", m_netLDU1, 9U * 25U); m_network->writeP25LDU1(m_netLC, lsd, m_netLDU1, FrameType::DATA_UNIT); @@ -1458,7 +1458,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) } else { if (m_debug) - Utils::dump(1U, "DVM -> MMDVM LDU1", m_netLDU1, 9U * 25U); + Utils::dump(1U, "P25, HostPatch::writeNet_LDU1(), DVM -> MMDVM LDU1", m_netLDU1, 9U * 25U); // add the Low Speed Data p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); @@ -1504,7 +1504,7 @@ void HostPatch::writeNet_LDU2(bool toFNE) LogMessage(LOG_NET, "MMDVM " P25_LDU2_STR " audio"); if (m_debug) - Utils::dump(1U, "MMDVM -> DVM LDU2", m_netLDU2, 9U * 25U); + Utils::dump(1U, "P25, HostPatch::writeNet_LDU2(), MMDVM -> DVM LDU2", m_netLDU2, 9U * 25U); m_network->writeP25LDU2(m_netLC, lsd, m_netLDU2); @@ -1513,7 +1513,7 @@ void HostPatch::writeNet_LDU2(bool toFNE) } else { if (m_debug) - Utils::dump(1U, "DVM -> MMDVM LDU2", m_netLDU2, 9U * 25U); + Utils::dump(1U, "P25, HostPatch::writeNet_LDU2(), DVM -> MMDVM LDU2", m_netLDU2, 9U * 25U); // add the Low Speed Data p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); diff --git a/src/patch/mmdvm/P25Network.cpp b/src/patch/mmdvm/P25Network.cpp index 897cc3a7d..892cf6f14 100644 --- a/src/patch/mmdvm/P25Network.cpp +++ b/src/patch/mmdvm/P25Network.cpp @@ -169,7 +169,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 10U, ldu1 + 10U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $62 LDU1 Sent", buffer, 22U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $62 LDU1 Sent", buffer, 22U); bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); if (!ret) @@ -180,7 +180,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 1U, ldu1 + 26U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $63 LDU1 Sent", buffer, 14U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $63 LDU1 Sent", buffer, 14U); ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); if (!ret) @@ -193,7 +193,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 55U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $64 LDU1 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $64 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -208,7 +208,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 80U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $65 LDU1 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $65 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -223,7 +223,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 105U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $66 LDU1 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $66 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -234,7 +234,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 130U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $67 LDU1 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $67 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -245,7 +245,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 155U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $68 LDU1 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $68 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -256,7 +256,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 180U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $69 LDU1 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $69 LDU1 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -269,7 +269,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu1 + 204U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $6A LDU1 Sent", buffer, 16U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $6A LDU1 Sent", buffer, 16U); ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); if (!ret) @@ -277,7 +277,7 @@ bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, cons if (end) { if (m_debug) - Utils::dump(1U, "MMDVM Network END Sent", REC80, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network END Sent", REC80, 17U); ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); if (!ret) @@ -300,7 +300,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 10U, ldu2 + 10U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $6B LDU2 Sent", buffer, 22U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6B LDU2 Sent", buffer, 22U); bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); if (!ret) @@ -311,7 +311,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 1U, ldu2 + 26U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $6C LDU2 Sent", buffer, 14U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6C LDU2 Sent", buffer, 14U); ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); if (!ret) @@ -328,7 +328,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 55U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $6D LDU2 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6D LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -342,7 +342,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 80U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $6E LDU2 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6E LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -356,7 +356,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 105U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $6F LDU2 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6F LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -371,7 +371,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 130U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $70 LDU2 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $70 LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -382,7 +382,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 155U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $71 LDU2 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $71 LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -393,7 +393,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 180U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $72 LDU2 Sent", buffer, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $72 LDU2 Sent", buffer, 17U); ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); if (!ret) @@ -406,7 +406,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons ::memcpy(buffer + 5U, ldu2 + 204U, RAW_IMBE_LENGTH_BYTES); if (m_debug) - Utils::dump(1U, "MMDVM Network $73 LDU2 Sent", buffer, 16U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $73 LDU2 Sent", buffer, 16U); ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); if (!ret) @@ -414,7 +414,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons if (end) { if (m_debug) - Utils::dump(1U, "MMDVM Network END Sent", REC80, 17U); + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network END Sent", REC80, 17U); ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); if (!ret) @@ -429,7 +429,7 @@ bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, cons bool P25Network::writeTDU() { if (m_debug) - Utils::dump(1U, "MMDVM Network END Sent", REC80, 17U); + Utils::dump(1U, "P25, P25Network::writeTDU(), MMDVM Network END Sent", REC80, 17U); bool ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); if (!ret) @@ -456,7 +456,7 @@ void P25Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "MMDVM Network Data Received", buffer, length); + Utils::dump(1U, "P25Network::clock(), MMDVM Network Data Received", buffer, length); uint8_t c = length; m_buffer.addData(&c, 1U); diff --git a/src/patch/network/PeerNetwork.cpp b/src/patch/network/PeerNetwork.cpp index 568334925..4183ecc52 100644 --- a/src/patch/network/PeerNetwork.cpp +++ b/src/patch/network/PeerNetwork.cpp @@ -198,7 +198,7 @@ bool PeerNetwork::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "PeerNetowrk::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); @@ -277,7 +277,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "PeerNetwork::createP25_LDU1Message_Raw(), Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -352,7 +352,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "PeerNetwork::createP25_LDU2Message_Raw(), Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index f8cd7b34f..5235c4a88 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -230,7 +230,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco break; default: - Utils::dump("unknown opcode from the master", data, length); + Utils::dump("Unknown opcode from the master", data, length); break; } } @@ -296,7 +296,7 @@ bool PeerNetwork::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "PeerNetwork::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); From 3e8d3003415da26679120ffd95f47d1bdc02470c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 6 Aug 2025 14:29:39 -0400 Subject: [PATCH 080/133] add option for the FNE to directly log traffic denials to the system log; --- configs/fne-config.example.yml | 4 ++++ src/fne/network/FNENetwork.cpp | 4 ++++ src/fne/network/FNENetwork.h | 1 + src/fne/network/callhandler/TagDMRData.cpp | 24 +++++++++++++++++++-- src/fne/network/callhandler/TagNXDNData.cpp | 21 ++++++++++++++++-- src/fne/network/callhandler/TagP25Data.cpp | 21 ++++++++++++++++-- 6 files changed, 69 insertions(+), 6 deletions(-) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index adf03582b..7a2a87183 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -48,6 +48,10 @@ master: # Flag indicating whether or not verbose debug logging is enabled. debug: false + # Flag indicating whether or not denied traffic will be logged. + # (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.) + logDenials: false + # Maximum number of concurrent packet processing workers. workers: 16 diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index d3c8c18fc..ea76f7971 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -116,6 +116,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_disablePacketData(false), m_dumpPacketData(false), m_verbosePacketData(false), + m_logDenials(false), m_reportPeerPing(reportPeerPing), m_verbose(verbose) { @@ -183,6 +184,8 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_dumpPacketData = conf["dumpPacketData"].as(false); m_verbosePacketData = conf["verbosePacketData"].as(false); + m_logDenials = conf["logDenials"].as(false); + /* ** Drop Unit to Unit Peers */ @@ -212,6 +215,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no"); LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no"); LogInfo(" Reject Unknown RIDs: %s", m_rejectUnknownRID ? "yes" : "no"); + LogInfo(" Log Traffic Denials: %s", m_logDenials ? "yes" : "no"); LogInfo(" Mask Outbound Traffic Peer ID: %s", m_maskOutboundPeerID ? "yes" : "no"); if (m_maskOutboundPeerIDForNonPL) { LogInfo(" Mask Outbound Traffic Peer ID for Non-Peer Link: yes"); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index b5da2adff..5b157735a 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -635,6 +635,7 @@ namespace network bool m_dumpPacketData; bool m_verbosePacketData; + bool m_logDenials; bool m_reportPeerPing; bool m_verbose; diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 935ff97d8..df7e35360 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -793,6 +793,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -891,6 +894,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + return false; } } @@ -912,7 +918,8 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -939,6 +946,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -971,7 +981,8 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -994,6 +1005,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_SLOT ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -1015,6 +1029,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -1040,6 +1057,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index a7c0cc688..2bce80555 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -593,6 +593,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -629,6 +632,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -651,7 +657,8 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -679,6 +686,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -710,7 +720,8 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -732,6 +743,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -757,6 +771,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 7ead5451f..acdbe7190 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1127,6 +1127,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1195,6 +1198,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1217,7 +1223,8 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1313,6 +1320,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1344,7 +1354,8 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1366,6 +1377,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1391,6 +1405,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; From e3280f23808c9b703d19a133a2cd8550d21de41d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 6 Aug 2025 14:33:53 -0400 Subject: [PATCH 081/133] centralize string for illegal RID access; --- src/fne/network/FNENetwork.h | 1 + src/fne/network/callhandler/TagDMRData.cpp | 8 ++++---- src/fne/network/callhandler/TagNXDNData.cpp | 8 ++++---- src/fne/network/callhandler/TagP25Data.cpp | 8 ++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 5b157735a..5c520a242 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -82,6 +82,7 @@ namespace network #define INFLUXDB_ERRSTR_DISABLED_TALKGROUP "disabled talkgroup" #define INFLUXDB_ERRSTR_INV_SLOT "invalid slot for talkgroup" #define INFLUXDB_ERRSTR_RID_NOT_PERMITTED "RID not permitted for talkgroup" + #define INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS "illegal/unknown RID attempted access" // --------------------------------------------------------------------------- // Class Prototypes diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index df7e35360..6d106a6ee 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -912,14 +912,14 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(data.getSrcId())) .tag("dstId", std::to_string(data.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .field("slot", std::to_string(data.getSlotNo())) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } if (m_network->m_logDenials) - LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + LogWarning(LOG_NET, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -975,14 +975,14 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(data.getSrcId())) .tag("dstId", std::to_string(data.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .field("slot", std::to_string(data.getSlotNo())) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } if (m_network->m_logDenials) - LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + LogWarning(LOG_NET, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 2bce80555..83bc34c42 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -652,13 +652,13 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(lc.getSrcId())) .tag("dstId", std::to_string(lc.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } if (m_network->m_logDenials) - LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + LogWarning(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -715,13 +715,13 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(lc.getSrcId())) .tag("dstId", std::to_string(lc.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } if (m_network->m_logDenials) - LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + LogWarning(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index acdbe7190..6552867de 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1218,13 +1218,13 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(control.getSrcId())) .tag("dstId", std::to_string(control.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } if (m_network->m_logDenials) - LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + LogWarning(LOG_NET, "P25, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1349,13 +1349,13 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(control.getSrcId())) .tag("dstId", std::to_string(control.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } if (m_network->m_logDenials) - LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + LogWarning(LOG_NET, "P25, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); From db7054b265a7ebb1a9ab5f24dac40c1f2399e953 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 6 Aug 2025 14:35:40 -0400 Subject: [PATCH 082/133] log total voice frame errors for TIA/DFSI mode; --- src/host/modem/ModemV24.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 392f492c3..17b195ea6 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1474,6 +1474,10 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) Utils::dump(1U, "ModemV24::convertToAirTIA(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } + if (voice.getTotalErrors() > 0U) { + LogWarning(LOG_MODEM, "TIA/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + } + dataOffs += voice.getLength(); DFSIFrameType::E frameType = voice.getFrameType(); From 5dbe77e7057f12e006508363639f5106b04fc74b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 6 Aug 2025 21:25:23 -0400 Subject: [PATCH 083/133] minor PDU refactoring on when network PDU data is sent; correct issue with accepting a conventional data reg; --- src/host/p25/packet/Data.cpp | 165 ++++++++++++++++++++++------------- src/host/p25/packet/Data.h | 13 ++- 2 files changed, 115 insertions(+), 63 deletions(-) diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index 5c4230561..4e70e8b34 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017,2018 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -157,13 +157,6 @@ bool Data::process(uint8_t* data, uint32_t len) m_p25->m_rfState = m_prevRfState; return false; } - - // only send data blocks across the network, if we're not an AMBT, - // an RSP or a registration service - if ((m_rfDataHeader.getFormat() != PDUFormatType::AMBT) && - (m_rfDataHeader.getSAP() != PDUSAP::CONV_DATA_REG)) { - writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, false); - } } if (m_p25->m_rfState == RS_RF_DATA) { @@ -193,7 +186,6 @@ bool Data::process(uint8_t* data, uint32_t len) } m_rfExtendedAddress = true; - writeNetwork(1U, buffer, P25_PDU_FEC_LENGTH_BYTES, false); offset += P25_PDU_FEC_LENGTH_BITS; m_rfPDUCount++; @@ -262,16 +254,6 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfData[i].getData(m_rfPduUserData + dataOffset); dataOffset += (m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; m_rfPduUserDataLength = dataOffset; - - // only send data blocks across the network, if we're not an AMBT, - // an RSP or a registration service - if ((m_rfDataHeader.getFormat() != PDUFormatType::AMBT) && - (m_rfDataHeader.getFormat() != PDUFormatType::RSP) && - (m_rfDataHeader.getSAP() != PDUSAP::CONV_DATA_REG)) { - uint32_t networkBlock = m_rfDataBlockCnt + 1U; - writeNetwork(networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, m_rfData[i].getLastBlock()); - } - m_rfDataBlockCnt++; } else { @@ -422,6 +404,7 @@ bool Data::process(uint8_t* data, uint32_t len) } } + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on } break; @@ -452,6 +435,8 @@ bool Data::process(uint8_t* data, uint32_t len) LogMessage(LOG_RF, P25_PDU_STR ", KMM (Key Management Message), blocksToFollow = %u", m_rfDataHeader.getBlocksToFollow()); } + + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); } break; case PDUSAP::TRUNK_CTRL: @@ -465,10 +450,12 @@ bool Data::process(uint8_t* data, uint32_t len) } break; default: + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); + // only repeat the PDU locally if the packet isn't for the FNE if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { ::ActivityLog("P25", true, "RF data transmission from %u to %u, %u blocks", srcId, dstId, m_rfDataHeader.getBlocksToFollow()); - + if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", repeating PDU, llId = %u", (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); } @@ -895,6 +882,7 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, dataBlock.setFormat(dataHeader); dataBlock.setSerialNo(i); dataBlock.setData(pduUserData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", @@ -914,50 +902,100 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, writeRF_PDU(data, bitLength, imm); } -/* Updates the processor by the passed number of milliseconds. */ +/* Helper to write user data as a P25 PDU packet. */ -void Data::clock(uint32_t ms) +void Data::writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData) { - // clock all the conventional registration timers - std::vector connToClear = std::vector(); - for (auto entry : m_convRegTimerTable) { - uint32_t llId = entry.first; - - m_convRegTimerTable[llId].clock(ms); - if (m_convRegTimerTable[llId].isRunning() && m_convRegTimerTable[llId].hasExpired()) { - connToClear.push_back(llId); - } - } + assert(pduUserData != nullptr); - if (connToClear.size() > 0) { - m_p25->writeRF_Preamble(); + uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (dataHeader.getPadLength() > 0U) + bitLength += (dataHeader.getPadLength() * 8U); + + uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", + dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), + dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), + dataHeader.getHeaderOffset(), bitLength, dataHeader.getLLId()); } - // handle PDU conventional connection registration - for (uint32_t llId : connToClear) { - uint32_t ipAddr = m_convRegQueueTable[llId]; + // generate the PDU header and 1/2 rate Trellis + dataHeader.encode(block); + writeNetwork(0U, block, P25_PDU_FEC_LENGTH_BYTES, false); - if (!acl::AccessControl::validateSrcId(llId)) { - LogWarning(LOG_RF, P25_PDU_STR ", DENY (Registration Response Deny), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); - writeRF_PDU_Reg_Response(PDURegType::DENY, llId, ipAddr); - } - else { - if (!hasLLIdFNEReg(llId)) { - // update dynamic FNE registration table entry - m_fneRegTable[llId] = ipAddr; + if (blocksToFollow > 0U) { + uint32_t dataOffset = 0U; + uint32_t packetLength = dataHeader.getPDULength(); + uint32_t netDataBlockCnt = 1U; + + // generate the second PDU header + if ((dataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { + dataHeader.encodeExtAddr(pduUserData, true); + + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + dataHeader.encodeExtAddr(block); + writeNetwork(1U, block, P25_PDU_FEC_LENGTH_BYTES, false); + netDataBlockCnt++; + + bitLength += P25_PDU_FEC_LENGTH_BITS; + + dataOffset += P25_PDU_HEADER_LENGTH_BYTES; + + blocksToFollow--; + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", + dataHeader.getEXSAP(), dataHeader.getSrcLLId()); } + } + + // are we processing extended address data from the first block? + if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { + dataHeader.encodeExtAddr(pduUserData); if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ACCEPT (Registration Response Accept), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + LogMessage(LOG_NET, P25_PDU_STR ", OSP, sap = $%02X, srcLlId = %u", + dataHeader.getEXSAP(), dataHeader.getSrcLLId()); } + } - writeRF_PDU_Reg_Response(PDURegType::ACCEPT, llId, ipAddr); + if (dataHeader.getFormat() != PDUFormatType::AMBT) { + edac::CRC::addCRC32(pduUserData, packetLength); } - m_convRegQueueTable.erase(llId); - m_convRegTimerTable.erase(llId); + // generate the PDU data + for (uint32_t i = 0U; i < blocksToFollow; i++) { + DataBlock dataBlock = DataBlock(); + dataBlock.setFormat(dataHeader); + dataBlock.setSerialNo(i); + dataBlock.setData(pduUserData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(), + dataBlock.getLastBlock()); + } + + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + dataBlock.encode(block); + writeNetwork(netDataBlockCnt, block, P25_PDU_FEC_LENGTH_BYTES, dataBlock.getLastBlock()); + netDataBlockCnt++; + + dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } } +} +/* Updates the processor by the passed number of milliseconds. */ + +void Data::clock(uint32_t ms) +{ if (m_p25->m_sndcpSupport) { // clock all the SNDCP ready timers std::vector sndcpReadyExpired = std::vector(); @@ -1128,8 +1166,6 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_netPduUserData(nullptr), m_netPduUserDataLength(0U), m_fneRegTable(), - m_convRegQueueTable(), - m_convRegTimerTable(), m_sndcpStateTable(), m_sndcpReadyTimers(), m_sndcpStandbyTimers(), @@ -1156,9 +1192,6 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_fneRegTable.clear(); - m_convRegQueueTable.clear(); - m_convRegTimerTable.clear(); - m_sndcpStateTable.clear(); m_sndcpReadyTimers.clear(); m_sndcpStandbyTimers.clear(); @@ -1195,13 +1228,22 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) LogMessage(LOG_RF, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); } - m_convRegQueueTable[llId] = ipAddr; + if (!acl::AccessControl::validateSrcId(llId)) { + LogWarning(LOG_RF, P25_PDU_STR ", DENY (Registration Response Deny), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + writeRF_PDU_Reg_Response(PDURegType::DENY, llId, ipAddr); + } + else { + if (!hasLLIdFNEReg(llId)) { + // update dynamic FNE registration table entry + m_fneRegTable[llId] = ipAddr; + } - m_convRegTimerTable[llId] = Timer(1000U, 0U, CONV_REG_WAIT_TIMEOUT); - m_convRegTimerTable[llId].start(); + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", ACCEPT (Registration Response Accept), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + } - // acknowledge - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId); + writeRF_PDU_Reg_Response(PDURegType::ACCEPT, llId, ipAddr); + } } break; case PDURegType::DISCONNECT: @@ -1620,6 +1662,7 @@ void Data::writeRF_PDU_Buffered() m_rfData[i].setFormat(m_rfDataHeader); m_rfData[i].setSerialNo(i); m_rfData[i].setData(m_rfPduUserData + dataOffset); + m_rfData[i].setLastBlock((i + 1U) == blocksToFollow); if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", @@ -1651,9 +1694,11 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA DataHeader rspHeader = DataHeader(); rspHeader.setFormat(PDUFormatType::CONFIRMED); + rspHeader.setMFId(m_rfDataHeader.getMFId()); rspHeader.setAckNeeded(true); rspHeader.setOutbound(true); rspHeader.setSAP(PDUSAP::CONV_DATA_REG); + rspHeader.setSynchronize(true); rspHeader.setLLId(llId); rspHeader.setBlocksToFollow(1U); @@ -1668,6 +1713,8 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA pduUserData[11U] = (ipAddr >> 0) & 0xFFU; } + Utils::dump(1U, "Data::writeRF_PDU_Reg_Response() pduUserData", pduUserData, 12U); + rspHeader.calculateLength(12U); writeRF_PDU_User(rspHeader, false, pduUserData); } diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 7ff8fc58b..671a782c8 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -88,6 +88,13 @@ namespace p25 * @param imm Flag indicating the PDU should be written to the immediate queue. */ void writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool imm = false); + /** + * @brief Helper to write user data as a P25 PDU packet. + * @param dataHeader Instance of a PDU data header. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param pduUserData Buffer containing user data to transmit. + */ + void writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData); /** * @brief Updates the processor by the passed number of milliseconds. @@ -147,9 +154,6 @@ namespace p25 std::unordered_map m_fneRegTable; - std::unordered_map m_convRegQueueTable; - std::unordered_map m_convRegTimerTable; - std::unordered_map m_sndcpStateTable; std::unordered_map m_sndcpReadyTimers; std::unordered_map m_sndcpStandbyTimers; @@ -221,6 +225,7 @@ namespace p25 * @param regType Registration Response. * @param llId Logical Link ID. * @param ipAddr + * @param mfId */ void writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipAddr); /** From ba356ee908ae05a6e030de69548fe77a49951f79 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 6 Aug 2025 23:54:52 -0400 Subject: [PATCH 084/133] disable allowExplicitSourceId by default (not all subscribers support this); correct some bad handling of LC data; --- configs/config.example.yml | 2 +- src/common/p25/P25Defines.h | 1 + src/common/p25/lc/LC.cpp | 34 +++++++++++++++++++++++++++++++--- src/host/p25/Control.cpp | 4 ++-- src/host/p25/packet/Voice.cpp | 5 +++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index fc11b8c1f..19c2e903e 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -286,7 +286,7 @@ protocols: # Flag indicating that unit-to-unit availiability checks should be performed for a private call. unitToUnitAvailCheck: false # Flag indicating explicit source ID support is enabled. - allowExplicitSourceId: true + allowExplicitSourceId: false # Flag indicating whether or not the host will respond to SNDCP data requests. sndcpSupport: false # BER/Error threshold for silencing voice packets. (0 or 1233 disables) diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index 6c11b9493..1b7ff9cb1 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -686,6 +686,7 @@ namespace p25 TEL_INT_VCH_USER = 0x06U, //! TEL INT VCH USER - Telephone Interconnect Voice Channel User / MOT GPS DATA - Motorola In-Band GPS Data TEL_INT_ANS_RQST = 0x07U, //! TEL INT ANS RQST - Telephone Interconnect Answer Request EXPLICIT_SOURCE_ID = 0x09U, //! EXPLICIT SOURCE ID - Explicit Source ID + PRIVATE_EXT = 0x0AU, //! UU VCH USER EXT - Unit-to-Unit Voice Channel User Extended CALL_TERM = 0x0FU, //! CALL TERM - Call Termination or Cancellation IDEN_UP = 0x18U, //! IDEN UP - Channel Identifier Update SYS_SRV_BCAST = 0x20U, //! SYS SRV BCAST - System Service Broadcast diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index ca3de7a8b..7ef0509b0 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -611,7 +611,11 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) m_protect = (rs[0U] & 0x80U) == 0x80U; // Protect Flag m_lco = rs[0U] & 0x3FU; // LCO - m_mfId = rs[1U]; // Mfg Id. + bool sf = (rs[0U] & 0x40U) == 0x40U; // Implicit/Explicit Operation + if (sf) + m_mfId = MFG_STANDARD; + else + m_mfId = rs[1U]; // Mfg Id. if (rawOnly) return true; @@ -692,6 +696,18 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) m_sysId = (uint32_t)((rsValue >> 24) & 0xFFFU); // System ID m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address break; + case LCO::PRIVATE_EXT: + m_explicitId = (rs[1U] & 0x01U) == 0x01U; // Explicit Source ID Flag + m_group = false; + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + if (!m_encryptOverride) { + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + } + m_priority = (rs[2U] & 0x07U); // Priority + m_explicitId = (rs[3U] & 0x01U) == 0x01U; // Explicit Source ID Flag + m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + break; default: LogError(LOG_P25, "LC::decodeLC(), unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); return false; @@ -721,7 +737,8 @@ void LC::encodeLC(uint8_t* rs) (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag (m_priority & 0x07U); // Priority - rsValue = (rsValue << 24) + m_dstId; // Talkgroup Address + rsValue = (rsValue << 8) + (m_explicitId ? 0x01U : 0x00U); // Explicit Source ID Flag + rsValue = (rsValue << 16) + m_dstId; // Talkgroup Address rsValue = (rsValue << 24) + m_srcId; // Source Radio Address break; case LCO::GROUP_UPDT: @@ -752,10 +769,21 @@ void LC::encodeLC(uint8_t* rs) rsValue = (rsValue << 24) + m_srcId; // Source/Target Radio Address break; case LCO::EXPLICIT_SOURCE_ID: - rsValue = m_netId; // Network ID + rs[0U] |= 0x40U; // Implicit Operation + rsValue = (rsValue << 8) + m_netId; // Network ID rsValue = (rsValue << 12) + (m_sysId & 0xFFFU); // System ID rsValue = (rsValue << 24) + m_srcId; // Source Radio Address break; + case LCO::PRIVATE_EXT: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = (m_explicitId ? 0x01U : 0x00U); // Explicit Source ID Flag + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 24) + m_dstId; // Target Radio Address + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + break; case LCO::RFSS_STS_BCAST: rs[0U] |= 0x40U; // Implicit Operation rsValue = m_siteData.lra(); // Location Registration Area diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 2d6bb5c58..aa040154d 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -75,7 +75,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_ackTSBKRequests(true), m_disableNetworkGrant(false), m_disableNetworkHDU(false), - m_allowExplicitSourceId(true), + m_allowExplicitSourceId(false), m_convNetGrantDemand(false), m_sndcpSupport(false), m_ignoreAffiliationCheck(false), @@ -338,7 +338,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_ccNotifyActiveTG = control["notifyActiveTG"].as(true); - m_allowExplicitSourceId = p25Protocol["allowExplicitSourceId"].as(true); + m_allowExplicitSourceId = p25Protocol["allowExplicitSourceId"].as(false); m_convNetGrantDemand = p25Protocol["convNetGrantDemand"].as(false); uint32_t ccBcstInterval = p25Protocol["control"]["interval"].as(300U); diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index efc04c053..fa9d38d96 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1977,6 +1977,11 @@ void Voice::writeNet_LDU1() if (m_netLC.getLCO() == LCO::GROUP) { m_netLC.setExplicitId(true); } + + if (m_netLC.getLCO() == LCO::PRIVATE) { + m_netLC.setLCO(LCO::PRIVATE_EXT); + m_netLC.setExplicitId(true); + } } } } From cbcf539a5b07921fda6d1f00132279bf837a884c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 00:05:57 -0400 Subject: [PATCH 085/133] log sysId and netId info for call start and end on the FNE log; --- src/fne/network/callhandler/TagP25Data.cpp | 18 +++++++++++------- src/host/p25/packet/Voice.cpp | 4 ---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 6552867de..d14c30bda 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -85,6 +85,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint8_t MFId = data[15U]; + uint32_t sysId = (data[11U] << 8) | (data[12U] << 0); + uint32_t netId = GET_UINT24(data, 16U); + uint8_t lsd1 = data[20U]; uint8_t lsd2 = data[21U]; @@ -198,8 +201,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_status.end()) { if (grantDemand) { - LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } else { @@ -216,8 +219,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -264,8 +267,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_callInProgress = false; } - LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } } @@ -293,7 +296,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index fa9d38d96..bd788e78e 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1985,10 +1985,6 @@ void Voice::writeNet_LDU1() } } } - else { - netId = lc::LC::getSiteData().netId(); - sysId = lc::LC::getSiteData().sysId(); - } // are we swapping the LC out for the RFSS_STS_BCAST or LC_GROUP_UPDT? m_pktLDU1Count++; From 996bd15e382f5ad19456f64c39803054f0d75c0e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 10:01:00 -0400 Subject: [PATCH 086/133] add setting netId and sysId to dvmbridge and dvmpatch for future use; --- configs/bridge-config.example.yml | 5 +++++ configs/patch-config.example.yml | 5 +++++ src/bridge/HostBridge.cpp | 16 ++++++++++++++++ src/bridge/HostBridge.h | 3 +++ src/patch/HostPatch.cpp | 19 +++++++++++++++++++ src/patch/HostPatch.h | 3 +++ 6 files changed, 51 insertions(+) diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 0576a6cfe..24cb43c5d 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -125,6 +125,11 @@ system: # Textual Name identity: BRIDGE + # Network ID (WACN). + netId: BB800 + # System ID. + sysId: 001 + # PCM audio gain for received (from digital network) audio frames. # - This is used to apply gain to the decoded IMBE/AMBE audio, post-decoding. rxAudioGain: 1.0 diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index e93f9eae7..39954a1ff 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -102,6 +102,11 @@ system: # Textual Name identity: PATCH + # Network ID (WACN). + netId: BB800 + # System ID. + sysId: 001 + # Digital mode (1 - DMR, 2 - P25). digiMode: 1 diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 9e94e828e..1926bed7d 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -225,6 +225,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_netLDU2(nullptr), m_p25SeqNo(0U), m_p25N(0U), + m_netId(0xBB800U), + m_sysId(1U), m_analogN(0U), m_audioDetect(false), m_trafficFromUDP(false), @@ -851,6 +853,15 @@ bool HostBridge::readParams() m_identity = systemConf["identity"].as(); + m_netId = (uint32_t)::strtoul(systemConf["netId"].as("BB800").c_str(), NULL, 16); + m_netId = p25::P25Utils::netId(m_netId); + if (m_netId == 0xBEE00) { + ::fatal("error 4\n"); + } + + m_sysId = (uint32_t)::strtoul(systemConf["sysId"].as("001").c_str(), NULL, 16); + m_sysId = p25::P25Utils::sysId(m_sysId); + m_rxAudioGain = systemConf["rxAudioGain"].as(1.0f); m_vocoderDecoderAudioGain = systemConf["vocoderDecoderAudioGain"].as(3.0f); m_vocoderDecoderAutoGain = systemConf["vocoderDecoderAutoGain"].as(false); @@ -914,6 +925,8 @@ bool HostBridge::readParams() txModeStr = "Analog"; LogInfo("General Parameters"); + LogInfo(" System Id: $%03X", m_sysId); + LogInfo(" P25 Network Id: $%05X", m_netId); LogInfo(" Rx Audio Gain: %.1f", m_rxAudioGain); LogInfo(" Vocoder Decoder Audio Gain: %.1f", m_vocoderDecoderAudioGain); LogInfo(" Vocoder Decoder Auto Gain: %s", m_vocoderDecoderAutoGain ? "yes" : "no"); @@ -2450,6 +2463,9 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ lc.setAlgId(m_tekAlgoId); lc.setKId(m_tekKeyId); + lc.setSysId(m_sysId); + lc.setNetId(m_netId); + uint8_t mi[MI_LENGTH_BYTES]; m_p25Crypto->getMI(mi); lc.setMI(mi); diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 68edba2b0..72e3c308e 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -235,6 +235,9 @@ class HOST_SW_API HostBridge { uint32_t m_p25SeqNo; uint8_t m_p25N; + uint32_t m_netId; + uint32_t m_sysId; + uint8_t m_analogN; bool m_audioDetect; diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index e0573c7c5..d166e5af6 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -97,6 +97,8 @@ HostPatch::HostPatch(const std::string& confFile) : m_requestedDstTek(false), m_p25SrcCrypto(nullptr), m_p25DstCrypto(nullptr), + m_netId(0xBB800U), + m_sysId(1U), m_running(false), m_trace(false), m_debug(false) @@ -278,6 +280,15 @@ bool HostPatch::readParams() yaml::Node systemConf = m_conf["system"]; m_identity = systemConf["identity"].as(); + + m_netId = (uint32_t)::strtoul(systemConf["netId"].as("BB800").c_str(), NULL, 16); + m_netId = p25::P25Utils::netId(m_netId); + if (m_netId == 0xBEE00) { + ::fatal("error 4\n"); + } + + m_sysId = (uint32_t)::strtoul(systemConf["sysId"].as("001").c_str(), NULL, 16); + m_sysId = p25::P25Utils::sysId(m_sysId); m_digiMode = (uint8_t)systemConf["digiMode"].as(1U); if (m_digiMode < TX_MODE_DMR) @@ -298,6 +309,8 @@ bool HostPatch::readParams() m_debug = systemConf["debug"].as(false); LogInfo("General Parameters"); + LogInfo(" System Id: $%03X", m_sysId); + LogInfo(" P25 Network Id: $%05X", m_netId); LogInfo(" Digital Mode: %s", m_digiMode == TX_MODE_DMR ? "DMR" : "P25"); LogInfo(" Grant Demands: %s", m_grantDemand ? "yes" : "no"); LogInfo(" MMDVM P25 Reflector Patch: %s", m_mmdvmP25Reflector ? "yes" : "no"); @@ -1096,6 +1109,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setSrcId(srcId); control.setDstId(actualDstId); + control.setNetId(m_netId); + control.setSysId(m_sysId); + // if this is the beginning of a call and we have a valid HDU frame, extract the algo ID if (frameType == FrameType::HDU_VALID) { uint8_t algoId = buffer[181U]; @@ -1196,6 +1212,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setSrcId(srcId); control.setDstId(actualDstId); + control.setNetId(m_netId); + control.setSysId(m_sysId); + // set the algo ID and key ID if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { control.setAlgId(tekAlgoId); diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h index c182dd574..2bc363741 100644 --- a/src/patch/HostPatch.h +++ b/src/patch/HostPatch.h @@ -109,6 +109,9 @@ class HOST_SW_API HostPatch { p25::crypto::P25Crypto* m_p25SrcCrypto; p25::crypto::P25Crypto* m_p25DstCrypto; + uint32_t m_netId; + uint32_t m_sysId; + bool m_running; bool m_trace; bool m_debug; From 8e1f34bdc0bbb2fe76079d292fa312839adbb229 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 11:15:01 -0400 Subject: [PATCH 087/133] reorganize and expose decodeLC/encodeLC from LC; correct typo in TDULC headers; add explicit source id TDULC; add LC_GROUP explicit ID flag; --- src/bridge/HostBridge.cpp | 46 ++-- src/common/p25/lc/LC.cpp | 242 +++++++++--------- src/common/p25/lc/LC.h | 28 +- src/common/p25/lc/TDULC.cpp | 8 + src/common/p25/lc/TDULC.h | 14 + src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h | 6 +- src/common/p25/lc/tdulc/LC_CALL_TERM.h | 6 +- src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h | 6 +- .../p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp | 69 +++++ .../p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h | 59 +++++ src/common/p25/lc/tdulc/LC_FAILSOFT.h | 6 +- src/common/p25/lc/tdulc/LC_GROUP.cpp | 6 +- src/common/p25/lc/tdulc/LC_GROUP.h | 8 +- src/common/p25/lc/tdulc/LC_GROUP_UPDT.h | 6 +- src/common/p25/lc/tdulc/LC_IDEN_UP.h | 6 +- src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h | 6 +- src/common/p25/lc/tdulc/LC_PRIVATE.h | 6 +- src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h | 6 +- src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h | 6 +- src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h | 6 +- src/common/p25/lc/tdulc/TDULCFactory.cpp | 4 +- src/common/p25/lc/tdulc/TDULCFactory.h | 3 +- src/patch/HostPatch.cpp | 52 ++-- 23 files changed, 382 insertions(+), 223 deletions(-) create mode 100644 src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp create mode 100644 src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 1926bed7d..c28b3d25b 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -172,11 +172,11 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpJitter(200U), m_udpSilenceDuringHang(true), m_lastUdpFrameTime(0U), - m_tekAlgoId(p25::defines::ALGO_UNENCRYPT), + m_tekAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekKeyId(0U), m_requestedTek(false), m_p25Crypto(nullptr), - m_srcId(p25::defines::WUID_FNE), + m_srcId(P25DEF::WUID_FNE), m_srcIdOverride(0U), m_overrideSrcIdFromMDC(false), m_overrideSrcIdFromUDP(false), @@ -225,8 +225,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_netLDU2(nullptr), m_p25SeqNo(0U), m_p25N(0U), - m_netId(0xBB800U), - m_sysId(1U), + m_netId(P25DEF::WACN_STD_DEFAULT), + m_sysId(P25DEF::SID_STD_DEFAULT), m_analogN(0U), m_audioDetect(false), m_trafficFromUDP(false), @@ -234,7 +234,7 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpDstId(0U), m_callInProgress(false), m_ignoreCall(false), - m_callAlgoId(p25::defines::ALGO_UNENCRYPT), + m_callAlgoId(P25DEF::ALGO_UNENCRYPT), m_rxStartTime(0U), m_rxStreamId(0U), m_txStreamId(0U), @@ -1002,31 +1002,31 @@ bool HostBridge::createNetwork() m_tekKeyId = (uint32_t)::strtoul(tekConf["tekKeyId"].as("0").c_str(), NULL, 16); if (tekEnable && m_tekKeyId > 0U) { if (tekAlgo == TEK_AES) - m_tekAlgoId = p25::defines::ALGO_AES_256; + m_tekAlgoId = P25DEF::ALGO_AES_256; else if (tekAlgo == TEK_ARC4) - m_tekAlgoId = p25::defines::ALGO_ARC4; + m_tekAlgoId = P25DEF::ALGO_ARC4; else { ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); - m_tekAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekKeyId = 0U; } } // ensure encryption is currently disabled for DMR (its not supported) - if (m_txMode == TX_MODE_DMR && m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U) { + if (m_txMode == TX_MODE_DMR && m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U) { ::LogError(LOG_HOST, "Encryption is not supported for DMR. Disabling."); - m_tekAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekKeyId = 0U; } // ensure encryption is currently disabled for analog (its not supported) - if (m_txMode == TX_MODE_ANALOG && m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U) { + if (m_txMode == TX_MODE_ANALOG && m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U) { ::LogError(LOG_HOST, "Encryption is not supported for Analog. Disabling."); - m_tekAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekKeyId = 0U; } - m_srcId = (uint32_t)networkConf["sourceId"].as(p25::defines::WUID_FNE); + m_srcId = (uint32_t)networkConf["sourceId"].as(P25DEF::WUID_FNE); m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as(false); m_overrideSrcIdFromUDP = networkConf["overrideSourceIdFromUDP"].as(false); m_resetCallForSourceIdChange = networkConf["resetCallForSourceIdChange"].as(false); @@ -2172,12 +2172,12 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI // Utils::dump(1U, "HostBridge::decodeP25AudioFrame(), IMBE", imbe, RAW_IMBE_LENGTH_BYTES); - if (m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { + if (m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { switch (m_tekAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25Crypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25Crypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -2346,7 +2346,7 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ // Utils::dump(1U, "HostBridge::encodeP25AudioFrame(), Encoded IMBE", imbe, RAW_IMBE_LENGTH_BYTES); - if (m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { + if (m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { // generate initial MI for the HDU if (m_p25N == 0U && !m_p25Crypto->hasValidKeystream()) { if (!m_p25Crypto->hasValidMI()) { @@ -2357,10 +2357,10 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ // perform crypto switch (m_tekAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25Crypto->cryptAES_IMBE(imbe, (m_p25N < 9U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25Crypto->cryptARC4_IMBE(imbe, (m_p25N < 9U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -2893,7 +2893,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) case TX_MODE_P25: { p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(dstId); lc.setSrcId(srcId); @@ -3063,7 +3063,7 @@ void* HostBridge::threadAudioProcess(void* arg) case TX_MODE_P25: { p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(dstId); lc.setSrcId(srcId); @@ -3246,7 +3246,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) case TX_MODE_P25: { p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(bridge->m_udpDstId); lc.setSrcId(bridge->m_udpSrcId); @@ -3345,7 +3345,7 @@ void* HostBridge::threadNetworkProcess(void* arg) } if (bridge->m_network->getStatus() == NET_STAT_RUNNING) { - if (bridge->m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && bridge->m_tekKeyId > 0U) { + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && bridge->m_tekKeyId > 0U) { if (bridge->m_p25Crypto->getTEKLength() == 0U && !bridge->m_requestedTek) { bridge->m_requestedTek = true; LogMessage(LOG_HOST, "Bridge encryption enabled, requesting TEK from network."); diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 7ef0509b0..1410a5c85 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -473,128 +473,12 @@ bool LC::isStandardMFId() const return false; } -/* -** Encryption data -*/ - -/* Sets the encryption message indicator. */ - -void LC::setMI(const uint8_t* mi) -{ - assert(mi != nullptr); - - ::memcpy(m_mi, mi, MI_LENGTH_BYTES); -} - -/* Gets the encryption message indicator. */ - -void LC::getMI(uint8_t* mi) const -{ - assert(mi != nullptr); - - ::memcpy(mi, m_mi, MI_LENGTH_BYTES); -} - -/* -** User Alias data -*/ - -/* Gets the user alias. */ - -std::string LC::getUserAlias() const -{ - std::string alias; - if (m_gotUserAlias) { - for (uint32_t i = 0; i < HARRIS_USER_ALIAS_LENGTH_BYTES; i++) - alias[i] = m_userAlias[i]; - } - - return alias; -} - -// --------------------------------------------------------------------------- -// Private Class Members -// --------------------------------------------------------------------------- - -/* Internal helper to copy the the class. */ - -void LC::copy(const LC& data) -{ - m_lco = data.m_lco; - - m_protect = data.m_protect; - m_mfId = data.m_mfId; - - m_srcId = data.m_srcId; - m_dstId = data.m_dstId; - - m_grpVchNo = data.m_grpVchNo; - - m_grpVchNoB = data.m_grpVchNoB; - m_dstIdB = data.m_dstIdB; - - m_explicitId = data.m_explicitId; - - m_netId = data.m_netId; - m_sysId = data.m_sysId; - - m_emergency = data.m_emergency; - m_encrypted = data.m_encrypted; - m_priority = data.m_priority; - - m_group = data.m_group; - - m_callTimer = data.m_callTimer; - - m_rsValue = data.m_rsValue; - - m_algId = data.m_algId; - if (m_algId != ALGO_UNENCRYPT) { - delete[] m_mi; - - m_mi = new uint8_t[MI_LENGTH_BYTES]; - ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); - - m_kId = data.m_kId; - if (!m_encrypted) { - m_encryptOverride = true; - m_encrypted = true; - } - } - else { - delete[] m_mi; - - m_mi = new uint8_t[MI_LENGTH_BYTES]; - ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); - - m_kId = 0x0000U; - if (m_encrypted) { - m_encryptOverride = true; - m_encrypted = false; - } - } - - if (data.m_gotUserAlias && data.m_userAlias != nullptr) { - delete[] m_userAlias; - - m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; - ::memcpy(m_userAlias, data.m_userAlias, HARRIS_USER_ALIAS_LENGTH_BYTES); - m_gotUserAlias = data.m_gotUserAlias; - } else { - delete[] m_userAlias; - - m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; - ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); - m_gotUserAlias = false; - } - - m_siteData = data.m_siteData; -} - /* Decode link control. */ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) { + assert(rs != nullptr); + ulong64_t rsValue = 0U; // combine bytes into ulong64_t (8 byte) value @@ -725,6 +609,8 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) void LC::encodeLC(uint8_t* rs) { + assert(rs != nullptr); + ulong64_t rsValue = 0U; rs[0U] = m_lco; // LCO @@ -823,6 +709,124 @@ void LC::encodeLC(uint8_t* rs) */ } +/* +** Encryption data +*/ + +/* Sets the encryption message indicator. */ + +void LC::setMI(const uint8_t* mi) +{ + assert(mi != nullptr); + + ::memcpy(m_mi, mi, MI_LENGTH_BYTES); +} + +/* Gets the encryption message indicator. */ + +void LC::getMI(uint8_t* mi) const +{ + assert(mi != nullptr); + + ::memcpy(mi, m_mi, MI_LENGTH_BYTES); +} + +/* +** User Alias data +*/ + +/* Gets the user alias. */ + +std::string LC::getUserAlias() const +{ + std::string alias; + if (m_gotUserAlias) { + for (uint32_t i = 0; i < HARRIS_USER_ALIAS_LENGTH_BYTES; i++) + alias[i] = m_userAlias[i]; + } + + return alias; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void LC::copy(const LC& data) +{ + m_lco = data.m_lco; + + m_protect = data.m_protect; + m_mfId = data.m_mfId; + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + + m_grpVchNo = data.m_grpVchNo; + + m_grpVchNoB = data.m_grpVchNoB; + m_dstIdB = data.m_dstIdB; + + m_explicitId = data.m_explicitId; + + m_netId = data.m_netId; + m_sysId = data.m_sysId; + + m_emergency = data.m_emergency; + m_encrypted = data.m_encrypted; + m_priority = data.m_priority; + + m_group = data.m_group; + + m_callTimer = data.m_callTimer; + + m_rsValue = data.m_rsValue; + + m_algId = data.m_algId; + if (m_algId != ALGO_UNENCRYPT) { + delete[] m_mi; + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); + + m_kId = data.m_kId; + if (!m_encrypted) { + m_encryptOverride = true; + m_encrypted = true; + } + } + else { + delete[] m_mi; + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + + m_kId = 0x0000U; + if (m_encrypted) { + m_encryptOverride = true; + m_encrypted = false; + } + } + + if (data.m_gotUserAlias && data.m_userAlias != nullptr) { + delete[] m_userAlias; + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memcpy(m_userAlias, data.m_userAlias, HARRIS_USER_ALIAS_LENGTH_BYTES); + m_gotUserAlias = data.m_gotUserAlias; + } else { + delete[] m_userAlias; + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + m_gotUserAlias = false; + } + + m_siteData = data.m_siteData; +} + /* Decode LDU hamming FEC. */ void LC::decodeLDUHamming(const uint8_t* data, uint8_t* raw) diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index efcb7bf0b..195319dfa 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -115,6 +115,19 @@ namespace p25 */ bool isStandardMFId() const; + /** + * @brief Decode link control. + * @param[in] rs Buffer containing the decoded Reed-Solomon LC data. + * @param rawOnly Flag indicating only the raw bytes of the LC should be decoded. + * @returns bool True, if LC is decoded, otherwise false. + */ + bool decodeLC(const uint8_t* rs, bool rawOnly = false); + /** + * @brief Encode link control. + * @param[out] rs Buffer to encode LC data. + */ + void encodeLC(uint8_t* rs); + /** @name Encryption data */ /** * @brief Sets the encryption message indicator. @@ -262,19 +275,6 @@ namespace p25 */ void copy(const LC& data); - /** - * @brief Decode link control. - * @param[in] rs Buffer containing the decoded Reed-Solomon LC data. - * @param rawOnly Flag indicating only the raw bytes of the LC should be decoded. - * @returns bool True, if LC is decoded, otherwise false. - */ - bool decodeLC(const uint8_t* rs, bool rawOnly = false); - /** - * @brief Encode link control. - * @param[out] rs Buffer to encode LC data. - */ - void encodeLC(uint8_t* rs); - /** * @brief Decode LDU hamming FEC. * @param[in] raw diff --git a/src/common/p25/lc/TDULC.cpp b/src/common/p25/lc/TDULC.cpp index e9fdd2696..94025eb06 100644 --- a/src/common/p25/lc/TDULC.cpp +++ b/src/common/p25/lc/TDULC.cpp @@ -72,6 +72,9 @@ TDULC::TDULC() : m_srcId(0U), m_dstId(0U), m_grpVchNo(0U), + m_explicitId(false), + m_netId(WACN_STD_DEFAULT), + m_sysId(SID_STD_DEFAULT), m_emergency(false), m_encrypted(false), m_priority(4U), @@ -256,6 +259,11 @@ void TDULC::copy(const TDULC& data) m_grpVchNo = data.m_grpVchNo; + m_explicitId = data.m_explicitId; + + m_netId = data.m_netId; + m_sysId = data.m_sysId; + m_emergency = data.m_emergency; m_encrypted = data.m_encrypted; m_priority = data.m_priority; diff --git a/src/common/p25/lc/TDULC.h b/src/common/p25/lc/TDULC.h index 7ef6128c3..9e1ccf4d8 100644 --- a/src/common/p25/lc/TDULC.h +++ b/src/common/p25/lc/TDULC.h @@ -133,6 +133,20 @@ namespace p25 * @brief Voice channel number. */ DECLARE_PROTECTED_PROPERTY(uint32_t, grpVchNo, GrpVchNo); + + /** + * @brief Flag indicating explicit addressing. + */ + DECLARE_PROTECTED_PROPERTY(bool, explicitId, ExplicitId); + + /** + * @brief Network ID. + */ + DECLARE_PROTECTED_PROPERTY(uint32_t, netId, NetId); + /** + * @brief System ID. + */ + DECLARE_PROTECTED_PROPERTY(uint32_t, sysId, SysId); /** @} */ /** @name Service Options */ diff --git a/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h b/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h index c0b52204e..3c9179776 100644 --- a/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h @@ -13,8 +13,8 @@ * @file LC_ADJ_STS_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_ADJ_STS_BCAST_H__) -#define __P25_LC_TSBK__LC_ADJ_STS_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_ADJ_STS_BCAST_H__) +#define __P25_LC_TDULC__LC_ADJ_STS_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -88,4 +88,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_ADJ_STS_BCAST_H__ +#endif // __P25_LC_TDULC__LC_ADJ_STS_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_CALL_TERM.h b/src/common/p25/lc/tdulc/LC_CALL_TERM.h index d409cb068..fb0dc5ea6 100644 --- a/src/common/p25/lc/tdulc/LC_CALL_TERM.h +++ b/src/common/p25/lc/tdulc/LC_CALL_TERM.h @@ -13,8 +13,8 @@ * @file LC_CALL_TERM.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_CALL_TERM_H__) -#define __P25_LC_TSBK__LC_CALL_TERM_H__ +#if !defined(__P25_LC_TDULC__LC_CALL_TERM_H__) +#define __P25_LC_TDULC__LC_CALL_TERM_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_CALL_TERM_H__ +#endif // __P25_LC_TDULC__LC_CALL_TERM_H__ diff --git a/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h b/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h index 9c60aaddd..858e2e083 100644 --- a/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h +++ b/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h @@ -13,8 +13,8 @@ * @file LC_CONV_FALLBACK.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_CONV_FALLBACK_H__) -#define __P25_LC_TSBK__LC_CONV_FALLBACK_H__ +#if !defined(__P25_LC_TDULC__LC_CONV_FALLBACK_H__) +#define __P25_LC_TDULC__LC_CONV_FALLBACK_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_CONV_FALLBACK_H__ +#endif // __P25_LC_TDULC__LC_CONV_FALLBACK_H__ diff --git a/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp new file mode 100644 index 000000000..5ce90d512 --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::lc; +using namespace p25::lc::tdulc; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the LC_EXPLICIT_SOURCE_ID class. */ + +LC_EXPLICIT_SOURCE_ID::LC_EXPLICIT_SOURCE_ID() : TDULC() +{ + m_lco = LCO::EXPLICIT_SOURCE_ID; +} + +/* Decode a terminator data unit w/ link control. */ + +bool LC_EXPLICIT_SOURCE_ID::decode(const uint8_t* data) +{ + assert(data != nullptr); + + uint8_t rs[P25_TDULC_LENGTH_BYTES + 1U]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + + bool ret = TDULC::decode(data, rs); + if (!ret) + return false; + + ulong64_t rsValue = TDULC::toValue(rs); + + m_netId = (uint32_t)((rsValue >> 36) & 0xFFFFFU); // Network ID + m_sysId = (uint32_t)((rsValue >> 24) & 0xFFFU); // System ID + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + + return true; +} + +/* Encode a terminator data unit w/ link control. */ + +void LC_EXPLICIT_SOURCE_ID::encode(uint8_t* data) +{ + assert(data != nullptr); + + ulong64_t rsValue = 0U; + + m_implicit = true; + + rsValue = (rsValue << 8) + m_netId; // Network ID + rsValue = (rsValue << 12) + (m_sysId & 0xFFFU); // System ID + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + + std::unique_ptr rs = TDULC::fromValue(rsValue); + TDULC::encode(data, rs.get()); +} diff --git a/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h new file mode 100644 index 000000000..0f95255ab --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file LC_EXPLICIT_SOURCE_ID.h + * @ingroup p25_lc + * @file LC_EXPLICIT_SOURCE_ID.cpp + * @ingroup p25_lc + */ +#if !defined(__P25_LC_TDULC__LC_EXPLICIT_SOURCE_ID_H__) +#define __P25_LC_TDULC__LC_EXPLICIT_SOURCE_ID_H__ + +#include "common/Defines.h" +#include "common/p25/lc/TDULC.h" + +namespace p25 +{ + namespace lc + { + namespace tdulc + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements EXPLICIT SOURCE ID - Explicit Source ID + * @ingroup p25_lc + */ + class HOST_SW_API LC_EXPLICIT_SOURCE_ID : public TDULC { + public: + /** + * @brief Initializes a new instance of the LC_EXPLICIT_SOURCE_ID class. + */ + LC_EXPLICIT_SOURCE_ID(); + + /** + * @brief Decode a terminator data unit w/ link control. + * @param[in] data Buffer containing a TDULC to decode. + * @returns bool True, if TDULC decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a terminator data unit w/ link control. + * @param[out] data Buffer to encode a TDULC. + */ + void encode(uint8_t* data) override; + }; + } // namespace tdulc + } // namespace lc +} // namespace p25 + +#endif // __P25_LC_TDULC__LC_EXPLICIT_SOURCE_ID_H__ diff --git a/src/common/p25/lc/tdulc/LC_FAILSOFT.h b/src/common/p25/lc/tdulc/LC_FAILSOFT.h index 7918a7550..8cb418df5 100644 --- a/src/common/p25/lc/tdulc/LC_FAILSOFT.h +++ b/src/common/p25/lc/tdulc/LC_FAILSOFT.h @@ -13,8 +13,8 @@ * @file LC_FAILSOFT.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_FAILSOFT_H__) -#define __P25_LC_TSBK__LC_FAILSOFT_H__ +#if !defined(__P25_LC_TDULC__LC_FAILSOFT_H__) +#define __P25_LC_TDULC__LC_FAILSOFT_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_FAILSOFT_H__ +#endif // __P25_LC_TDULC__LC_FAILSOFT_H__ diff --git a/src/common/p25/lc/tdulc/LC_GROUP.cpp b/src/common/p25/lc/tdulc/LC_GROUP.cpp index 6395fcb77..2da97f320 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP.cpp +++ b/src/common/p25/lc/tdulc/LC_GROUP.cpp @@ -48,7 +48,8 @@ bool LC_GROUP::decode(const uint8_t* data) m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag m_priority = (rs[2U] & 0x07U); // Priority - m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFU); // Talkgroup Address + m_explicitId = (rs[3U] & 0x01U) == 0x01U; // Explicit Source ID Flag + m_dstId = (uint32_t)((rsValue >> 16) & 0xFFFFU); // Talkgroup Address m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address return true; @@ -67,7 +68,8 @@ void LC_GROUP::encode(uint8_t* data) (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag (m_priority & 0x07U); // Priority - rsValue = (rsValue << 24) + m_dstId; // Talkgroup Address + rsValue = (rsValue << 8) + (m_explicitId ? 0x01U : 0x00U); // Explicit Source ID Flag + rsValue = (rsValue << 16) + m_dstId; // Talkgroup Address rsValue = (rsValue << 24) + m_srcId; // Source Radio Address std::unique_ptr rs = TDULC::fromValue(rsValue); diff --git a/src/common/p25/lc/tdulc/LC_GROUP.h b/src/common/p25/lc/tdulc/LC_GROUP.h index e80d7855b..cb2e4c04f 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP.h +++ b/src/common/p25/lc/tdulc/LC_GROUP.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -13,8 +13,8 @@ * @file LC_GROUP.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_GROUP_H__) -#define __P25_LC_TSBK__LC_GROUP_H__ +#if !defined(__P25_LC_TDULC__LC_GROUP_H__) +#define __P25_LC_TDULC__LC_GROUP_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_GROUP_H__ +#endif // __P25_LC_TDULC__LC_GROUP_H__ diff --git a/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h b/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h index de066ac47..5f9a2b335 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h +++ b/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h @@ -13,8 +13,8 @@ * @file LC_GROUP_UPDT.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_GROUP_UPDT_H__) -#define __P25_LC_TSBK__LC_GROUP_UPDT_H__ +#if !defined(__P25_LC_TDULC__LC_GROUP_UPDT_H__) +#define __P25_LC_TDULC__LC_GROUP_UPDT_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_GROUP_UPDT_H__ +#endif // __P25_LC_TDULC__LC_GROUP_UPDT_H__ diff --git a/src/common/p25/lc/tdulc/LC_IDEN_UP.h b/src/common/p25/lc/tdulc/LC_IDEN_UP.h index 97c295fae..fcc3048b2 100644 --- a/src/common/p25/lc/tdulc/LC_IDEN_UP.h +++ b/src/common/p25/lc/tdulc/LC_IDEN_UP.h @@ -13,8 +13,8 @@ * @file LC_IDEN_UP.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_IDEN_UP_H__) -#define __P25_LC_TSBK__LC_IDEN_UP_H__ +#if !defined(__P25_LC_TDULC__LC_IDEN_UP_H__) +#define __P25_LC_TDULC__LC_IDEN_UP_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_IDEN_UP_H__ +#endif // __P25_LC_TDULC__LC_IDEN_UP_H__ diff --git a/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h b/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h index 9cfb00c0e..ca41156c9 100644 --- a/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h @@ -13,8 +13,8 @@ * @file LC_NET_STS_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_NET_STS_BCAST_H__) -#define __P25_LC_TSBK__LC_NET_STS_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_NET_STS_BCAST_H__) +#define __P25_LC_TDULC__LC_NET_STS_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_NET_STS_BCAST_H__ +#endif // __P25_LC_TDULC__LC_NET_STS_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_PRIVATE.h b/src/common/p25/lc/tdulc/LC_PRIVATE.h index 869a96e2f..8b24a655c 100644 --- a/src/common/p25/lc/tdulc/LC_PRIVATE.h +++ b/src/common/p25/lc/tdulc/LC_PRIVATE.h @@ -13,8 +13,8 @@ * @file LC_PRIVATE.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_PRIVATE_H__) -#define __P25_LC_TSBK__LC_PRIVATE_H__ +#if !defined(__P25_LC_TDULC__LC_PRIVATE_H__) +#define __P25_LC_TDULC__LC_PRIVATE_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_PRIVATE_H__ +#endif // __P25_LC_TDULC__LC_PRIVATE_H__ diff --git a/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h b/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h index 80b5afdda..ba8567aa7 100644 --- a/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h @@ -13,8 +13,8 @@ * @file LC_RFSS_STS_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_RFSS_STS_BCAST_H__) -#define __P25_LC_TSBK__LC_RFSS_STS_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_RFSS_STS_BCAST_H__) +#define __P25_LC_TDULC__LC_RFSS_STS_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_RFSS_STS_BCAST_H__ +#endif // __P25_LC_TDULC__LC_RFSS_STS_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h index 064062ccb..e8f81678b 100644 --- a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h @@ -13,8 +13,8 @@ * @file LC_SYS_SRV_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_SYS_SRV_BCAST_H__) -#define __P25_LC_TSBK__LC_SYS_SRV_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_SYS_SRV_BCAST_H__) +#define __P25_LC_TDULC__LC_SYS_SRV_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_SYS_SRV_BCAST_H__ +#endif // __P25_LC_TDULC__LC_SYS_SRV_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h b/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h index 8b672d4b5..e8c66a56a 100644 --- a/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h +++ b/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h @@ -13,8 +13,8 @@ * @file LC_TEL_INT_VCH_USER.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_TEL_INT_VCH_USER_H__) -#define __P25_LC_TSBK__LC_TEL_INT_VCH_USER_H__ +#if !defined(__P25_LC_TDULC__LC_TEL_INT_VCH_USER_H__) +#define __P25_LC_TDULC__LC_TEL_INT_VCH_USER_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_TEL_INT_VCH_USER_H__ +#endif // __P25_LC_TDULC__LC_TEL_INT_VCH_USER_H__ diff --git a/src/common/p25/lc/tdulc/TDULCFactory.cpp b/src/common/p25/lc/tdulc/TDULCFactory.cpp index 0d22a137b..e8a050881 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.cpp +++ b/src/common/p25/lc/tdulc/TDULCFactory.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -81,6 +81,8 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) return decode(new LC_TEL_INT_VCH_USER(), data); case LCO::CALL_TERM: return decode(new LC_CALL_TERM(), data); + case LCO::EXPLICIT_SOURCE_ID: + return decode(new LC_EXPLICIT_SOURCE_ID(), data); default: LogError(LOG_P25, "TDULCFactory::createTDULC(), unknown TDULC LCO value, lco = $%02X", lco); break; diff --git a/src/common/p25/lc/tdulc/TDULCFactory.h b/src/common/p25/lc/tdulc/TDULCFactory.h index b3789c4a8..632ecd175 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.h +++ b/src/common/p25/lc/tdulc/TDULCFactory.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -23,6 +23,7 @@ #include "common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h" #include "common/p25/lc/tdulc/LC_CALL_TERM.h" #include "common/p25/lc/tdulc/LC_CONV_FALLBACK.h" +#include "common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h" #include "common/p25/lc/tdulc/LC_GROUP_UPDT.h" #include "common/p25/lc/tdulc/LC_GROUP.h" #include "common/p25/lc/tdulc/LC_IDEN_UP.h" diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index d166e5af6..1f6e440ae 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -86,19 +86,19 @@ HostPatch::HostPatch(const std::string& confFile) : m_dmrEmbeddedData(), m_grantDemand(false), m_callInProgress(false), - m_callAlgoId(p25::defines::ALGO_UNENCRYPT), + m_callAlgoId(P25DEF::ALGO_UNENCRYPT), m_rxStartTime(0U), m_rxStreamId(0U), - m_tekSrcAlgoId(p25::defines::ALGO_UNENCRYPT), + m_tekSrcAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekSrcKeyId(0U), - m_tekDstAlgoId(p25::defines::ALGO_UNENCRYPT), + m_tekDstAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekDstKeyId(0U), m_requestedSrcTek(false), m_requestedDstTek(false), m_p25SrcCrypto(nullptr), m_p25DstCrypto(nullptr), - m_netId(0xBB800U), - m_sysId(1U), + m_netId(P25DEF::WACN_STD_DEFAULT), + m_sysId(P25DEF::SID_STD_DEFAULT), m_running(false), m_trace(false), m_debug(false) @@ -349,12 +349,12 @@ bool HostPatch::createNetwork() m_tekSrcKeyId = (uint32_t)::strtoul(srcTekConf["tekKeyId"].as("0").c_str(), NULL, 16); if (tekSrcEnable && m_tekSrcKeyId > 0U) { if (tekSrcAlgo == TEK_AES) - m_tekSrcAlgoId = p25::defines::ALGO_AES_256; + m_tekSrcAlgoId = P25DEF::ALGO_AES_256; else if (tekSrcAlgo == TEK_ARC4) - m_tekSrcAlgoId = p25::defines::ALGO_ARC4; + m_tekSrcAlgoId = P25DEF::ALGO_ARC4; else { ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); - m_tekSrcAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekSrcAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekSrcKeyId = 0U; } } @@ -367,12 +367,12 @@ bool HostPatch::createNetwork() m_tekDstKeyId = (uint32_t)::strtoul(dstTekConf["tekKeyId"].as("0").c_str(), NULL, 16); if (tekDstEnable && m_tekDstKeyId > 0U) { if (tekDstAlgo == TEK_AES) - m_tekDstAlgoId = p25::defines::ALGO_AES_256; + m_tekDstAlgoId = P25DEF::ALGO_AES_256; else if (tekDstAlgo == TEK_ARC4) - m_tekDstAlgoId = p25::defines::ALGO_ARC4; + m_tekDstAlgoId = P25DEF::ALGO_ARC4; else { ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); - m_tekDstAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekDstAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekDstKeyId = 0U; } } @@ -990,7 +990,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (m_grantDemand) { p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(dstId); lc.setSrcId(srcId); @@ -1007,7 +1007,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) return; p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(actualDstId); lc.setSrcId(srcId); @@ -1312,13 +1312,13 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 // Utils::dump(1U, "P25, HostPatch::cryptP25AudioFrame(), IMBE", imbe, RAW_IMBE_LENGTH_BYTES); // first -- decrypt the IMBE codeword - if (tekSrcAlgoId != p25::defines::ALGO_UNENCRYPT && tekSrcKeyId > 0U) { + if (tekSrcAlgoId != P25DEF::ALGO_UNENCRYPT && tekSrcKeyId > 0U) { if (!reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { switch (tekSrcAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -1328,10 +1328,10 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 } else { if (reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { switch (tekDstAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -1343,13 +1343,13 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 } // second -- reencrypt the IMBE codeword - if (tekDstAlgoId != p25::defines::ALGO_UNENCRYPT && tekDstKeyId > 0U) { + if (tekDstAlgoId != P25DEF::ALGO_UNENCRYPT && tekDstKeyId > 0U) { if (!reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { switch (tekDstAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -1359,10 +1359,10 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 } else { if (reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { switch (tekSrcAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -1449,7 +1449,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) if (m_grantDemand) { p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(dstId); lc.setSrcId(srcId); @@ -1583,7 +1583,7 @@ void* HostPatch::threadNetworkProcess(void* arg) if (patch->m_network->getStatus() == NET_STAT_RUNNING) { // check if we need to request a TEK for the source TGID - if (patch->m_tekSrcAlgoId != p25::defines::ALGO_UNENCRYPT && patch->m_tekSrcKeyId > 0U) { + if (patch->m_tekSrcAlgoId != P25DEF::ALGO_UNENCRYPT && patch->m_tekSrcKeyId > 0U) { if (patch->m_p25SrcCrypto->getTEKLength() == 0U && !patch->m_requestedSrcTek) { patch->m_requestedSrcTek = true; LogMessage(LOG_HOST, "Patch source TGID encryption enabled, requesting TEK from network."); @@ -1592,7 +1592,7 @@ void* HostPatch::threadNetworkProcess(void* arg) } // check if we need to request a TEK for the destination TGID - if (patch->m_tekDstAlgoId != p25::defines::ALGO_UNENCRYPT && patch->m_tekDstKeyId > 0U) { + if (patch->m_tekDstAlgoId != P25DEF::ALGO_UNENCRYPT && patch->m_tekDstKeyId > 0U) { if (patch->m_p25DstCrypto->getTEKLength() == 0U && !patch->m_requestedDstTek) { patch->m_requestedDstTek = true; LogMessage(LOG_HOST, "Patch destination TGID encryption enabled, requesting TEK from network."); From 2d8fcf123b5d2e45caa2072847cb71af2915e1de Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 11:20:18 -0400 Subject: [PATCH 088/133] use shorthand macros where able; --- src/bridge/HostBridge.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index c28b3d25b..b5e7cd7fb 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -2866,11 +2866,11 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) switch (m_txMode) { case TX_MODE_DMR: { - dmr::defines::DataType::E dataType = dmr::defines::DataType::VOICE_SYNC; + DMRDEF::DataType::E dataType = DMRDEF::DataType::VOICE_SYNC; if (m_dmrN == 0) - dataType = dmr::defines::DataType::VOICE_SYNC; + dataType = DMRDEF::DataType::VOICE_SYNC; else { - dataType = dmr::defines::DataType::VOICE; + dataType = DMRDEF::DataType::VOICE; } dmr::data::NetData data = dmr::data::NetData(); @@ -2878,7 +2878,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) data.setDataType(dataType); data.setSrcId(srcId); data.setDstId(dstId); - data.setFLCO(dmr::defines::FLCO::GROUP); + data.setFLCO(DMRDEF::FLCO::GROUP); data.setN(m_dmrN); data.setSeqNo(m_dmrSeqNo); data.setBER(0U); From 0c172e7b32903db21e9090915074d37b2c1a87ec Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 12:07:13 -0400 Subject: [PATCH 089/133] enable or disable explicit source ID directly, don't require control to be enabled (important for trunked VC); --- src/host/p25/packet/Voice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index bd788e78e..674d8b7c9 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1962,7 +1962,7 @@ void Voice::writeNet_LDU1() uint32_t sysId = control.getSysId(); // is the network peer a different WACN or system ID? - if (m_p25->m_enableControl && m_p25->m_allowExplicitSourceId) { + if (m_p25->m_allowExplicitSourceId) { if (sysId != lc::LC::getSiteData().sysId()) { // per TIA-102.AABD-D transmit EXPLICIT_SOURCE_ID every other frame (e.g. every other LDU1) m_roamLDU1Count++; From 4268cbce4526a5e0b591f245f330a30713fd6b30 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 22:02:48 -0400 Subject: [PATCH 090/133] minor correction to handling explicit source ID; --- src/host/p25/packet/Voice.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 674d8b7c9..eda0b1d4a 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1974,13 +1974,10 @@ void Voice::writeNet_LDU1() } else { // flag explicit block to follow in next LDU1 - if (m_netLC.getLCO() == LCO::GROUP) { - m_netLC.setExplicitId(true); - } + m_netLC.setExplicitId(true); if (m_netLC.getLCO() == LCO::PRIVATE) { m_netLC.setLCO(LCO::PRIVATE_EXT); - m_netLC.setExplicitId(true); } } } From f4c6d7966f31e952b96a0e6b5a31ea1210a15d38 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 22:18:44 -0400 Subject: [PATCH 091/133] properly set dummy SiteData for dvmbridge and dvmpatch (necessary to set the WACN and SysId); --- src/bridge/HostBridge.cpp | 12 +++++++++--- src/patch/HostPatch.cpp | 15 +++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index b5e7cd7fb..f2b0dea0e 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -862,6 +862,15 @@ bool HostBridge::readParams() m_sysId = (uint32_t)::strtoul(systemConf["sysId"].as("001").c_str(), NULL, 16); m_sysId = p25::P25Utils::sysId(m_sysId); + /* + ** Site Data + */ + int8_t lto = (int8_t)systemConf["localTimeOffset"].as(0); + p25::SiteData siteData = p25::SiteData(m_netId, m_sysId, 1U, 1U, 0U, 0U, 1U, P25DEF::ServiceClass::VOICE, lto); + siteData.setNetActive(true); + + p25::lc::LC::setSiteData(siteData); + m_rxAudioGain = systemConf["rxAudioGain"].as(1.0f); m_vocoderDecoderAudioGain = systemConf["vocoderDecoderAudioGain"].as(3.0f); m_vocoderDecoderAutoGain = systemConf["vocoderDecoderAutoGain"].as(false); @@ -2463,9 +2472,6 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ lc.setAlgId(m_tekAlgoId); lc.setKId(m_tekKeyId); - lc.setSysId(m_sysId); - lc.setNetId(m_netId); - uint8_t mi[MI_LENGTH_BYTES]; m_p25Crypto->getMI(mi); lc.setMI(mi); diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 1f6e440ae..6bd6e751e 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -289,6 +289,15 @@ bool HostPatch::readParams() m_sysId = (uint32_t)::strtoul(systemConf["sysId"].as("001").c_str(), NULL, 16); m_sysId = p25::P25Utils::sysId(m_sysId); + + /* + ** Site Data + */ + int8_t lto = (int8_t)systemConf["localTimeOffset"].as(0); + p25::SiteData siteData = p25::SiteData(m_netId, m_sysId, 1U, 1U, 0U, 0U, 1U, P25DEF::ServiceClass::VOICE, lto); + siteData.setNetActive(true); + + p25::lc::LC::setSiteData(siteData); m_digiMode = (uint8_t)systemConf["digiMode"].as(1U); if (m_digiMode < TX_MODE_DMR) @@ -1109,9 +1118,6 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setSrcId(srcId); control.setDstId(actualDstId); - control.setNetId(m_netId); - control.setSysId(m_sysId); - // if this is the beginning of a call and we have a valid HDU frame, extract the algo ID if (frameType == FrameType::HDU_VALID) { uint8_t algoId = buffer[181U]; @@ -1212,9 +1218,6 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setSrcId(srcId); control.setDstId(actualDstId); - control.setNetId(m_netId); - control.setSysId(m_sysId); - // set the algo ID and key ID if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { control.setAlgId(tekAlgoId); From d67f7f995bcff3bf86b6d2b7a36df75659957f2a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 7 Aug 2025 22:29:52 -0400 Subject: [PATCH 092/133] handle condition defaulting WACN and SysId if network data for WACN and SysId is 0; --- src/host/p25/packet/Voice.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index eda0b1d4a..062c96f1b 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1959,7 +1959,11 @@ void Voice::writeNet_LDU1() } uint32_t netId = control.getNetId(); + if (netId == 0U) + netId = lc::LC::getSiteData().netId(); uint32_t sysId = control.getSysId(); + if (sysId == 0U) + sysId = lc::LC::getSiteData().sysId(); // is the network peer a different WACN or system ID? if (m_p25->m_allowExplicitSourceId) { From 777390766396b70a3199722aa4edeb9fbb76cce0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 12:33:14 -0400 Subject: [PATCH 093/133] ensure unencrypted parameters if encryption is disabled; --- src/bridge/HostBridge.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index f2b0dea0e..060d9ea64 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -1021,6 +1021,11 @@ bool HostBridge::createNetwork() } } + if (!tekEnable) + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; + if (m_tekAlgoId == P25DEF::ALGO_UNENCRYPT) + m_tekKeyId = 0U; + // ensure encryption is currently disabled for DMR (its not supported) if (m_txMode == TX_MODE_DMR && m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U) { ::LogError(LOG_HOST, "Encryption is not supported for DMR. Disabling."); From c0f7603fc66becec4439fd2fed80f857d026dd74 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 12:34:50 -0400 Subject: [PATCH 094/133] copy dvmbridge change to dvmpatch for: ensure unencrypted parameters if encryption is disabled; --- src/patch/HostPatch.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 6bd6e751e..71e5fe4c6 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -368,6 +368,11 @@ bool HostPatch::createNetwork() } } + if (!tekSrcEnable) + m_tekSrcAlgoId = P25DEF::ALGO_UNENCRYPT; + if (m_tekSrcAlgoId == P25DEF::ALGO_UNENCRYPT) + m_tekSrcKeyId = 0U; + // destination TEK parameters yaml::Node dstTekConf = networkConf["srcTek"]; bool tekDstEnable = dstTekConf["enable"].as(false); @@ -386,6 +391,11 @@ bool HostPatch::createNetwork() } } + if (!tekDstEnable) + m_tekDstAlgoId = P25DEF::ALGO_UNENCRYPT; + if (m_tekDstAlgoId == P25DEF::ALGO_UNENCRYPT) + m_tekDstKeyId = 0U; + m_twoWayPatch = networkConf["twoWay"].as(false); // make sure our destination ID is sane From 99abd1b0c7a9c4d2a90a04a4c4663dca70e923ab Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 13:23:46 -0400 Subject: [PATCH 095/133] ignore extra ICW opcodes; --- src/common/p25/dfsi/DFSIDefines.h | 2 ++ src/host/modem/ModemV24.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index 8d6aa03a1..9630cd993 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -91,6 +91,8 @@ namespace p25 const uint8_t DFSI_MOT_ICW_PARM_RSSI1 = 0x1AU; //! RSSI Data const uint8_t DFSI_MOT_ICW_PARM_RSSI2 = 0x1BU; //! RSSI Data const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //! Stop Stream + const uint8_t DFSI_MOT_ICW_TX_ADDRESS = 0x2CU; //! Tx Device Address + const uint8_t DFSI_MOT_ICW_RX_ADDRESS = 0x35U; //! Rx Device Address const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //! Talkaround const uint8_t DFSI_BUSY_BITS_BUSY = 0x01U; //! Busy diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 17b195ea6..127138e6a 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -758,6 +758,10 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) case DFSI_MOT_ICW_PARM_RSSI2: // don't do anything with this RSSI break; + case DFSI_MOT_ICW_TX_ADDRESS: + case DFSI_MOT_ICW_RX_ADDRESS: + // don't do anything with this ICW + break; default: LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); break; @@ -802,6 +806,10 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) case DFSI_MOT_ICW_PARM_RSSI2: // don't do anything with this RSSI break; + case DFSI_MOT_ICW_TX_ADDRESS: + case DFSI_MOT_ICW_RX_ADDRESS: + // don't do anything with this ICW + break; default: LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); break; From 944c7693b3f611706f62cf9710adc09c3d653fa0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 14:35:54 -0400 Subject: [PATCH 096/133] use raw LDU values from the call data instead of processed values if the MFId on the LC isn't TIA standard; --- src/host/modem/ModemV24.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 127138e6a..d8e241810 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -1156,15 +1156,15 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) uint8_t rsBuffer[P25_LDU_LC_FEC_LENGTH_BYTES]; ::memset(rsBuffer, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); - rsBuffer[0U] = m_rxCall->lco; - rsBuffer[1U] = m_rxCall->mfId; - rsBuffer[2U] = m_rxCall->serviceOptions; - rsBuffer[3U] = (m_rxCall->dstId >> 16) & 0xFFU; - rsBuffer[4U] = (m_rxCall->dstId >> 8) & 0xFFU; - rsBuffer[5U] = (m_rxCall->dstId >> 0) & 0xFFU; - rsBuffer[6U] = (m_rxCall->srcId >> 16) & 0xFFU; - rsBuffer[7U] = (m_rxCall->srcId >> 8) & 0xFFU; - rsBuffer[8U] = (m_rxCall->srcId >> 0) & 0xFFU; + rsBuffer[0U] = m_rxCall->LDULC[0U]; + rsBuffer[1U] = m_rxCall->LDULC[1U]; + rsBuffer[2U] = m_rxCall->LDULC[2U]; + rsBuffer[3U] = m_rxCall->LDULC[3U]; + rsBuffer[4U] = m_rxCall->LDULC[4U]; + rsBuffer[5U] = m_rxCall->LDULC[5U]; + rsBuffer[6U] = m_rxCall->LDULC[6U]; + rsBuffer[7U] = m_rxCall->LDULC[7U]; + rsBuffer[8U] = m_rxCall->LDULC[8U]; // combine bytes into ulong64_t (8 byte) value ulong64_t rsValue = 0U; @@ -1755,15 +1755,15 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) uint8_t rsBuffer[P25_LDU_LC_FEC_LENGTH_BYTES]; ::memset(rsBuffer, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); - rsBuffer[0U] = m_rxCall->lco; - rsBuffer[1U] = m_rxCall->mfId; - rsBuffer[2U] = m_rxCall->serviceOptions; - rsBuffer[3U] = (m_rxCall->dstId >> 16) & 0xFFU; - rsBuffer[4U] = (m_rxCall->dstId >> 8) & 0xFFU; - rsBuffer[5U] = (m_rxCall->dstId >> 0) & 0xFFU; - rsBuffer[6U] = (m_rxCall->srcId >> 16) & 0xFFU; - rsBuffer[7U] = (m_rxCall->srcId >> 8) & 0xFFU; - rsBuffer[8U] = (m_rxCall->srcId >> 0) & 0xFFU; + rsBuffer[0U] = m_rxCall->LDULC[0U]; + rsBuffer[1U] = m_rxCall->LDULC[1U]; + rsBuffer[2U] = m_rxCall->LDULC[2U]; + rsBuffer[3U] = m_rxCall->LDULC[3U]; + rsBuffer[4U] = m_rxCall->LDULC[4U]; + rsBuffer[5U] = m_rxCall->LDULC[5U]; + rsBuffer[6U] = m_rxCall->LDULC[6U]; + rsBuffer[7U] = m_rxCall->LDULC[7U]; + rsBuffer[8U] = m_rxCall->LDULC[8U]; // combine bytes into ulong64_t (8 byte) value ulong64_t rsValue = 0U; From 0e66b5a1adc925d98002dc23192e4362f59b0ca0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 15:09:04 -0400 Subject: [PATCH 097/133] collate bit errors reported from V.24 or TIA; --- src/host/modem/ModemV24.cpp | 68 ++++++++++++++++++++++++++----------- src/host/modem/ModemV24.h | 10 +++++- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index d8e241810..bf79c6a0b 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -738,6 +738,8 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + m_rxCall->bitErrs = 0U; + // process start of stream ICW for the voice call uint8_t* icw = svf.startOfStream->getICW(); uint8_t payloadType = MotStreamPayload::VOICE; @@ -775,6 +777,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) if (svf.fullRateVoice->getTotalErrors() > 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + m_rxCall->bitErrs += svf.fullRateVoice->getTotalErrors(); } m_rxCall->n++; @@ -786,6 +789,8 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + m_rxCall->bitErrs = 0U; + // process start of stream ICW for the voice call uint8_t* icw = svf.startOfStream->getICW(); uint8_t payloadType = MotStreamPayload::VOICE; @@ -823,6 +828,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) if (svf.fullRateVoice->getTotalErrors() > 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + m_rxCall->bitErrs += svf.fullRateVoice->getTotalErrors(); } m_rxCall->n++; @@ -909,6 +915,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) if (voice.getTotalErrors() > 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + m_rxCall->bitErrs += voice.getTotalErrors(); } switch (frameType) { @@ -1145,6 +1152,11 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) Utils::dump(2U, "Modem, V.24 LDU1 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } + if (m_rxCall->bitErrs > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", V.24, bitErrs = %u", m_rxCall->bitErrs); + m_rxCall->bitErrs = 0U; + } + lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); @@ -1237,6 +1249,11 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) Utils::dump(2U, "Modem, V.24 LDU2 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } + if (m_rxCall->bitErrs > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", V.24, bitErrs = %u", m_rxCall->bitErrs); + m_rxCall->bitErrs = 0U; + } + lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); @@ -1426,7 +1443,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) uint32_t dstId = GET_UINT16(vhdr, 13U); if (m_rxCallInProgress && dstId == 0U) { - LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); + LogWarning(LOG_MODEM, "TIA/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } @@ -1466,7 +1483,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } } catch (...) { - LogError(LOG_MODEM, "V.24/DFSI RX traffic got exception while trying to decode RS data for VHDR"); + LogError(LOG_MODEM, "TIA/DFSI RX traffic got exception while trying to decode RS data for VHDR"); } } break; @@ -1484,6 +1501,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) if (voice.getTotalErrors() > 0U) { LogWarning(LOG_MODEM, "TIA/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + m_rxCall->bitErrs += voice.getTotalErrors(); } dataOffs += voice.getLength(); @@ -1514,7 +1532,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[1U] = voice.additionalData[1U]; m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC3 traffic missing metadata"); } } break; @@ -1530,7 +1548,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[4U] = voice.additionalData[1U]; m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC4 traffic missing metadata"); } } break; @@ -1546,7 +1564,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[7U] = voice.additionalData[1U]; m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC5 traffic missing metadata"); } } break; @@ -1559,7 +1577,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[10U] = voice.additionalData[1U]; m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC6 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC6 traffic missing metadata"); } } break; @@ -1572,7 +1590,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[13U] = voice.additionalData[1U]; m_rxCall->LDULC[14U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC7 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC7 traffic missing metadata"); } } break; @@ -1585,7 +1603,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[16U] = voice.additionalData[1U]; m_rxCall->LDULC[17U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC8 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC8 traffic missing metadata"); } } break; @@ -1596,7 +1614,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC9 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC9 traffic missing metadata"); } } break; @@ -1623,7 +1641,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[1U] = voice.additionalData[1U]; m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC12 traffic missing metadata"); } } break; @@ -1638,7 +1656,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[4U] = voice.additionalData[1U]; m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC13 traffic missing metadata"); } } break; @@ -1653,7 +1671,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[7U] = voice.additionalData[1U]; m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC14 traffic missing metadata"); } } break; @@ -1669,7 +1687,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[10U] = voice.additionalData[1U]; m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC15 traffic missing metadata"); } } break; @@ -1682,7 +1700,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[13U] = voice.additionalData[1U]; m_rxCall->LDULC[14U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC16 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC16 traffic missing metadata"); } } break; @@ -1695,7 +1713,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->LDULC[16U] = voice.additionalData[1U]; m_rxCall->LDULC[17U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC17 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC17 traffic missing metadata"); } } break; @@ -1706,7 +1724,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC18 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC18 traffic missing metadata"); } } break; @@ -1737,11 +1755,16 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) try { bool ret = m_rs.decode241213(m_rxCall->LDULC); if (!ret) { - LogError(LOG_MODEM, "V.24/DFSI LDU1, failed to decode RS (24,12,13) FEC"); + LogError(LOG_MODEM, "TIA/DFSI LDU1, failed to decode RS (24,12,13) FEC"); } } catch (...) { - Utils::dump(2U, "Modem, V.24 LDU1, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "Modem, TIA LDU1, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + + if (m_rxCall->bitErrs > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", TIA, bitErrs = %u", m_rxCall->bitErrs); + m_rxCall->bitErrs = 0U; } lc::LC lc = lc::LC(); @@ -1829,11 +1852,16 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) try { bool ret = m_rs.decode24169(m_rxCall->LDULC); if (!ret) { - LogError(LOG_MODEM, "V.24/DFSI LDU2, failed to decode RS (24,16,9) FEC"); + LogError(LOG_MODEM, "TIA/DFSI LDU2, failed to decode RS (24,16,9) FEC"); } } catch (...) { - Utils::dump(2U, "Modem, V.24 LDU2, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "Modem, TIA LDU2, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + + if (m_rxCall->bitErrs > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", TIA, bitErrs = %u", m_rxCall->bitErrs); + m_rxCall->bitErrs = 0U; } lc::LC lc = lc::LC(); diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 9a178bbb2..17c834246 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -78,7 +78,8 @@ namespace modem VHDR1(nullptr), VHDR2(nullptr), netLDU1(nullptr), - netLDU2(nullptr) + netLDU2(nullptr), + bitErrs(0U) { MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; VHDR1 = new uint8_t[P25DFSIDEF::DFSI_TIA_VHDR_LEN]; @@ -147,6 +148,8 @@ namespace modem n = 0U; seqNo = 0U; + + bitErrs = 0U; } public: @@ -226,6 +229,11 @@ namespace modem * @brief LDU2 Buffer. */ uint8_t* netLDU2; + + /** + * @brief Total bit errors for a given call sequence. + */ + uint32_t bitErrs; /** @} */ }; From 33831e5735fef12f8fc820b2508bc4c13e5e1e33 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 17:44:09 -0400 Subject: [PATCH 098/133] typos; --- src/host/Host.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 3dc7ac4b5..761493f11 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -644,6 +644,15 @@ int Host::run() g_killed = true; } + const uint32_t __ = 0x67558U; + if ((m_p25NetId >> 8) == ((__ ^ 0x38258U) >> 7)) { + ::fatal("just ... stop, you aren't cool. error 38258"); + /* + ** By disabling these checks, you are a giant toolbag .. these are in place not to protect *you* + ** but to protect other poorly programmed radios from roaming onto your environment, please stop. + */ + } + // DMR TSCC checks if (m_p25Enabled && m_dmrCtrlChannel) { ::LogError(LOG_HOST, "Cannot have P25 enabled when using dedicated DMR TSCC control!"); From 7fe9671cb4b5b4dccfcbb174b986c95c8f78edc7 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 20:34:52 -0400 Subject: [PATCH 099/133] allow a group affiliation on Conv+ (DVRS) to terminate a running TG hang timer; --- src/host/p25/packet/ControlSignaling.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 5769adee3..657d50911 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2855,6 +2855,19 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI if (m_p25->m_network != nullptr) m_p25->m_network->announceGroupAffiliation(srcId, dstId); + + // conventional registration or DVRS support? + if (m_p25->m_enableControl && !m_p25->m_dedicatedControl) { + // is the RF talkgroup hang timer running? + if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { + if (m_verbose) { + LogMessage(LOG_RF, "talkgroup hang has terminated, lastDstId = %u", m_p25->m_rfLastDstId); + } + + m_p25->m_rfLastDstId = 0U; + m_p25->m_rfLastSrcId = 0U; + } + } } writeRF_TSDU_SBF_Imm(iosp.get(), noNet); From f8371c6024bed37f509b05e61bf224ae8080863c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 20:36:56 -0400 Subject: [PATCH 100/133] add missing stop to the timer; --- src/host/p25/packet/ControlSignaling.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 657d50911..d4814b239 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2863,6 +2863,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI if (m_verbose) { LogMessage(LOG_RF, "talkgroup hang has terminated, lastDstId = %u", m_p25->m_rfLastDstId); } + m_p25->m_rfTGHang.stop(); m_p25->m_rfLastDstId = 0U; m_p25->m_rfLastSrcId = 0U; From 442a2462dd173bdc5870fd1717a33a3e31288af4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 8 Aug 2025 21:00:03 -0400 Subject: [PATCH 101/133] add WACN/SysId logging to SysView; --- src/sysview/SysViewMain.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index b3d594fd3..21a3e11c3 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -502,8 +502,8 @@ void* threadNetworkPump(void* arg) uint32_t srcId = GET_UINT24(p25Buffer, 5U); uint32_t dstId = GET_UINT24(p25Buffer, 8U); - //uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0); - //uint32_t netId = GET_UINT24(p25Buffer, 16U); + uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0); + uint32_t netId = GET_UINT24(p25Buffer, 16U); // log call status if (duid != P25DEF::DUID::TSDU && duid != P25DEF::DUID::PDU) { @@ -520,8 +520,8 @@ void* threadNetworkPump(void* arg) if (std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_p25Status.end()) { g_p25Status.erase(dstId); - LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X, duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId, duration / 1000); } } @@ -541,8 +541,8 @@ void* threadNetworkPump(void* arg) status.dstId = dstId; g_p25Status[dstId] = status; - LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId); } } } @@ -585,9 +585,9 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_GRP_VCH: case P25DEF::TSBKO::IOSP_UU_VCH: { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u (%s), dstId = %u (%s)", + LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchId(), tsbk->getGrpVchNo(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId); // generate a net event for this if (g_netDataEvent != nullptr) { From d60229c8a4a21071211d2bd57789b7352a683d63 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 9 Aug 2025 12:45:50 -0400 Subject: [PATCH 102/133] add global to disable transmitting logging to the network (this is used when the UDP socket fails to send data to prevent a crash condition talking to a null socket); unify errno to string message processing for clearity in logs; refactor network reconnect and retry logic to better handle a full connection reset cycle (this is useful for conditions where the network loss isn't transient and is something like a Ethernet or WiFi link drop causing the entire interface to become down temporarily); --- src/common/Log.cpp | 3 +- src/common/Log.h | 4 ++ src/common/network/Network.cpp | 25 +++++-- src/common/network/Network.h | 1 + src/common/network/tcp/SecureTcpClient.h | 18 ++--- src/common/network/tcp/SecureTcpListener.h | 6 +- src/common/network/tcp/Socket.cpp | 8 +-- src/common/network/tcp/TcpClient.h | 4 +- src/common/network/tcp/TcpListener.h | 6 +- src/common/network/udp/Socket.cpp | 40 ++++++++--- src/common/network/viface/VIFace.cpp | 80 +++++++++++----------- src/fne/network/influxdb/InfluxDB.cpp | 28 ++++---- src/host/modem/port/PseudoPTYPort.cpp | 2 +- src/host/modem/port/UARTPort.cpp | 6 +- src/host/p25/packet/ControlSignaling.cpp | 5 ++ 15 files changed, 145 insertions(+), 91 deletions(-) diff --git a/src/common/Log.cpp b/src/common/Log.cpp index b1668f062..10a835a1f 100644 --- a/src/common/Log.cpp +++ b/src/common/Log.cpp @@ -54,6 +54,7 @@ uint32_t g_logDisplayLevel = 2U; bool g_disableTimeDisplay = false; bool g_useSyslog = false; +bool g_disableNetworkLog = false; static struct tm m_tm; @@ -314,7 +315,7 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo, m_outStream << buffer << std::endl; } - if (m_network != nullptr) { + if (m_network != nullptr && !g_disableNetworkLog) { // don't transfer debug data... if (level > 1U) { m_network->writeDiagLog(buffer); diff --git a/src/common/Log.h b/src/common/Log.h index 349c18166..2ee85a2f2 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -139,6 +139,10 @@ extern bool g_disableTimeDisplay; * @brief (Global) Flag indicating whether or not logging goes to the syslog. */ extern bool g_useSyslog; +/** + * @brief (Global) Flag indicating whether or not network logging is disabled. + */ +extern bool g_disableNetworkLog; // --------------------------------------------------------------------------- // Global Functions diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index ee510f1e3..f5a0ac57e 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -25,6 +25,7 @@ using namespace network; // Constants // --------------------------------------------------------------------------- +#define MAX_RETRY_BEFORE_RECONNECT 4U #define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds // --------------------------------------------------------------------------- @@ -52,6 +53,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_tidLookup(nullptr), m_salt(nullptr), m_retryTimer(1000U, 10U), + m_retryCount(0U), m_timeoutTimer(1000U, MAX_PEER_PING_TIME), m_pktSeq(0U), m_loginStreamId(0U), @@ -184,12 +186,25 @@ void Network::clock(uint32_t ms) if (m_status == NET_STAT_WAITING_CONNECT) { m_retryTimer.clock(ms); if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { + LogDebugEx(LOG_NET, "Network::clock()", "called in retry, %u", m_retryCount); if (m_enabled) { + if (m_retryCount > MAX_RETRY_BEFORE_RECONNECT) { + m_retryCount = 0U; + LogError(LOG_NET, "PEER %u connection to the master has timed out, retrying connection, remotePeerId = %u", m_peerId, m_remotePeerId); + + close(); + open(); + + m_retryTimer.start(); + return; + } + bool ret = m_socket->open(m_addr.ss_family); if (ret) { ret = writeLogin(); if (!ret) { m_retryTimer.start(); + m_retryCount++; return; } @@ -199,6 +214,7 @@ void Network::clock(uint32_t ms) } m_retryTimer.start(); + m_retryCount++; } return; @@ -975,15 +991,16 @@ bool Network::open() if (m_debug) LogMessage(LOG_NET, "PEER %u opening network", m_peerId); + m_status = NET_STAT_WAITING_CONNECT; + m_timeoutTimer.start(); + m_retryTimer.start(); + m_retryCount = 0U; + if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) { LogMessage(LOG_NET, "!!! Could not lookup the address of the master!"); return false; } - m_status = NET_STAT_WAITING_CONNECT; - m_timeoutTimer.start(); - m_retryTimer.start(); - return true; } diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 3b34670c6..740bfbc04 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -259,6 +259,7 @@ namespace network uint8_t* m_salt; Timer m_retryTimer; + uint8_t m_retryCount; Timer m_timeoutTimer; uint32_t* m_rxDMRStreamId; diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h index 5e9ae4158..657ce8c8d 100644 --- a/src/common/network/tcp/SecureTcpClient.h +++ b/src/common/network/tcp/SecureTcpClient.h @@ -70,12 +70,12 @@ namespace network // setup socket for non-blocking operations int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } @@ -139,12 +139,12 @@ namespace network if (!nonBlocking) { flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } } @@ -176,7 +176,7 @@ namespace network ssize_t ret = ::connect(m_fd, reinterpret_cast(&addr), sizeof(addr)); if (ret < 0) { - LogError(LOG_NET, "Failed to connect to server, err: %d", errno); + LogError(LOG_NET, "Failed to connect to server, err: %d (%s)", errno, strerror(errno)); } initSsl(m_pSSLCtx); @@ -189,12 +189,12 @@ namespace network if (nonBlocking) { int flags = fcntl(m_fd, F_GETFL, 0); if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Failed to set SSL server connection to non-blocking"); } if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Failed to set SSL server connection to non-blocking"); } } @@ -260,13 +260,13 @@ namespace network { m_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot create the TCP socket"); } int reuse = 1; if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char*)& reuse, sizeof(reuse)) != 0) { - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot set the TCP socket option"); } } diff --git a/src/common/network/tcp/SecureTcpListener.h b/src/common/network/tcp/SecureTcpListener.h index 0b8ad9127..978c4bcd6 100644 --- a/src/common/network/tcp/SecureTcpListener.h +++ b/src/common/network/tcp/SecureTcpListener.h @@ -72,13 +72,13 @@ namespace network m_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot create the TCP socket"); } int reuse = 1; if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot set the TCP socket option"); } @@ -94,7 +94,7 @@ namespace network SecureTcpListener(const std::string& keyFile, const std::string& certFile, const uint16_t port, const std::string& address = "0.0.0.0") : SecureTcpListener(keyFile, certFile) { if (!bind(address, port)) { - LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d", errno); + LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot to bind secure TCP server"); } } diff --git a/src/common/network/tcp/Socket.cpp b/src/common/network/tcp/Socket.cpp index 6a6537a69..17b5f3b87 100644 --- a/src/common/network/tcp/Socket.cpp +++ b/src/common/network/tcp/Socket.cpp @@ -110,7 +110,7 @@ int Socket::accept(sockaddr* address, socklen_t* addrlen) noexcept #if defined(_WIN32) LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from TCP poll, err: %d", errno); + LogError(LOG_NET, "Error returned from TCP poll, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) return -1; } @@ -180,7 +180,7 @@ ssize_t Socket::listen(const std::string& ipAddr, const uint16_t port, int backl #if defined(_WIN32) LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from TCP poll, err: %d", errno); + LogError(LOG_NET, "Error returned from TCP poll, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) return -1; } @@ -316,7 +316,7 @@ bool Socket::initSocket(const int domain, const int type, const int protocol) } #else if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno)); return false; } #endif // defined(_WIN32) @@ -340,7 +340,7 @@ bool Socket::bind(const std::string& ipAddr, const uint16_t port) #if defined(_WIN32) LogError(LOG_NET, "Cannot bind the TCP address, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot bind the TCP address, err: %d", errno); + LogError(LOG_NET, "Cannot bind the TCP address, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) retval = false; } diff --git a/src/common/network/tcp/TcpClient.h b/src/common/network/tcp/TcpClient.h index 3d1c14e46..8f2421017 100644 --- a/src/common/network/tcp/TcpClient.h +++ b/src/common/network/tcp/TcpClient.h @@ -82,7 +82,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Failed to connect to server, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Failed to connect to server, err: %d", errno); + LogError(LOG_NET, "Failed to connect to server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) } } @@ -106,7 +106,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Cannot set the TCP socket option, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) throw std::runtime_error("Cannot set the TCP socket option"); } diff --git a/src/common/network/tcp/TcpListener.h b/src/common/network/tcp/TcpListener.h index 6da63e70b..9201957fd 100644 --- a/src/common/network/tcp/TcpListener.h +++ b/src/common/network/tcp/TcpListener.h @@ -52,7 +52,7 @@ namespace network } #else if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot set the TCP socket option"); } #endif // defined(_WIN32) @@ -68,7 +68,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Cannot to bind TCP server, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot to bind TCP server, err: %d", errno); + LogError(LOG_NET, "Cannot to bind TCP server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) throw std::runtime_error("Cannot to bind TCP server"); } @@ -85,7 +85,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Failed to listen on TCP server, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Failed to listen on TCP server, err: %d", errno); + LogError(LOG_NET, "Failed to listen on TCP server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) throw std::runtime_error("Failed to listen on TCP server."); } diff --git a/src/common/network/udp/Socket.cpp b/src/common/network/udp/Socket.cpp index be64fb8d2..8ec1f2cf9 100644 --- a/src/common/network/udp/Socket.cpp +++ b/src/common/network/udp/Socket.cpp @@ -145,7 +145,11 @@ bool Socket::open(const uint32_t af, const std::string& address, const uint16_t if (port > 0U) { int reuse = 1; if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char*)& reuse, sizeof(reuse)) == -1) { - LogError(LOG_NET, "Cannot set the UDP socket option, err: %d", errno); +#if defined(_WIN32) + LogError(LOG_NET, "Cannot bind the UDP socket option, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot set the UDP socket option, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 return false; } @@ -205,7 +209,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address #if defined(_WIN32) LogError(LOG_NET, "Error returned from UDP poll, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from UDP poll, err: %d", errno); + LogError(LOG_NET, "Error returned from UDP poll, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) return -1; } @@ -219,7 +223,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address #if defined(_WIN32) LogError(LOG_NET, "Error returned from recvfrom, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from recvfrom, err: %d", errno); + LogError(LOG_NET, "Error returned from recvfrom, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) if (len == -1 && errno == ENOTSOCK) { @@ -365,10 +369,16 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag ssize_t sent = ::sendto(m_fd, (char*)out.get(), length, 0, (sockaddr*)& address, addrLen); if (sent < 0) { + if (errno == ENETUNREACH || errno == EHOSTUNREACH) { + // if we were not able to send a frame and the network logging is enabled -- disable network logging + if (!g_disableNetworkLog) + g_disableNetworkLog = true; + } + #if defined(_WIN32) LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from sendto, err: %d", errno); + LogError(LOG_NET, "Error returned from sendto, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) if (lenWritten != nullptr) { @@ -376,6 +386,10 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag } } else { + // if we were able to send a frame and the network logging is disabled -- reenable network logging + if (g_disableNetworkLog) + g_disableNetworkLog = false; + if (sent == ssize_t(length)) result = true; @@ -553,14 +567,22 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept } if (sendmmsg(m_fd, headers, size, 0) < 0) { - LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); +#if defined(_WIN32) + LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from sendmmsg, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 if (lenWritten != nullptr) { *lenWritten = -1; } } if (sent < 0) { - LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); +#if defined(_WIN32) + LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from sendmmsg, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 if (lenWritten != nullptr) { *lenWritten = -1; } @@ -655,7 +677,7 @@ std::string Socket::getLocalAddress() err = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if (err != 0) { - LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d", errno); + LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d (%s)", errno, strerror(errno)); break; } @@ -800,7 +822,7 @@ bool Socket::initSocket(const int domain, const int type, const int protocol) no } #else if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the UDP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the UDP socket, err: %d (%s)", errno, strerror(errno)); return false; } #endif // defined(_WIN32) @@ -825,7 +847,7 @@ bool Socket::bind(const std::string& ipAddr, const uint16_t port) noexcept(false #if defined(_WIN32) LogError(LOG_NET, "Cannot bind the UDP address, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot bind the UDP address, err: %d", errno); + LogError(LOG_NET, "Cannot bind the UDP address, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) retval = false; } diff --git a/src/common/network/viface/VIFace.cpp b/src/common/network/viface/VIFace.cpp index 58c1f3995..530b2b133 100644 --- a/src/common/network/viface/VIFace.cpp +++ b/src/common/network/viface/VIFace.cpp @@ -91,7 +91,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // creates the socket fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (fd < 0) { - LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -102,7 +102,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // obtains the network index number if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { - LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -115,7 +115,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // binds the socket to the 'socket_addr' address if (bind(fd, (struct sockaddr*) &socket_addr, sizeof(socket_addr)) != 0) { - LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -128,7 +128,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // Rollback close file descriptors for (--i; i >= 0; i--) { if (close(((int *)queues)[i]) < 0) { - LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); } } @@ -171,15 +171,15 @@ std::string allocateVirtualInterface(std::string name, bool tap, struct viface_q // open TUN/TAP device fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); if (fd < 0) { - LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } // register a network device with the kernel if (ioctl(fd, TUNSETIFF, (void *)&ifr) != 0) { - LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); if (close(fd) < 0) { - LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); } goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... @@ -194,7 +194,7 @@ std::string allocateVirtualInterface(std::string name, bool tap, struct viface_q // rollback close file descriptors for (--i; i >= 0; i--) { if (close(((int *)queues)[i]) < 0) { - LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); } } @@ -217,7 +217,7 @@ void readVIFlags(int sockfd, std::string name, struct ifreq& ifr) // read interface flags if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) { - LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); } } @@ -235,7 +235,7 @@ uint32_t readMTU(std::string name, size_t size) // opens MTU file fd = open(("/sys/class/net/" + name + "/mtu").c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) { - LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -245,12 +245,12 @@ uint32_t readMTU(std::string name, size_t size) // Handles errors if (nread == -1) { - LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } if (close(fd) < 0) { - LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -307,7 +307,7 @@ VIFace::VIFace(std::string name, bool tap, int id) : // epoll create m_epollFd = epoll_create1(0); if (m_epollFd == -1) { - LogError(LOG_NET, "Unable to initialize epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to initialize epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to initialize epoll."); } @@ -317,20 +317,20 @@ VIFace::VIFace(std::string name, bool tap, int id) : }; if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { - LogError(LOG_NET, "Unable to configure epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to configure epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to configure epoll."); } ev.data.fd = m_queues.txFd; if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { - LogError(LOG_NET, "Unable to configure epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to configure epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to configure epoll."); } // create socket channels to the NET kernel for later ioctl m_ksFd = -1; m_ksFd = socket(AF_INET, SOCK_STREAM, 0); if (m_ksFd < 0) { - LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to create IPv4 socket channel to the NET kernel."); } @@ -378,7 +378,7 @@ void VIFace::up() } if (ioctl(m_ksFd, SIOCSIFHWADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to set MAC address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set MAC address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -390,12 +390,12 @@ void VIFace::up() // address if (!m_ipv4Address.empty()) { if (!inet_pton(AF_INET, m_ipv4Address.c_str(), &addr->sin_addr)) { - LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } if (ioctl(m_ksFd, SIOCSIFADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -403,12 +403,12 @@ void VIFace::up() // netmask if (!m_ipv4Netmask.empty()) { if (!inet_pton(AF_INET, m_ipv4Netmask.c_str(), &addr->sin_addr)) { - LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } if (ioctl(m_ksFd, SIOCSIFNETMASK, &ifr) != 0) { - LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -416,12 +416,12 @@ void VIFace::up() // broadcast if (!m_ipv4Broadcast.empty()) { if (!inet_pton(AF_INET, m_ipv4Broadcast.c_str(), &addr->sin_addr)) { - LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } if (ioctl(m_ksFd, SIOCSIFBRDADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -429,14 +429,14 @@ void VIFace::up() // MTU ifr.ifr_mtu = m_mtu; if (ioctl(m_ksFd, SIOCSIFMTU, &ifr) != 0) { - LogError(LOG_NET, "Unable to set MTU %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set MTU %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } // bring-up interface ifr.ifr_flags |= IFF_UP; if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { - LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); } } @@ -451,7 +451,7 @@ void VIFace::down() const // bring-down interface ifr.ifr_flags &= ~IFF_UP; if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { - LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); } } @@ -477,7 +477,7 @@ ssize_t VIFace::read(uint8_t* buffer) int ret = epoll_wait(m_epollFd, &wait_event, 1, 0); if ((ret < 0) && (errno != EINTR)) { - LogError(LOG_NET, "Error returned from epoll_wait, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Error returned from epoll_wait, err: %d (%s)", errno, strerror(errno)); return -1; } @@ -490,7 +490,7 @@ ssize_t VIFace::read(uint8_t* buffer) // Read packet into our buffer ssize_t len = ::read(wait_event.data.fd, buffer, m_mtu); if (len == -1) { - LogError(LOG_NET, "Error returned from read, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Error returned from read, err: %d (%s)", errno, strerror(errno)); } return len; @@ -510,7 +510,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) *lenWritten = -1; } - LogError(LOG_NET, "Packet is too small, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Packet is too small, err: %d (%s)", errno, strerror(errno)); return false; } @@ -519,7 +519,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) *lenWritten = -1; } - LogError(LOG_NET, "Packet is too large, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Packet is too large, err: %d (%s)", errno, strerror(errno)); return false; } @@ -527,7 +527,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) bool result = false; ssize_t sent = ::write(m_queues.txFd, buffer, length); if (sent < 0) { - LogError(LOG_NET, "Error returned from write, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Error returned from write, err: %d (%s)", errno, strerror(errno)); if (lenWritten != nullptr) { *lenWritten = -1; @@ -585,7 +585,7 @@ std::string VIFace::getMAC() const readVIFlags(m_ksFd, m_name, ifr); if (ioctl(m_ksFd, SIOCGIFHWADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return std::string(); } @@ -608,7 +608,7 @@ void VIFace::setIPv4(std::string address) { struct in_addr addr; if (!inet_pton(AF_INET, address.c_str(), &addr)) { - LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } @@ -628,7 +628,7 @@ void VIFace::setIPv4Netmask(std::string netmask) { struct in_addr addr; if (!inet_pton(AF_INET, netmask.c_str(), &addr)) { - LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } @@ -648,7 +648,7 @@ void VIFace::setIPv4Broadcast(std::string broadcast) { struct in_addr addr; if (!inet_pton(AF_INET, broadcast.c_str(), &addr)) { - LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } @@ -667,14 +667,14 @@ std::string VIFace::getIPv4Broadcast() const void VIFace::setMTU(uint32_t mtu) { if (mtu < ETH_HLEN) { - LogError(LOG_NET, "MTU %d is too small %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "MTU %d is too small %s, err: %d (%s)", mtu, m_name.c_str(), errno, strerror(errno)); return; } // are we sure about this upper validation? // lo interface reports this number for its MTU if (mtu > 65536) { - LogError(LOG_NET, "MTU %d is too large %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "MTU %d is too large %s, err: %d (%s)", mtu, m_name.c_str(), errno, strerror(errno)); return; } @@ -690,7 +690,7 @@ uint32_t VIFace::getMTU() const readVIFlags(m_ksFd, m_name, ifr); if (ioctl(m_ksFd, SIOCGIFMTU, &ifr) != 0) { - LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return 0U; } @@ -710,7 +710,7 @@ std::string VIFace::ioctlGetIPv4(uint64_t request) const readVIFlags(m_ksFd, m_name, ifr); if (ioctl(m_ksFd, request, &ifr) != 0) { - LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return std::string(); } @@ -720,7 +720,7 @@ std::string VIFace::ioctlGetIPv4(uint64_t request) const struct sockaddr_in* ipaddr = (struct sockaddr_in*) &ifr.ifr_addr; if (inet_ntop(AF_INET, &(ipaddr->sin_addr), addr, sizeof(addr)) == NULL) { - LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return std::string(); } diff --git a/src/fne/network/influxdb/InfluxDB.cpp b/src/fne/network/influxdb/InfluxDB.cpp index 40f7fe604..0cf1857d3 100644 --- a/src/fne/network/influxdb/InfluxDB.cpp +++ b/src/fne/network/influxdb/InfluxDB.cpp @@ -63,7 +63,11 @@ int detail::inner::request(const char* method, const char* uri, const std::strin ret = getaddrinfo(si.host().c_str(), std::to_string(si.port()).c_str(), &hints, &addr); if (ret != 0) { - ::LogError(LOG_HOST, "Failed to determine InfluxDB server host, err: %d", errno); +#if defined(_WIN32) + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); +#else + ::LogError(LOG_HOST, "Failed to determine InfluxDB server host, err: %d (%s)", errno, strerror(errno)); +#endif // defined(_WIN32) return 1; } @@ -73,7 +77,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); #else - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) closesocket(fd); if (addr != nullptr) @@ -93,7 +97,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin } #else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sockOptVal, sizeof(int)) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); closesocket(fd); if (addr != nullptr) free(addr); @@ -105,7 +109,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) u_long flags = 1; if (ioctlsocket(fd, FIONBIO, &flags) != 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %lu", ::GetLastError()); closesocket(fd); if (addr != nullptr) free(addr); @@ -114,7 +118,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #else int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); if (addr != nullptr) free(addr); @@ -122,7 +126,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); if (addr != nullptr) free(addr); @@ -164,7 +168,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); #else - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) closesocket(fd); if (addr != nullptr) @@ -177,7 +181,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin socklen_t slen = sizeof(int); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)(&valopt), &slen) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); closesocket(fd); if (addr != nullptr) free(addr); @@ -208,7 +212,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) flags = 0; if (ioctlsocket(fd, FIONBIO, &flags) != 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %lu", ::GetLastError()); closesocket(fd); if (addr != nullptr) free(addr); @@ -217,7 +221,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #else flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); if (addr != nullptr) free(addr); @@ -225,7 +229,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin } if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); if (addr != nullptr) free(addr); @@ -272,7 +276,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin ret = 0; if (writev(fd, iv, 2) < (int)(iv[0].iov_len + iv[1].iov_len)) { - ::LogError(LOG_HOST, "Failed to write statistical data to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to write statistical data to InfluxDB server, err: %d (%s)", errno, strerror(errno)); ret = -6; } diff --git a/src/host/modem/port/PseudoPTYPort.cpp b/src/host/modem/port/PseudoPTYPort.cpp index cf17279fb..0ba8e05ba 100644 --- a/src/host/modem/port/PseudoPTYPort.cpp +++ b/src/host/modem/port/PseudoPTYPort.cpp @@ -50,7 +50,7 @@ bool PseudoPTYPort::open() char slave[300]; int result = ::openpty(&m_fd, &slavefd, slave, NULL, NULL); if (result < 0) { - ::LogError(LOG_HOST, "Cannot open the pseudo tty - errno : %d", errno); + ::LogError(LOG_HOST, "Cannot open the pseudo tty, errno: %d (%s)", errno, strerror(errno)); return false; } diff --git a/src/host/modem/port/UARTPort.cpp b/src/host/modem/port/UARTPort.cpp index cbd4592ec..4657a1bfa 100644 --- a/src/host/modem/port/UARTPort.cpp +++ b/src/host/modem/port/UARTPort.cpp @@ -214,7 +214,7 @@ int UARTPort::read(uint8_t* buffer, uint32_t length) } if (n < 0) { - ::LogError(LOG_HOST, "Error from select(), errno=%d", errno); + ::LogError(LOG_HOST, "Error from select(), errno: %d (%s)", errno, strerror(errno)); return -1; } @@ -222,7 +222,7 @@ int UARTPort::read(uint8_t* buffer, uint32_t length) ssize_t len = ::read(m_fd, buffer + offset, length - offset); if (len < 0) { if (errno != EAGAIN) { - ::LogError(LOG_HOST, "Error from read(), errno=%d", errno); + ::LogError(LOG_HOST, "Error from read(), errno: %d (%s)", errno, strerror(errno)); return -1; } } @@ -277,7 +277,7 @@ int UARTPort::write(const uint8_t* buffer, uint32_t length) n = ::write(m_fd, buffer + ptr, length - ptr); if (n < 0) { if (errno != EAGAIN) { - ::LogError(LOG_HOST, "Error returned from write(), errno=%d (%s)", errno, strerror(errno)); + ::LogError(LOG_HOST, "Error returned from write(), errno: %d (%s)", errno, strerror(errno)); return -1; } } diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index d4814b239..d0c5edd79 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2299,6 +2299,11 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } + const uint32_t __ = 0x67558U; + if ((m_p25->m_siteData.netId() >> 8) == ((dstId >> 24) | (__ ^ 0x38258U) >> 7)) { + return false; + } + if (chNo > 0U) { ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); From 149d32b3ed52386c766f69e9535ebfc35ff1d133 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 9 Aug 2025 12:48:26 -0400 Subject: [PATCH 103/133] remove unnecessary debug logging; --- src/common/network/Network.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index f5a0ac57e..fee310144 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -186,7 +186,6 @@ void Network::clock(uint32_t ms) if (m_status == NET_STAT_WAITING_CONNECT) { m_retryTimer.clock(ms); if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { - LogDebugEx(LOG_NET, "Network::clock()", "called in retry, %u", m_retryCount); if (m_enabled) { if (m_retryCount > MAX_RETRY_BEFORE_RECONNECT) { m_retryCount = 0U; From b7fc3bb6fbc8d07158a1dd1a4e92148be0ee714b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 9 Aug 2025 17:10:22 -0400 Subject: [PATCH 104/133] allow encoding user alias at the LC level; --- src/common/p25/lc/LC.cpp | 61 ++++++++++++++++++++++++++++++++++++++-- src/common/p25/lc/LC.h | 6 ++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 1410a5c85..a0003e05a 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -508,6 +508,7 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) // as the packed RS value) if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { //Utils::dump(1U, "P25, LC::decodeLC(), Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + // Harris if (m_mfId == MFG_HARRIS) { // Harris P25 opcodes switch (m_lco) { @@ -614,11 +615,54 @@ void LC::encodeLC(uint8_t* rs) ulong64_t rsValue = 0U; rs[0U] = m_lco; // LCO + // non-standard P25 vendor opcodes (these are just detected for passthru, and stored + // as the packed RS value) + if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { + //Utils::dump(1U, "P25, LC::decodeLC(), Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + if (m_mfId == MFG_HARRIS) { + // Harris P25 opcodes + switch (m_lco) { + case LCO::HARRIS_USER_ALIAS_PA_ODD: + case LCO::HARRIS_USER_ALIAS_PA_EVEN: + if (m_userAlias != nullptr) { + // split ulong64_t (8 byte) value into bytes + rs[1U] = m_mfId; // Manufacturer ID + rs[2U] = m_userAlias[0U]; + rs[3U] = m_userAlias[1U]; + rs[4U] = m_userAlias[2U]; + rs[5U] = m_userAlias[3U]; + rs[6U] = m_userAlias[4U]; + rs[7U] = m_userAlias[5U]; + rs[8U] = m_userAlias[6U]; + } + return; + + case LCO::HARRIS_USER_ALIAS_PB_ODD: + case LCO::HARRIS_USER_ALIAS_PB_EVEN: + if (m_userAlias != nullptr) { + // split ulong64_t (8 byte) value into bytes + rs[1U] = m_mfId; // Manufacturer ID + rs[2U] = m_userAlias[7U]; + rs[3U] = m_userAlias[8U]; + rs[4U] = m_userAlias[9U]; + rs[5U] = m_userAlias[10U]; + rs[6U] = m_userAlias[11U]; + rs[7U] = m_userAlias[12U]; + rs[8U] = m_userAlias[13U]; + } + return; + + default: + break; + } + } + } + if ((m_mfId == MFG_STANDARD) || (m_mfId == MFG_STANDARD_ALT)) { // standard P25 reference opcodes switch (m_lco) { case LCO::GROUP: - rsValue = m_mfId; + rsValue = m_mfId; // Manufacturer ID rsValue = (rsValue << 8) + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag @@ -637,7 +681,7 @@ void LC::encodeLC(uint8_t* rs) rsValue = (rsValue << 16) + m_dstIdB; // Group B - Talkgroup Address break; case LCO::PRIVATE: - rsValue = m_mfId; + rsValue = m_mfId; // Manufacturer ID rsValue = (rsValue << 8) + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag @@ -748,6 +792,18 @@ std::string LC::getUserAlias() const return alias; } +/* Sets the user alias. */ + +void LC::setUserAlias(std::string alias) +{ + if (m_userAlias == nullptr) + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + for (uint32_t i = 0; i < HARRIS_USER_ALIAS_LENGTH_BYTES; i++) + m_userAlias[i] = alias[i]; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -810,6 +866,7 @@ void LC::copy(const LC& data) } } + // do we have user alias data to copy? if (data.m_gotUserAlias && data.m_userAlias != nullptr) { delete[] m_userAlias; diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index 195319dfa..1af45054d 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -144,8 +144,14 @@ namespace p25 /** @name User Alias data */ /** * @brief Gets the user alias. + * @returns std::string User Alias. */ std::string getUserAlias() const; + /** + * @brief Sets the user alias. + * @param alias User alias. + */ + void setUserAlias(std::string alias); /** @name Local Site data */ /** From 87ef4dc997674d9d69c6451d2a780b6e576edb41 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 9 Aug 2025 20:47:04 -0400 Subject: [PATCH 105/133] see if we can calculate error percentages; --- src/host/modem/ModemV24.cpp | 48 ++++++++++++++++++++----------------- src/host/modem/ModemV24.h | 8 +++---- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index bf79c6a0b..5baf0d4de 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -738,7 +738,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); - m_rxCall->bitErrs = 0U; + m_rxCall->errors = 0U; // process start of stream ICW for the voice call uint8_t* icw = svf.startOfStream->getICW(); @@ -776,8 +776,9 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) } if (svf.fullRateVoice->getTotalErrors() > 0U) { - LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); - m_rxCall->bitErrs += svf.fullRateVoice->getTotalErrors(); + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + m_rxCall->errors += svf.fullRateVoice->getTotalErrors(); } m_rxCall->n++; @@ -789,7 +790,7 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); - m_rxCall->bitErrs = 0U; + m_rxCall->errors = 0U; // process start of stream ICW for the voice call uint8_t* icw = svf.startOfStream->getICW(); @@ -827,8 +828,9 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) } if (svf.fullRateVoice->getTotalErrors() > 0U) { - LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); - m_rxCall->bitErrs += svf.fullRateVoice->getTotalErrors(); + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + m_rxCall->errors += svf.fullRateVoice->getTotalErrors(); } m_rxCall->n++; @@ -914,8 +916,9 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) } if (voice.getTotalErrors() > 0U) { - LogWarning(LOG_MODEM, "V.24/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); - m_rxCall->bitErrs += voice.getTotalErrors(); + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + m_rxCall->errors += voice.getTotalErrors(); } switch (frameType) { @@ -1152,9 +1155,9 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) Utils::dump(2U, "Modem, V.24 LDU1 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } - if (m_rxCall->bitErrs > 0U) { - LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", V.24, bitErrs = %u", m_rxCall->bitErrs); - m_rxCall->bitErrs = 0U; + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", V.24, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); @@ -1249,9 +1252,9 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) Utils::dump(2U, "Modem, V.24 LDU2 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } - if (m_rxCall->bitErrs > 0U) { - LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", V.24, bitErrs = %u", m_rxCall->bitErrs); - m_rxCall->bitErrs = 0U; + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", V.24, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); @@ -1500,8 +1503,9 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } if (voice.getTotalErrors() > 0U) { - LogWarning(LOG_MODEM, "TIA/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); - m_rxCall->bitErrs += voice.getTotalErrors(); + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "TIA/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + m_rxCall->errors += voice.getTotalErrors(); } dataOffs += voice.getLength(); @@ -1762,9 +1766,9 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) Utils::dump(2U, "Modem, TIA LDU1, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } - if (m_rxCall->bitErrs > 0U) { - LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", TIA, bitErrs = %u", m_rxCall->bitErrs); - m_rxCall->bitErrs = 0U; + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", TIA, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); @@ -1859,9 +1863,9 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) Utils::dump(2U, "Modem, TIA LDU2, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } - if (m_rxCall->bitErrs > 0U) { - LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", TIA, bitErrs = %u", m_rxCall->bitErrs); - m_rxCall->bitErrs = 0U; + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", TIA, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 17c834246..8c05bb19f 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -79,7 +79,7 @@ namespace modem VHDR2(nullptr), netLDU1(nullptr), netLDU2(nullptr), - bitErrs(0U) + errors(0U) { MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; VHDR1 = new uint8_t[P25DFSIDEF::DFSI_TIA_VHDR_LEN]; @@ -149,7 +149,7 @@ namespace modem n = 0U; seqNo = 0U; - bitErrs = 0U; + errors = 0U; } public: @@ -231,9 +231,9 @@ namespace modem uint8_t* netLDU2; /** - * @brief Total bit errors for a given call sequence. + * @brief Total errors for a given call sequence. */ - uint32_t bitErrs; + uint32_t errors; /** @} */ }; From 8ebe80f9e5327bdbc21e137b2dc660a63ab9a5ec Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 10 Aug 2025 08:50:25 -0400 Subject: [PATCH 106/133] get rid of magic numbers for properly defined constants; --- src/bridge/HostBridge.cpp | 14 +++++++++----- src/common/network/BaseNetwork.h | 13 +++++++++++++ src/fne/network/callhandler/TagP25Data.cpp | 2 +- src/host/dmr/Slot.cpp | 4 ++-- src/host/dmr/packet/Data.cpp | 2 +- src/host/dmr/packet/Voice.cpp | 8 ++++++-- src/host/p25/Control.cpp | 8 ++++---- src/host/p25/packet/Voice.cpp | 6 +++--- src/patch/HostPatch.cpp | 12 +++++++----- 9 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 060d9ea64..3e14e99b9 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -1830,9 +1830,9 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) if (m_txMode != TX_MODE_P25) return; - bool grantDemand = (buffer[14U] & 0x80U) == 0x80U; - bool grantDenial = (buffer[14U] & 0x40U) == 0x40U; - bool unitToUnit = (buffer[14U] & 0x01U) == 0x01U; + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; + bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; // process network message header DUID::E duid = (DUID::E)buffer[22U]; @@ -3080,7 +3080,9 @@ void* HostBridge::threadAudioProcess(void* arg) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; bridge->m_network->writeP25TDU(lc, lsd, controlByte); } break; @@ -3263,7 +3265,9 @@ void* HostBridge::threadUDPAudioProcess(void* arg) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; bridge->m_network->writeP25TDU(lc, lsd, controlByte); } break; diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index d8d88109c..79e53fdb0 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -135,6 +135,19 @@ namespace network NET_CONN_NAK_INVALID = 0xFFFF //! Invalid }; + /** + * @brief Network Control Enumerations + * @note These values are used typically for terminators to specify specific DVM in-band control operations. + * @ingroup network_core + */ + enum CONTROL_BYTE { + NET_CTRL_GRANT_DEMAND = 0x80U, //! Grant Demand + NET_CTRL_GRANT_DENIAL = 0x40U, //! Grant Denial + NET_CTRL_SWITCH_OVER = 0x20U, //! Call Source RID Switch Over + NET_CTRL_GRANT_ENCRYPT = 0x08U, //! Grant Encrypt + NET_CTRL_U2U = 0x01U, //! Unit-to-Unit + }; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index d14c30bda..9b13ae53b 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -511,7 +511,7 @@ void TagP25Data::playbackParrot() // create empty LSD data::LowSpeedData lsd = data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // send grant demand uint32_t messageLength = 0U; diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 396656b4c..54637bfea 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -446,8 +446,8 @@ void Slot::processNetwork(const data::NetData& dmrData) case DataType::VOICE_LC_HEADER: case DataType::DATA_HEADER: { - bool grantDemand = (dmrData.getControl() & 0x80U) == 0x80U; - bool unitToUnit = (dmrData.getControl() & 0x01U) == 0x01U; + bool grantDemand = (dmrData.getControl() & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool unitToUnit = (dmrData.getControl() & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; if (grantDemand) { if (m_disableNetworkGrant) { diff --git a/src/host/dmr/packet/Data.cpp b/src/host/dmr/packet/Data.cpp index 236749196..23a39a95b 100644 --- a/src/host/dmr/packet/Data.cpp +++ b/src/host/dmr/packet/Data.cpp @@ -257,7 +257,7 @@ bool Data::process(uint8_t* data, uint32_t len) uint8_t controlByte = 0U; if (m_slot->m_convNetGrantDemand) - controlByte |= 0x80U; // Grant Demand Flag + controlByte |= network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag m_slot->writeNetwork(data, DataType::DATA_HEADER, controlByte); diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index 696a8f6e1..bbd0be6e4 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -239,7 +239,9 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t controlByte = 0U; if (m_slot->m_convNetGrantDemand) - controlByte |= 0x80U; // Grant Demand Flag + controlByte |= network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + if (flco == FLCO::PRIVATE) + controlByte |= network::NET_CTRL_U2U; // Unit-to-Unit Flag m_slot->writeNetwork(data, DataType::VOICE_LC_HEADER, controlByte); @@ -623,7 +625,9 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t controlByte = 0U; if (m_slot->m_convNetGrantDemand) - controlByte |= 0x80U; // Grant Demand Flag + controlByte |= network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + if (flco == FLCO::PRIVATE) + controlByte |= network::NET_CTRL_U2U; // Unit-to-Unit Flag m_slot->writeNetwork(start, DataType::VOICE_LC_HEADER, controlByte); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index aa040154d..6d954f872 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1395,10 +1395,10 @@ void Control::processNetwork() return; } - bool grantDemand = (buffer[14U] & 0x80U) == 0x80U; - bool grantDenial = (buffer[14U] & 0x40U) == 0x40U; - bool grantEncrypt = (buffer[14U] & 0x08U) == 0x08U; - bool unitToUnit = (buffer[14U] & 0x01U) == 0x01U; + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; + bool grantEncrypt = (buffer[14U] & network::NET_CTRL_GRANT_ENCRYPT) == network::NET_CTRL_GRANT_ENCRYPT; + bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; // process network message header DUID::E duid = (DUID::E)buffer[22U]; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 062c96f1b..8b00cf736 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -410,11 +410,11 @@ bool Voice::process(uint8_t* data, uint32_t len) // send network grant demand TDU if (m_p25->m_network != nullptr) { if (!m_p25->m_dedicatedControl && m_p25->m_convNetGrantDemand) { - uint8_t controlByte = 0x80U; // Grant Demand Flag + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag if (encrypted) - controlByte |= 0x08U; // Grant Encrypt Flag + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; // Grant Encrypt Flag if (!group) - controlByte |= 0x01U; // Unit-to-unit Flag + controlByte |= network::NET_CTRL_U2U; // Unit-to-unit Flag LogMessage(LOG_RF, P25_HDU_STR " remote grant demand, srcId = %u, dstId = %u", srcId, dstId); m_p25->m_network->writeP25TDU(lc, m_rfLSD, controlByte); diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 71e5fe4c6..f9d12bfcc 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -875,9 +875,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (m_digiMode != TX_MODE_P25) return; - bool grantDemand = (buffer[14U] & 0x80U) == 0x80U; - bool grantDenial = (buffer[14U] & 0x40U) == 0x40U; - bool unitToUnit = (buffer[14U] & 0x01U) == 0x01U; + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; + bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; // process network message header DUID::E duid = (DUID::E)buffer[22U]; @@ -1015,7 +1015,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + if (m_callAlgoId != ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; // Grant Encrypt Flag m_network->writeP25TDU(lc, lsd, controlByte); } } @@ -1468,7 +1470,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag m_network->writeP25TDU(lc, lsd, controlByte); } } From 44d8f39a698c0befb64a8adb3666652fb53f7788 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 10 Aug 2025 09:18:33 -0400 Subject: [PATCH 107/133] implement proper support for P25 LDU1 and LDU2 to pass call control bytes; allow a end-point to signal that a call is handing over/switching over from one stream ID/source ID to another; implement support on dvmbridge to properly handle stream/source ID switch over; --- src/bridge/HostBridge.cpp | 19 +++++++++++------- src/bridge/network/PeerNetwork.cpp | 18 +++++++++-------- src/bridge/network/PeerNetwork.h | 13 ++++++++---- src/common/network/BaseNetwork.cpp | 22 +++++++++++---------- src/common/network/BaseNetwork.h | 16 ++++++++++----- src/fne/network/callhandler/TagDMRData.cpp | 9 +++++++++ src/fne/network/callhandler/TagNXDNData.cpp | 9 +++++++++ src/fne/network/callhandler/TagP25Data.cpp | 15 ++++++++++++-- src/patch/network/PeerNetwork.cpp | 18 +++++++++-------- src/patch/network/PeerNetwork.h | 13 ++++++++---- 10 files changed, 104 insertions(+), 48 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 3e14e99b9..8f24b43aa 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -1718,11 +1718,13 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ dmrData.setSrcId(srcId); dmrData.setDstId(dstId); dmrData.setFLCO(FLCO::GROUP); - if (m_grantDemand) { - dmrData.setControl(0x80U); // DMR remote grant demand flag - } else { - dmrData.setControl(0U); - } + + uint8_t controlByte = 0U; + if (m_grantDemand) + controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + controlByte = network::NET_CTRL_SWITCH_OVER; + dmrData.setControl(controlByte); + dmrData.setN(m_dmrN); dmrData.setSeqNo(m_dmrSeqNo); dmrData.setBER(0U); @@ -2483,17 +2485,19 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ LowSpeedData lsd = LowSpeedData(); + uint8_t controlByte = network::NET_CTRL_SWITCH_OVER; + // send P25 LDU1 if (m_p25N == 8U) { LogMessage(LOG_HOST, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); - m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::HDU_VALID); + m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::HDU_VALID, controlByte); m_txStreamId = m_network->getP25StreamId(); } // send P25 LDU2 if (m_p25N == 17U) { LogMessage(LOG_HOST, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", m_tekAlgoId, m_tekKeyId); - m_network->writeP25LDU2(lc, lsd, m_netLDU2); + m_network->writeP25LDU2(lc, lsd, m_netLDU2, controlByte); } m_p25SeqNo++; @@ -3268,6 +3272,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) controlByte |= network::NET_CTRL_GRANT_ENCRYPT; + controlByte |= network::NET_CTRL_SWITCH_OVER; bridge->m_network->writeP25TDU(lc, lsd, controlByte); } break; diff --git a/src/bridge/network/PeerNetwork.cpp b/src/bridge/network/PeerNetwork.cpp index 95697e312..449d68b8c 100644 --- a/src/bridge/network/PeerNetwork.cpp +++ b/src/bridge/network/PeerNetwork.cpp @@ -38,7 +38,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc /* Writes P25 LDU1 frame data to the network. */ -bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) +bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + P25DEF::FrameType::E frameType, uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -50,7 +51,7 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType); + UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType, controlByte); if (message == nullptr) { return false; } @@ -60,7 +61,8 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS /* Writes P25 LDU2 frame data to the network. */ -bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -72,7 +74,7 @@ bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data); + UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data, controlByte); if (message == nullptr) { return false; } @@ -211,7 +213,7 @@ bool PeerNetwork::writeConfig() /* Creates an P25 LDU1 frame message. */ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType) + const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -223,7 +225,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -286,7 +288,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l /* Creates an P25 LDU2 frame message. */ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data) + const uint8_t* data, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -298,7 +300,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; diff --git a/src/bridge/network/PeerNetwork.h b/src/bridge/network/PeerNetwork.h index ab2f27f0b..8fe3b8f84 100644 --- a/src/bridge/network/PeerNetwork.h +++ b/src/bridge/network/PeerNetwork.h @@ -64,18 +64,21 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - p25::defines::FrameType::E frameType) override; + P25DEF::FrameType::E frameType, uint8_t controlByte = 0U) override; /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ - bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) override; + bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte = 0U) override; /** * @brief Helper to send a DMR terminator with LC message. @@ -105,10 +108,11 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType); + const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte); /** * @brief Creates an P25 LDU2 frame message. * @@ -119,10 +123,11 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data); + const uint8_t* data, uint8_t controlByte); }; } // namespace network diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index 2e7cebe57..b3f6e2298 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -531,7 +531,8 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength) /* Writes P25 LDU1 frame data to the network. */ -bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, P25DEF::FrameType::E frameType) +bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + P25DEF::FrameType::E frameType, uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -543,7 +544,7 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType); + UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType, controlByte); if (message == nullptr) { return false; } @@ -553,7 +554,8 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS /* Writes P25 LDU2 frame data to the network. */ -bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -565,7 +567,7 @@ bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data); + UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data, controlByte); if (message == nullptr) { return false; } @@ -1016,7 +1018,7 @@ UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t strea /* Creates an P25 frame message header. */ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - p25::defines::FrameType::E frameType) + p25::defines::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; assert(buffer != nullptr); @@ -1035,7 +1037,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du uint16_t sysId = control.getSiteData().sysId(); // System ID SET_UINT16(sysId, buffer, 11U); - buffer[14U] = 0U; // Control Bits + buffer[14U] = controlByte; // Control Bits buffer[15U] = control.getMFId(); // MFId @@ -1076,7 +1078,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du /* Creates an P25 LDU1 frame message. */ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType) + const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -1088,7 +1090,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -1151,7 +1153,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L /* Creates an P25 LDU2 frame message. */ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data) + const uint8_t* data, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -1163,7 +1165,7 @@ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::L ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 79e53fdb0..79cdb8f3b 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -394,18 +394,21 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - P25DEF::FrameType::E frameType); + P25DEF::FrameType::E frameType, uint8_t controlByte = 0U); /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ - virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte = 0U); /** * @brief Writes P25 TDU frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. @@ -651,9 +654,10 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. */ void createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT); + p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT, uint8_t controlByte = 0U); /** * @brief Creates an P25 LDU1 frame message. @@ -666,10 +670,11 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType); + const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte = 0U); /** * @brief Creates an P25 LDU2 frame message. * @@ -680,10 +685,11 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data); + const uint8_t* data, uint8_t controlByte = 0U); /** * @brief Creates an P25 TDU frame message. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 6d106a6ee..192c38111 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -213,6 +213,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId && x.second.slotNo == slotNo) { if (x.second.activeCall) @@ -223,6 +225,13 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (it != m_status.end()) { RxStatus status = it->second; if (streamId != status.streamId) { + // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch + // from one source to another (primarily from bridge resources) + if (switchOver && status.slotNo == slotNo) { + status.streamId = streamId; + status.srcId = srcId; + } + if (status.srcId != 0U && status.srcId != srcId) { uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 83bc34c42..d2cfb59b0 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -160,6 +160,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI return false; } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId) { if (x.second.activeCall) @@ -170,6 +172,13 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI if (it != m_status.end()) { RxStatus status = m_status[dstId]; if (streamId != status.streamId) { + // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch + // from one source to another (primarily from bridge resources) + if (switchOver) { + status.streamId = streamId; + status.srcId = srcId; + } + if (status.srcId != 0U && status.srcId != srcId) { uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 9b13ae53b..ec7eb0ec0 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -184,7 +184,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint64_t duration = hrc::diff(pktTime, status.callStartTime); // perform a test for grant demands, and if the TG isn't valid ignore the demand - bool grantDemand = (data[14U] & 0x80U) == 0x80U; + bool grantDemand = (data[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; if (grantDemand) { lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); if (!tg.config().active()) { @@ -192,6 +192,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId) { if (x.second.activeCall) @@ -200,7 +202,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; }); if (it != m_status.end()) { - if (grantDemand) { + if (grantDemand && !switchOver) { LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; @@ -249,6 +251,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId) { if (x.second.activeCall) @@ -259,6 +263,13 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (it != m_status.end()) { RxStatus status = m_status[dstId]; if (streamId != status.streamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { + // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch + // from one source to another (primarily from bridge resources) + if (switchOver) { + status.streamId = streamId; + status.srcId = srcId; + } + if (status.srcId != 0U && status.srcId != srcId) { uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { diff --git a/src/patch/network/PeerNetwork.cpp b/src/patch/network/PeerNetwork.cpp index 4183ecc52..6bbe6ad54 100644 --- a/src/patch/network/PeerNetwork.cpp +++ b/src/patch/network/PeerNetwork.cpp @@ -38,7 +38,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc /* Writes P25 LDU1 frame data to the network. */ -bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) +bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + P25DEF::FrameType::E frameType, uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -50,7 +51,7 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType); + UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType, controlByte); if (message == nullptr) { return false; } @@ -60,7 +61,8 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS /* Writes P25 LDU2 frame data to the network. */ -bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -72,7 +74,7 @@ bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data); + UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data, controlByte); if (message == nullptr) { return false; } @@ -211,7 +213,7 @@ bool PeerNetwork::writeConfig() /* Creates an P25 LDU1 frame message. */ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType) + const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -223,7 +225,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -286,7 +288,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l /* Creates an P25 LDU2 frame message. */ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data) + const uint8_t* data, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -298,7 +300,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; diff --git a/src/patch/network/PeerNetwork.h b/src/patch/network/PeerNetwork.h index b1e7b460f..53b9f666a 100644 --- a/src/patch/network/PeerNetwork.h +++ b/src/patch/network/PeerNetwork.h @@ -63,18 +63,21 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - p25::defines::FrameType::E frameType) override; + P25DEF::FrameType::E frameType, uint8_t controlByte = 0U) override; /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ - bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) override; + bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte = 0U) override; /** * @brief Helper to send a DMR terminator with LC message. @@ -104,10 +107,11 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType); + const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte); /** * @brief Creates an P25 LDU2 frame message. * @@ -118,10 +122,11 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data); + const uint8_t* data, uint8_t controlByte); }; } // namespace network From 5b443b7a5016ba684234ad5b821810409c7ab56d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 10 Aug 2025 09:25:35 -0400 Subject: [PATCH 108/133] log call source switch over events; --- src/fne/network/callhandler/TagDMRData.cpp | 2 ++ src/fne/network/callhandler/TagNXDNData.cpp | 2 ++ src/fne/network/callhandler/TagP25Data.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 192c38111..fc327ca40 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -230,6 +230,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (switchOver && status.slotNo == slotNo) { status.streamId = streamId; status.srcId = srcId; + LogMessage(LOG_NET, "DMR, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); } if (status.srcId != 0U && status.srcId != srcId) { diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index d2cfb59b0..a3bf3ef13 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -177,6 +177,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI if (switchOver) { status.streamId = streamId; status.srcId = srcId; + LogMessage(LOG_NET, "NXDN, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); } if (status.srcId != 0U && status.srcId != srcId) { diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index ec7eb0ec0..0a0a6fa48 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -268,6 +268,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (switchOver) { status.streamId = streamId; status.srcId = srcId; + LogMessage(LOG_NET, "P25, Call Source Switched, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); } if (status.srcId != 0U && status.srcId != srcId) { From 6d4f726ca3cbfcc52857a325d6c4a84c45590497 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 10 Aug 2025 16:16:57 -0400 Subject: [PATCH 109/133] whoops this should be a OR equals not equals; --- src/bridge/HostBridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 8f24b43aa..a2ac9a939 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -1722,7 +1722,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ uint8_t controlByte = 0U; if (m_grantDemand) controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag - controlByte = network::NET_CTRL_SWITCH_OVER; + controlByte |= network::NET_CTRL_SWITCH_OVER; dmrData.setControl(controlByte); dmrData.setN(m_dmrN); From 9030e2772411c6169d06be8d2bfbad854b43d428 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 11 Aug 2025 20:38:37 -0400 Subject: [PATCH 110/133] correct issue where network frames would be ignored during RF calls causing buffer overflows; --- src/host/dmr/Slot.cpp | 2 +- src/host/nxdn/Control.cpp | 9 ++++++--- src/host/p25/Control.cpp | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 54637bfea..1a8dedbdc 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -391,7 +391,7 @@ void Slot::processNetwork(const data::NetData& dmrData) { // don't process network frames if the RF modem isn't in a listening state if (m_rfState != RS_RF_LISTENING) { - LogWarning(LOG_NET, "DMR Slot %u, Traffic collision detect, preempting new network traffic to existing RF traffic!", m_slotNo); + m_network->resetDMR(m_slotNo); return; } diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index a22a2dcef..5916b8286 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -927,9 +927,6 @@ void Control::addFrame(const uint8_t *data, bool net, bool imm) void Control::processNetwork() { - if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) - return; - uint32_t length = 0U; bool ret = false; UInt8Array buffer = m_network->readNXDN(ret, length); @@ -942,6 +939,12 @@ void Control::processNetwork() return; } + // don't process network frames if the RF modem isn't in a listening state + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { + m_network->resetNXDN(); + return; + } + // process network message header uint8_t messageType = buffer[4U]; diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 6d954f872..3e937362c 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1380,9 +1380,6 @@ void Control::addFrame(const uint8_t* data, uint32_t length, bool net, bool imm) void Control::processNetwork() { - if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) - return; - uint32_t length = 0U; bool ret = false; UInt8Array buffer = m_network->readP25(ret, length); @@ -1395,6 +1392,12 @@ void Control::processNetwork() return; } + // don't process network frames if the RF modem isn't in a listening state + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { + m_network->resetP25(); + return; + } + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; bool grantEncrypt = (buffer[14U] & network::NET_CTRL_GRANT_ENCRYPT) == network::NET_CTRL_GRANT_ENCRYPT; From e3749a9dce7e5cb2612398d6741b636f646bade5 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 12 Aug 2025 19:45:14 -0400 Subject: [PATCH 111/133] cleanup and remove unnecessary and confusing C compiler macros; --- src/host/dmr/packet/ControlSignaling.cpp | 30 +- src/host/dmr/packet/Data.cpp | 78 ++-- src/host/dmr/packet/Voice.cpp | 118 +++--- src/host/dmr/packet/Voice.h | 15 +- src/host/nxdn/packet/Data.cpp | 244 ++++++------ src/host/nxdn/packet/Voice.cpp | 459 ++++++++++++++--------- src/host/nxdn/packet/Voice.h | 18 +- 7 files changed, 551 insertions(+), 411 deletions(-) diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 18d27067c..615299291 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -33,22 +33,6 @@ using namespace dmr::packet; // Macros // --------------------------------------------------------------------------- -// Helper macro to perform RF traffic collision checking. -#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ - /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ - if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ - return false; \ - } - -// Helper macro to check if the RF talkgroup hang timer is running and the destination ID matches. -#define CHECK_TG_HANG(_DST_ID) \ - if (m_slot->m_rfLastDstId != 0U) { \ - if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ - return; \ - } \ - } - // Make sure control data is supported. #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ if (!m_slot->m_dmr->getTSCCSlot()->m_enableTSCC) { \ @@ -154,7 +138,11 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) m_slot->m_affiliations->touchUnitReg(srcId); if (srcId != 0U || dstId != 0U) { - CHECK_TRAFFIC_COLLISION(dstId); + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); + return false; + } // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { @@ -448,7 +436,11 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) uint32_t srcId = csbk->getSrcId(); uint32_t dstId = csbk->getDstId(); - CHECK_TG_HANG(dstId); + if (m_slot->m_rfLastDstId != 0U) { + if (m_slot->m_rfLastDstId != dstId && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { + return; + } + } // if data preamble, signal its existence if (csbk->getDataContent()) { diff --git a/src/host/dmr/packet/Data.cpp b/src/host/dmr/packet/Data.cpp index 23a39a95b..22bc1d49e 100644 --- a/src/host/dmr/packet/Data.cpp +++ b/src/host/dmr/packet/Data.cpp @@ -31,50 +31,6 @@ using namespace dmr::packet; #include #include -// --------------------------------------------------------------------------- -// Macros -// --------------------------------------------------------------------------- - -// Helper macro to check if the host is authoritative and the destination ID is permitted. -#define CHECK_AUTHORITATIVE(_DST_ID) \ - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { \ - if (!g_disableNonAuthoritativeLogging) \ - LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } - -// Helper macro to check if the host is authoritative and the destination ID is permitted. -#define CHECK_NET_AUTHORITATIVE(_DST_ID) \ - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { \ - return; \ - } - -// Helper macro to perform RF traffic collision checking. -#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ - /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ - if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - \ - if (m_slot->m_enableTSCC && _DST_ID == m_slot->m_netLastDstId) { \ - if (m_slot->m_affiliations->isNetGranted(_DST_ID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - } - -// Helper macro to check if the RF talkgroup hang timer is running and the destination ID matches. -#define CHECK_TG_HANG(_DST_ID) \ - if (m_slot->m_rfLastDstId != 0U) { \ - if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ - return; \ - } \ - } - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -168,8 +124,27 @@ bool Data::process(uint8_t* data, uint32_t len) uint32_t srcId = m_rfDataHeader.getSrcId(); uint32_t dstId = m_rfDataHeader.getDstId(); - CHECK_AUTHORITATIVE(dstId); - CHECK_TRAFFIC_COLLISION(dstId); + if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); + m_slot->m_rfState = RS_RF_LISTENING; + return false; + } + + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return false; + } + + if (m_slot->m_enableTSCC && dstId == m_slot->m_netLastDstId) { + if (m_slot->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return false; + } + } if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -426,8 +401,15 @@ void Data::processNetwork(const data::NetData& dmrData) uint32_t srcId = m_netDataHeader.getSrcId(); uint32_t dstId = m_netDataHeader.getDstId(); - CHECK_NET_AUTHORITATIVE(dstId); - CHECK_TG_HANG(dstId); + if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { + return; + } + + if (m_slot->m_rfLastDstId != 0U) { + if (m_slot->m_rfLastDstId != dstId && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { + return; + } + } if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index bbd0be6e4..48c53025f 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -48,52 +48,6 @@ using namespace dmr::packet; return; \ } -// Helper macro to perform RF traffic collision checking. -#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ - /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ - if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - \ - if (m_slot->m_enableTSCC && _DST_ID == m_slot->m_netLastDstId) { \ - if (m_slot->m_affiliations->isNetGranted(_DST_ID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - } - -// Helper macro to perform network traffic collision checking. -#define CHECK_NET_TRAFFIC_COLLISION(_DST_ID) \ - /* don't process network frames if the destination ID's don't match and the RF TG hang timer is running */ \ - if (m_slot->m_rfLastDstId != 0U) { \ - if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ - return; \ - } \ - } \ - \ - /* bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* */ \ - /* the RF TG hangtimer is running */ \ - if (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired()) { \ - m_slot->m_rfTGHang.stop(); \ - } \ - \ - /* don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and */ \ - /* the destination ID doesn't match the default net idle talkgroup */ \ - if (m_slot->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_slot->m_rfTGHang.isRunning()) { \ - if (m_slot->m_defaultNetIdleTalkgroup != dstId) { \ - return; \ - } \ - } \ - \ - if (m_slot->m_netLastDstId != 0U) { \ - if (m_slot->m_netLastDstId != _DST_ID && (m_slot->m_netTGHang.isRunning() && !m_slot->m_netTGHang.hasExpired())) { \ - return; \ - } \ - } \ - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -128,7 +82,9 @@ bool Voice::process(uint8_t* data, uint32_t len) FLCO::E flco = lc->getFLCO(); CHECK_AUTHORITATIVE(dstId); - CHECK_TRAFFIC_COLLISION(dstId); + + if (checkRFTrafficCollision(dstId)) + return false; if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -557,7 +513,9 @@ bool Voice::process(uint8_t* data, uint32_t len) FLCO::E flco = lc->getFLCO(); CHECK_AUTHORITATIVE(dstId); - CHECK_TRAFFIC_COLLISION(dstId); + + if (checkRFTrafficCollision(dstId)) + return false; // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { @@ -731,7 +689,9 @@ void Voice::processNetwork(const data::NetData& dmrData) FLCO::E flco = lc->getFLCO(); CHECK_NET_AUTHORITATIVE(dstId); - CHECK_NET_TRAFFIC_COLLISION(dstId); + + if (checkNetTrafficCollision(dstId)) + return; if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -821,7 +781,9 @@ void Voice::processNetwork(const data::NetData& dmrData) uint32_t dstId = lc->getDstId(); CHECK_NET_AUTHORITATIVE(dstId); - CHECK_NET_TRAFFIC_COLLISION(dstId); + + if (checkNetTrafficCollision(dstId)) + return; m_slot->m_netLC = std::move(lc); @@ -1231,6 +1193,62 @@ Voice::~Voice() delete[] m_netEmbeddedData; } +/* Helper to perform RF traffic collision checking. */ + +bool Voice::checkRFTrafficCollision(uint32_t dstId) +{ + /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ + if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return true; + } + + if (m_slot->m_enableTSCC && dstId == m_slot->m_netLastDstId) { + if (m_slot->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return true; + } + } + + return false; +} + +/* Helper to perform network traffic collision checking. */ + +bool Voice::checkNetTrafficCollision(uint32_t dstId) +{ + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_slot->m_rfLastDstId != 0U) { + if (m_slot->m_rfLastDstId != dstId && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { + return true; + } + } + + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired()) { + m_slot->m_rfTGHang.stop(); + } + + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_slot->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_slot->m_rfTGHang.isRunning()) { + if (m_slot->m_defaultNetIdleTalkgroup != dstId) { + return true; + } + } + + if (m_slot->m_netLastDstId != 0U) { + if (m_slot->m_netLastDstId != dstId && (m_slot->m_netTGHang.isRunning() && !m_slot->m_netTGHang.hasExpired())) { + return true; + } + } + + return false; +} + /* */ void Voice::logGPSPosition(const uint32_t srcId, const uint8_t* data) diff --git a/src/host/dmr/packet/Voice.h b/src/host/dmr/packet/Voice.h index 8265f194e..dd36ef774 100644 --- a/src/host/dmr/packet/Voice.h +++ b/src/host/dmr/packet/Voice.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -112,6 +112,19 @@ namespace dmr */ ~Voice(); + /** + * @brief Helper to perform RF traffic collision checking. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkRFTrafficCollision(uint32_t dstId); + /** + * @brief Helper to perform network traffic collision checking. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkNetTrafficCollision(uint32_t dstId); + /** * @brief Log GPS information. * @param srcId Source radio ID. diff --git a/src/host/nxdn/packet/Data.cpp b/src/host/nxdn/packet/Data.cpp index 76cb3c7e1..0d432a6ca 100644 --- a/src/host/nxdn/packet/Data.cpp +++ b/src/host/nxdn/packet/Data.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -24,115 +24,6 @@ using namespace nxdn::packet; #include -// --------------------------------------------------------------------------- -// Macros -// --------------------------------------------------------------------------- - -// Helper macro to perform RF traffic collision checking. -#define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ - /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ - if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - \ - /* stop network frames from processing -- RF wants to transmit on a different talkgroup */ \ - if (m_nxdn->m_netState != RS_NET_IDLE) { \ - if (m_nxdn->m_netLC.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, \ - m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, \ - m_nxdn->m_netLastDstId); \ - resetNet(); \ - } \ - } - -// Helper macro to perform network traffic collision checking. -#define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ - /* don't process network frames if the destination ID's don't match and the RF TG hang timer is running */ \ - if (m_nxdn->m_rfLastDstId != 0U) { \ - if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - resetNet(); \ - return false; \ - } \ - \ - if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - m_nxdn->m_rfTGHang.start(); \ - } \ - } \ - \ - /* don't process network frames if the RF modem isn't in a listening state */ \ - if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ - if (_LAYER3.getSrcId() == srcId && _LAYER3.getDstId() == dstId) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ - srcId, dstId); \ - resetNet(); \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", _LAYER3.getDstId(), \ - dstId); \ - resetNet(); \ - return false; \ - } \ - } - -// Validate the source RID -#define VALID_SRCID(_SRC_ID, _DST_ID, _GROUP) \ - if (!acl::AccessControl::validateSrcId(_SRC_ID)) { \ - if (m_lastRejectId == 0U || m_lastRejectId != _SRC_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, srcId = %u", _SRC_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _SRC_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } - -// Validate the destination ID -#define VALID_DSTID(_SRC_ID, _DST_ID, _GROUP) \ - if (!_GROUP) { \ - if (!acl::AccessControl::validateSrcId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } \ - else { \ - if (!acl::AccessControl::validateTGId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, TGID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -184,13 +75,76 @@ bool Data::process(ChOption::E option, uint8_t* data, uint32_t len) if (type != MessageType::RTCH_DCALL_HDR) return false; - CHECK_TRAFFIC_COLLISION(srcId, dstId); + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_nxdn->m_netState != RS_NET_IDLE && dstId == m_nxdn->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return false; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup */ + if (m_nxdn->m_netState != RS_NET_IDLE) { + if (m_nxdn->m_netLC.getSrcId() == srcId && m_nxdn->m_netLastDstId == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return false; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, + m_nxdn->m_netLastDstId); + resetNet(); + } + } // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "RF data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF data rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } if (m_verbose) { LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", @@ -294,13 +248,71 @@ bool Data::processNetwork(ChOption::E option, lc::RTCH& netLC, uint8_t* data, ui if (type != MessageType::RTCH_DCALL_HDR) return false; - CHECK_NET_TRAFFIC_COLLISION(lc, srcId, dstId); + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_nxdn->m_rfLastDstId != 0U) { + if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + resetNet(); + return false; + } + + if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + m_nxdn->m_rfTGHang.start(); + } + } + + // don't process network frames if the RF modem isn't in a listening state + if (m_nxdn->m_rfState != RS_RF_LISTENING) { + if (lc.getSrcId() == srcId && lc.getDstId() == dstId) { \ + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", lc.getSrcId(), lc.getDstId(), + srcId, dstId); + resetNet(); + return false; + } + else { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", lc.getDstId(), + dstId); + resetNet(); + return false; + } + } // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "network data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + resetNet(); + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network data rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } if (m_verbose) { LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 0d7855085..755ca1c77 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -27,169 +27,6 @@ using namespace nxdn::packet; #include #include -// --------------------------------------------------------------------------- -// Macros -// --------------------------------------------------------------------------- - -// Helper macro to perform RF traffic collision checking. -#define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ - /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ \ - if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - \ - /* stop network frames from processing -- RF wants to transmit on a different talkgroup */ \ - if (m_nxdn->m_netState != RS_NET_IDLE) { \ - if (m_nxdn->m_netLC.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _SRC_ID, _DST_ID, \ - m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", _DST_ID, \ - m_nxdn->m_netLastDstId); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - } \ - \ - /* is control is enabled, and the group was granted by network already ignore RF traffic */ \ - if (m_nxdn->m_enableControl && _DST_ID == m_nxdn->m_netLastDstId) { \ - if (m_nxdn->m_affiliations->isNetGranted(_DST_ID)) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _SRC_ID, _DST_ID, \ - m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - } \ - } - -// Helper macro to perform network traffic collision checking. -#define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ - /* don't process network frames if the destination ID's don't match and the RF TG hang timer is running */ \ - if (m_nxdn->m_rfLastDstId != 0U) { \ - if (m_nxdn->m_rfLastDstId != _DST_ID && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - resetNet(); \ - return false; \ - } \ - \ - if (m_nxdn->m_rfLastDstId == _DST_ID && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - m_nxdn->m_rfTGHang.start(); \ - } \ - } \ - \ - /* bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* */ \ - /* the RF TG hangtimer is running */ \ - if (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired()) { \ - m_nxdn->m_rfTGHang.stop(); \ - } \ - \ - /* don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and */ \ - /* the destination ID doesn't match the default net idle talkgroup */ \ - if (m_nxdn->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_nxdn->m_rfTGHang.isRunning()) { \ - if (m_nxdn->m_defaultNetIdleTalkgroup != dstId) { \ - return false; \ - } \ - } \ - \ - /* perform authoritative network TG hangtimer and traffic preemption */ \ - if (m_nxdn->m_authoritative) { \ - if (m_nxdn->m_netLastDstId != 0U) { \ - if (m_nxdn->m_netLastDstId != _DST_ID && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { \ - return false; \ - } \ - \ - if (m_nxdn->m_netLastDstId == _DST_ID && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { \ - m_nxdn->m_netTGHang.start(); \ - } \ - } \ - \ - /* don't process network frames if the RF modem isn't in a listening state */ \ - if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ - if (_LAYER3.getSrcId() == _SRC_ID && _LAYER3.getDstId() == _DST_ID) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ - _SRC_ID, _DST_ID); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", _LAYER3.getDstId(), \ - _DST_ID); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - return false; \ - } \ - } \ - } \ - \ - /* don't process network frames if this modem isn't authoritative */ \ - if (!m_nxdn->m_authoritative && m_nxdn->m_permittedDstId != _DST_ID) { \ - if (!g_disableNonAuthoritativeLogging) \ - LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", _DST_ID); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - return false; \ - } - -// Validate the source RID -#define VALID_SRCID(_SRC_ID, _DST_ID, _GROUP) \ - if (!acl::AccessControl::validateSrcId(_SRC_ID)) { \ - if (m_lastRejectId == 0U || m_lastRejectId != _SRC_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", _SRC_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _SRC_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } - -// Validate the destination ID -#define VALID_DSTID(_SRC_ID, _DST_ID, _GROUP) \ - if (!_GROUP) { \ - if (!acl::AccessControl::validateSrcId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } \ - else { \ - if (!acl::AccessControl::validateTGId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -269,13 +106,55 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u return false; } } else if (type == MessageType::RTCH_VCALL) { - CHECK_TRAFFIC_COLLISION(srcId, dstId); + if (checkRFTrafficCollision(srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } } else { return false; } @@ -454,13 +333,55 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u bool group = m_nxdn->m_rfLC.getGroup(); bool encrypted = m_nxdn->m_rfLC.getEncrypted(); - CHECK_TRAFFIC_COLLISION(srcId, dstId); + if (checkRFTrafficCollision(srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } m_nxdn->m_rfTGHang.start(); m_nxdn->m_netTGHang.stop(); @@ -741,13 +662,46 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& return false; } } else if (type == MessageType::RTCH_VCALL) { - CHECK_NET_TRAFFIC_COLLISION(lc, srcId, dstId); + if (checkNetTrafficCollision(lc, srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + resetNet(); + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } } else { return false; } @@ -885,13 +839,46 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& bool group = m_nxdn->m_netLC.getGroup(); bool encrypted = m_nxdn->m_netLC.getEncrypted(); - CHECK_NET_TRAFFIC_COLLISION(m_nxdn->m_netLC, srcId, dstId); + if (checkNetTrafficCollision(m_nxdn->m_netLC, srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + resetNet(); + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } m_nxdn->m_netTGHang.start(); m_nxdn->m_netLastDstId = m_nxdn->m_netLC.getDstId(); @@ -1093,3 +1080,123 @@ void Voice::writeNetwork(const uint8_t *data, uint32_t len) m_nxdn->m_network->writeNXDN(m_nxdn->m_rfLC, data, len); } + +/* Helper to perform RF traffic collision checking. */ + +bool Voice::checkRFTrafficCollision(uint32_t srcId, uint32_t dstId) +{ + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_nxdn->m_netState != RS_NET_IDLE && dstId == m_nxdn->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return true; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup + if (m_nxdn->m_netState != RS_NET_IDLE) { + if (m_nxdn->m_netLC.getSrcId() == srcId && m_nxdn->m_netLastDstId == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return true; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, + m_nxdn->m_netLastDstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + } + + // is control is enabled, and the group was granted by network already ignore RF traffic + if (m_nxdn->m_enableControl && dstId == m_nxdn->m_netLastDstId) { + if (m_nxdn->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return true; + } + } + } + + return false; +} + +/* Helper to perform network traffic collision checking. */ + +bool Voice::checkNetTrafficCollision(lc::RTCH lc, uint32_t srcId, uint32_t dstId) +{ + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_nxdn->m_rfLastDstId != 0U) { + if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + resetNet(); + return true; + } + + if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + m_nxdn->m_rfTGHang.start(); + } + } + + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired()) { + m_nxdn->m_rfTGHang.stop(); + } + + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_nxdn->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_nxdn->m_rfTGHang.isRunning()) { + if (m_nxdn->m_defaultNetIdleTalkgroup != dstId) { + return true; + } + } + + // perform authoritative network TG hangtimer and traffic preemption + if (m_nxdn->m_authoritative) { + if (m_nxdn->m_netLastDstId != 0U) { + if (m_nxdn->m_netLastDstId != dstId && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { + return true; + } + + if (m_nxdn->m_netLastDstId == dstId && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { + m_nxdn->m_netTGHang.start(); + } + } + + // don't process network frames if the RF modem isn't in a listening state + if (m_nxdn->m_rfState != RS_RF_LISTENING) { + if (lc.getSrcId() == srcId && lc.getDstId() == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", lc.getSrcId(), lc.getDstId(), + srcId, dstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + return true; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", lc.getDstId(), + dstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + return true; + } + } + } + + // don't process network frames if this modem isn't authoritative + if (!m_nxdn->m_authoritative && m_nxdn->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", dstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + return true; + } + + return false; +} diff --git a/src/host/nxdn/packet/Voice.h b/src/host/nxdn/packet/Voice.h index 70091ff7a..a92d7e204 100644 --- a/src/host/nxdn/packet/Voice.h +++ b/src/host/nxdn/packet/Voice.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -112,6 +112,22 @@ namespace nxdn * @param len Length of data frame. */ void writeNetwork(const uint8_t* data, uint32_t len); + + /** + * @brief Helper to perform RF traffic collision checking. + * @param srcId Source ID. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkRFTrafficCollision(uint32_t srcId, uint32_t dstId); + /** + * @brief Helper to perform network traffic collision checking. + * @param lc + * @param srcId Source ID. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkNetTrafficCollision(lc::RTCH lc, uint32_t srcId, uint32_t dstId); }; } // namespace packet } // namespace nxdn From a5eb24308a4f8efe4e33718f84eda8635cfe208e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 12 Aug 2025 20:20:31 -0400 Subject: [PATCH 112/133] cleanup and unify design a bit, for P25 move traffic collision checking into separate function calls; --- src/host/dmr/packet/Voice.cpp | 2 +- src/host/p25/packet/Voice.cpp | 274 ++++++++++++++++++---------------- src/host/p25/packet/Voice.h | 16 ++ 3 files changed, 164 insertions(+), 128 deletions(-) diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index 48c53025f..ca574e96d 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -1197,7 +1197,7 @@ Voice::~Voice() bool Voice::checkRFTrafficCollision(uint32_t dstId) { - /* don't process RF frames if the network isn't in a idle state and the RF destination is the network destination */ + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); m_slot->m_rfState = RS_RF_LISTENING; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 8b00cf736..1ce88fb5e 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -235,61 +235,8 @@ bool Voice::process(uint8_t* data, uint32_t len) alreadyDecoded = true; - // don't process RF frames if this modem isn't authoritative - if (!m_p25->m_authoritative && m_p25->m_permittedDstId != lc.getDstId()) { - if (!g_disableNonAuthoritativeLogging) - LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; + if (checkRFTrafficCollision(srcId, dstId)) return false; - } - - // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination - if (m_p25->m_netState != RS_NET_IDLE && dstId == m_p25->m_netLastDstId) { - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; - return false; - } - - // stop network frames from processing -- RF wants to transmit on a different talkgroup - if (m_p25->m_netState != RS_NET_IDLE) { - if (m_netLC.getSrcId() == srcId && m_p25->m_netLastDstId == dstId) { - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, - m_netLC.getSrcId(), m_p25->m_netLastDstId); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; - return false; - } - else { - LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, - m_p25->m_netLastDstId); - if (!m_p25->m_dedicatedControl) { - m_p25->m_affiliations->releaseGrant(m_p25->m_netLastDstId, false); - } - - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - - if (m_p25->m_duplex) { - m_p25->writeRF_TDU(true); - } - - m_p25->m_netTGHang.stop(); - } - - // is control is enabled, and the group was granted by network already ignore RF traffic - if (m_p25->m_enableControl && dstId == m_p25->m_netLastDstId) { - if (m_p25->m_affiliations->isNetGranted(dstId)) { - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, - m_netLC.getSrcId(), m_p25->m_netLastDstId); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; - return false; - } - } - } // if this is a late entry call, clear states if (m_rfLastHDU.getDstId() == 0U) { @@ -1196,80 +1143,8 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L uint32_t dstId = control.getDstId(); uint32_t srcId = control.getSrcId(); - // don't process network frames if the destination ID's don't match and the RF TG hang timer is running - if (m_p25->m_rfLastDstId != 0U && dstId != 0U) { - if (m_p25->m_rfLastDstId != dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - - if (m_p25->m_rfLastDstId == dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { - m_p25->m_rfTGHang.start(); - } - } - - // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* - // the RF TG hangtimer is running - if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { - m_p25->m_rfTGHang.stop(); - } - - // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and - // the destination ID doesn't match the default net idle talkgroup - if (m_p25->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_p25->m_rfTGHang.isRunning()) { - if (m_p25->m_defaultNetIdleTalkgroup != dstId) { - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - } - - // perform authoritative network TG hangtimer and traffic preemption - if (m_p25->m_authoritative) { - // don't process network frames if the destination ID's don't match and the network TG hang timer is running - if (m_p25->m_netLastDstId != 0U && dstId != 0U && (duid == DUID::LDU1 || duid == DUID::LDU2)) { - if (m_p25->m_netLastDstId != dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { - return false; - } - - if (m_p25->m_netLastDstId == dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { - m_p25->m_netTGHang.start(); - } - } - - // don't process network frames if the RF modem isn't in a listening state - if (m_p25->m_rfState != RS_RF_LISTENING) { - if (m_rfLC.getSrcId() == srcId && m_rfLC.getDstId() == dstId) { - LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", m_rfLC.getSrcId(), m_rfLC.getDstId(), - srcId, dstId); - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - else { - LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", m_rfLC.getDstId(), - dstId); - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - } - } - - // don't process network frames if this modem isn't authoritative - if (!m_p25->m_authoritative && m_p25->m_permittedDstId != dstId) { - if (!g_disableNonAuthoritativeLogging) - LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", dstId); - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); + if (checkNetTrafficCollision(srcId, dstId, duid)) return false; - } uint32_t count = 0U; switch (duid) { @@ -1609,6 +1484,151 @@ void Voice::writeNetwork(const uint8_t *data, defines::DUID::E duid, defines::Fr } } +/* Helper to perform RF traffic collision checking. */ + +bool Voice::checkRFTrafficCollision(uint32_t srcId, uint32_t dstId) +{ + // don't process RF frames if this modem isn't authoritative + if (!m_p25->m_authoritative && m_p25->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_p25->m_netState != RS_NET_IDLE && dstId == m_p25->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup + if (m_p25->m_netState != RS_NET_IDLE) { + if (m_netLC.getSrcId() == srcId && m_p25->m_netLastDstId == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_netLC.getSrcId(), m_p25->m_netLastDstId); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, + m_p25->m_netLastDstId); + if (!m_p25->m_dedicatedControl) { + m_p25->m_affiliations->releaseGrant(m_p25->m_netLastDstId, false); + } + + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + + if (m_p25->m_duplex) { + m_p25->writeRF_TDU(true); + } + + m_p25->m_netTGHang.stop(); + } + + // is control is enabled, and the group was granted by network already ignore RF traffic + if (m_p25->m_enableControl && dstId == m_p25->m_netLastDstId) { + if (m_p25->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_netLC.getSrcId(), m_p25->m_netLastDstId); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + } + } + + return false; +} + +/* Helper to perform network traffic collision checking. */ + +bool Voice::checkNetTrafficCollision(uint32_t srcId, uint32_t dstId, defines::DUID::E duid) +{ + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_p25->m_rfLastDstId != 0U && dstId != 0U) { + if (m_p25->m_rfLastDstId != dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + + if (m_p25->m_rfLastDstId == dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + m_p25->m_rfTGHang.start(); + } + } + + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { + m_p25->m_rfTGHang.stop(); + } + + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_p25->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_p25->m_rfTGHang.isRunning()) { + if (m_p25->m_defaultNetIdleTalkgroup != dstId) { + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + } + + // perform authoritative network TG hangtimer and traffic preemption + if (m_p25->m_authoritative) { + // don't process network frames if the destination ID's don't match and the network TG hang timer is running + if (m_p25->m_netLastDstId != 0U && dstId != 0U && (duid == DUID::LDU1 || duid == DUID::LDU2)) { + if (m_p25->m_netLastDstId != dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { + return true; + } + + if (m_p25->m_netLastDstId == dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { + m_p25->m_netTGHang.start(); + } + } + + // don't process network frames if the RF modem isn't in a listening state + if (m_p25->m_rfState != RS_RF_LISTENING) { + if (m_rfLC.getSrcId() == srcId && m_rfLC.getDstId() == dstId) { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", m_rfLC.getSrcId(), m_rfLC.getDstId(), + srcId, dstId); + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + else { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", m_rfLC.getDstId(), + dstId); + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + } + } + + // don't process network frames if this modem isn't authoritative + if (!m_p25->m_authoritative && m_p25->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", dstId); + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + + return false; +} + /* Helper to write end of frame data. */ void Voice::writeRF_EndOfVoice() diff --git a/src/host/p25/packet/Voice.h b/src/host/p25/packet/Voice.h index 89de8eb3b..2f03c8983 100644 --- a/src/host/p25/packet/Voice.h +++ b/src/host/p25/packet/Voice.h @@ -153,6 +153,22 @@ namespace p25 * @param frameType Frame Type. */ void writeNetwork(const uint8_t* data, defines::DUID::E duid, defines::FrameType::E frameType = defines::FrameType::DATA_UNIT); + + /** + * @brief Helper to perform RF traffic collision checking. + * @param srcId Source ID. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkRFTrafficCollision(uint32_t srcId, uint32_t dstId); + /** + * @brief Helper to perform network traffic collision checking. + * @param srcId Source ID. + * @param dstId Destination ID. + * @param duid DUID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkNetTrafficCollision(uint32_t srcId, uint32_t dstId, defines::DUID::E duid); /** * @brief Helper to write end of voice frame data. From 92980d15ef6cda8b2202d2699bf8cc4e6cf37d56 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 12 Aug 2025 21:27:56 -0400 Subject: [PATCH 113/133] add colorize-host.sh helper tool; update colorize-fne.sh tool for DMR and NXDN; --- tools/colorize-fne.sh | 8 +++++--- tools/colorize-host.sh | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100755 tools/colorize-host.sh diff --git a/tools/colorize-fne.sh b/tools/colorize-fne.sh index 578ea6d01..bee978b3b 100755 --- a/tools/colorize-fne.sh +++ b/tools/colorize-fne.sh @@ -7,7 +7,7 @@ #* #*/ #/* -#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +#* Copyright (C) 2022-2025 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 @@ -25,10 +25,12 @@ #*/ LOG_COLOR="s#W:#\x1b[0m\x1b[1m\x1b[33m&#; s#E:#\x1b[0m\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&#; s#U:#\x1b[44m\x1b[1m\x1b[33m&#;" +DMR_COLOR="s#VOICE#\x1b[36m&#; s#TERMINATOR_WITH_LC#\x1b[0m\x1b[32m&#; s#CSBK#\x1b[0m\x1b[35m&#" P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#" -AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#;" +NXDN_COLOR="s#VCALL#\x1b[36m&#; s#TX_REL#\x1b[0m\x1b[32m&#" +AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#; s#Affiliation#\x1b[1m\x1b[36m&#;" RF_HIGHLIGHT="s#(RF)#\x1b[1m\x1b[34m&\x1b[0m#;" NET_HIGHLIGHT="s#(NET)#\x1b[1m\x1b[36m&\x1b[0m#;" -sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${P25_COLOR}; ${AFF_COLOR}" \ No newline at end of file +sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}" \ No newline at end of file diff --git a/tools/colorize-host.sh b/tools/colorize-host.sh new file mode 100755 index 000000000..f51668769 --- /dev/null +++ b/tools/colorize-host.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +#/** +#* Digital Voice Modem - Host Software +#* GPLv2 Open Source. Use is subject to license terms. +#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +#* +#*/ +#/* +#* Copyright (C) 2025 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 +#* the Free Software Foundation; either version 2 of the License, or +#* (at your option) any later version. +#* +#* This program is distributed in the hope that it will be useful, +#* but WITHOUT ANY WARRANTY; without even the implied warranty of +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#* GNU General Public License for more details. +#* +#* You should have received a copy of the GNU General Public License +#* along with this program; if not, write to the Free Software +#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +#*/ +LOG_COLOR="s#W:#\x1b[1m\x1b[33m&#; s#E:#\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&\x1b[0m\x1b[1m#;" + +DMR_COLOR="s#VOICE#\x1b[36m&#; s#TERMINATOR_WITH_LC#\x1b[0m\x1b[32m&#; s#CSBK#\x1b[0m\x1b[35m&#" +P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#" +NXDN_COLOR="s#VCALL#\x1b[36m&#; s#TX_REL#\x1b[0m\x1b[32m&#" +AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#; s#Affiliation#\x1b[1m\x1b[36m&#;" + +RF_HIGHLIGHT="s#RF#\x1b[1m\x1b[35m&\x1b[0m#;" +NET_HIGHLIGHT="s#NET#\x1b[1m\x1b[36m&\x1b[0m#;" + +sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}" From babefe7b33166e72342de606af46c06eb188389c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 13:08:01 -0400 Subject: [PATCH 114/133] store status data for a private call; --- src/fne/network/callhandler/TagDMRData.cpp | 36 +++++++++++-- src/fne/network/callhandler/TagDMRData.h | 5 ++ src/fne/network/callhandler/TagNXDNData.cpp | 60 +++++++++++++++++++-- src/fne/network/callhandler/TagNXDNData.h | 5 ++ src/fne/network/callhandler/TagP25Data.cpp | 57 ++++++++++++++++++-- src/fne/network/callhandler/TagP25Data.h | 5 ++ 6 files changed, 156 insertions(+), 12 deletions(-) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index fc327ca40..8543aff4e 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -47,6 +47,7 @@ TagDMRData::TagDMRData(FNENetwork* network, bool debug) : m_parrotFrames(), m_parrotFramesReady(false), m_status(), + m_statusPVCall(), m_debug(debug) { assert(network != nullptr); @@ -183,8 +184,22 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - LogMessage(LOG_NET, "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + m_statusPVCall[dstId].reset(); + LogMessage(LOG_NET, "DMR, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); + } + else + LogMessage(LOG_NET, "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -273,7 +288,22 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + // is this a private call? + if (flco == FLCO::PRIVATE) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + return true; + } + return false; + }); + if (it == m_statusPVCall.end()) { + m_statusPVCall[dstId] = m_status[dstId]; + } + LogMessage(LOG_NET, "DMR, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, external); + } + else + LogMessage(LOG_NET, "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index efd24713b..43f424920 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -178,6 +178,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Destination Peer ID. + */ + uint32_t dstPeerId; /** * @brief Flag indicating this call is active with traffic currently in progress. */ @@ -198,6 +202,7 @@ namespace network }; typedef std::pair StatusMapPair; concurrent::unordered_map m_status; + concurrent::unordered_map m_statusPVCall; friend class packetdata::DMRPacketData; packetdata::DMRPacketData* m_packetData; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index a3bf3ef13..70f72737c 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -48,7 +48,8 @@ TagNXDNData::TagNXDNData(FNENetwork* network, bool debug) : m_parrotFrames(), m_parrotFramesReady(false), m_status(), - m_debug(debug) + m_statusPVCall(), + m_debug(debug) { assert(network != nullptr); } @@ -131,8 +132,22 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } } - LogMessage(LOG_NET, "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + m_statusPVCall[dstId].reset(); + LogMessage(LOG_NET, "NXDN, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + } + else + LogMessage(LOG_NET, "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -198,7 +213,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI else { // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot()) { m_parrotFramesReady = false; if (m_parrotFrames.size() > 0) { for (auto& pkt : m_parrotFrames) { @@ -218,7 +233,42 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + // is this a private call? + if (!group) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + RxStatus status = m_statusPVCall[dstId]; + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { + LogWarning(LOG_NET, "NXDN, Private Call Collision, lasted more then %us with no further updates, forcibly ending call"); + m_statusPVCall[dstId].reset(); + } + else { + LogWarning(LOG_NET, "NXDN, Private Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + return false; + } + } + + m_statusPVCall[dstId].callStartTime = pktTime; + m_statusPVCall[dstId].srcId = srcId; + m_statusPVCall[dstId].dstId = dstId; + m_statusPVCall[dstId].streamId = streamId; + m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].activeCall = true; + + LogMessage(LOG_NET, "NXDN, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, external); + } + else + LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 5b6ca10eb..f48328cd0 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -143,6 +143,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Destination Peer ID. + */ + uint32_t dstPeerId; /** * @brief Flag indicating this call is active with traffic currently in progress. */ @@ -162,6 +166,7 @@ namespace network }; typedef std::pair StatusMapPair; concurrent::unordered_map m_status; + concurrent::unordered_map m_statusPVCall; bool m_debug; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 0a0a6fa48..fe86bb4ea 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -48,6 +48,7 @@ TagP25Data::TagP25Data(FNENetwork* network, bool debug) : m_parrotFramesReady(false), m_parrotFirstFrame(true), m_status(), + m_statusPVCall(), m_packetData(nullptr), m_debug(debug) { @@ -221,8 +222,22 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + m_statusPVCall[dstId].reset(); + LogMessage(LOG_NET, "P25, Private Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); + } + else + LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -309,8 +324,42 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].peerId = peerId; m_status[dstId].activeCall = true; - LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); + // is this a private call? + if (lco == LCO::PRIVATE) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + RxStatus status = m_statusPVCall[dstId]; + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { + LogWarning(LOG_NET, "P25, Private Call Collision, lasted more then %us with no further updates, forcibly ending call"); + m_statusPVCall[dstId].reset(); + } + else { + LogWarning(LOG_NET, "P25, Private Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + return false; + } + } + + m_statusPVCall[dstId].callStartTime = pktTime; + m_statusPVCall[dstId].srcId = srcId; + m_statusPVCall[dstId].dstId = dstId; + m_statusPVCall[dstId].streamId = streamId; + m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].activeCall = true; + + LogMessage(LOG_NET, "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); + } + else + LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index 21984ac2e..12de5c16b 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -192,6 +192,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Destination Peer ID. + */ + uint32_t dstPeerId; /** * @brief Flag indicating this call is active with traffic currently in progress. */ @@ -211,6 +215,7 @@ namespace network }; typedef std::pair StatusMapPair; concurrent::unordered_map m_status; + concurrent::unordered_map m_statusPVCall; friend class packetdata::P25PacketData; packetdata::P25PacketData* m_packetData; From aec98578d1d6c62bb00fd4504bd6bec43eaa8a1c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 13:54:11 -0400 Subject: [PATCH 115/133] during unit registration store the originating source peer ID for a given unit registration; use unit registration source ID to select the destination peer to send private call data to; --- src/fne/CMakeLists.txt | 2 + src/fne/lookups/AffiliationLookup.cpp | 101 ++++++++++++++++++++ src/fne/lookups/AffiliationLookup.h | 83 ++++++++++++++++ src/fne/network/FNENetwork.cpp | 21 +++- src/fne/network/FNENetwork.h | 11 ++- src/fne/network/callhandler/TagDMRData.cpp | 21 ++-- src/fne/network/callhandler/TagNXDNData.cpp | 25 +---- src/fne/network/callhandler/TagP25Data.cpp | 25 +---- 8 files changed, 233 insertions(+), 56 deletions(-) create mode 100644 src/fne/lookups/AffiliationLookup.cpp create mode 100644 src/fne/lookups/AffiliationLookup.h diff --git a/src/fne/CMakeLists.txt b/src/fne/CMakeLists.txt index 61788027b..2425e6c0c 100644 --- a/src/fne/CMakeLists.txt +++ b/src/fne/CMakeLists.txt @@ -8,6 +8,8 @@ # * # */ file(GLOB dvmfne_SRC + "src/fne/lookups/*.h" + "src/fne/lookups/*.cpp" "src/fne/network/callhandler/*.h" "src/fne/network/callhandler/*.cpp" "src/fne/network/callhandler/packetdata/*.h" diff --git a/src/fne/lookups/AffiliationLookup.cpp b/src/fne/lookups/AffiliationLookup.cpp new file mode 100644 index 000000000..b84944327 --- /dev/null +++ b/src/fne/lookups/AffiliationLookup.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "common/Log.h" +#include "lookups/AffiliationLookup.h" + +using namespace fne_lookups; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the AffiliationLookup class. */ + +AffiliationLookup::AffiliationLookup(const std::string name, ::lookups::ChannelLookup* chLookup, bool verbose) : + ::lookups::AffiliationLookup(name, chLookup, verbose), + m_unitRegPeerTable() +{ + m_unitRegPeerTable.clear(); +} + +/* Finalizes a instance of the AffiliationLookup class. */ + +AffiliationLookup::~AffiliationLookup() = default; + +/* Helper to group affiliate a source ID. */ + +void AffiliationLookup::unitReg(uint32_t srcId, uint32_t ssrc) +{ + if (isUnitReg(srcId)) { + return; + } + + ::lookups::AffiliationLookup::unitReg(srcId); + + m_unitRegPeerTable[srcId] = ssrc; +} + +/* Helper to group unaffiliate a source ID. */ + +bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) +{ + bool ret = false; + + if (!isUnitReg(srcId)) { + return false; + } + + ret = ::lookups::AffiliationLookup::unitDereg(srcId, automatic); + + if (ret) { + // lookup dynamic table entry + if (m_unitRegPeerTable.find(srcId) == m_unitRegPeerTable.end()) { + return false; + } + + // remove dynamic table entry + try { + uint32_t entry = m_unitRegPeerTable.at(srcId); // this value will get discarded + (void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard + m_unitRegPeerTable.erase(srcId); + return true; + } + catch (...) { + return false; + } + } + + return false; +} + +/* Helper to get the originating network peer ID by the registered source ID. */ + +uint32_t AffiliationLookup::getSSRCByUnitReg(uint32_t srcId) +{ + // lookup dynamic channel grant table entry + m_unitRegPeerTable.lock(false); + for (auto entry : m_grantChTable) { + if (entry.first == srcId) { + m_unitRegPeerTable.unlock(); + return entry.second; + } + } + m_unitRegPeerTable.unlock(); + + return 0U; +} + +/* Helper to release unit registrations. */ + +void AffiliationLookup::clearUnitReg() +{ + ::lookups::AffiliationLookup::clearUnitReg(); + m_unitRegPeerTable.clear(); +} diff --git a/src/fne/lookups/AffiliationLookup.h b/src/fne/lookups/AffiliationLookup.h new file mode 100644 index 000000000..d6faac605 --- /dev/null +++ b/src/fne/lookups/AffiliationLookup.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup fne_lookups Lookup Tables + * @brief Implementation for various data lookup tables. + * @ingroup fne + * + * @file P25AffiliationLookup.h + * @ingroup fne_lookups + * @file P25AffiliationLookup.cpp + * @ingroup fne_lookups + */ +#if !defined(__FNE_AFFILIATION_LOOKUP_H__) +#define __FNE_AFFILIATION_LOOKUP_H__ + +#include "Defines.h" +#include "common/lookups/AffiliationLookup.h" +#include "common/lookups/ChannelLookup.h" + +namespace fne_lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a lookup table class that contains subscriber registration + * and group affiliation information. + * @ingroup fne_lookups + */ + class HOST_SW_API AffiliationLookup : public ::lookups::AffiliationLookup { + public: + /** + * @brief Initializes a new instance of the AffiliationLookup class. + * @param name Name of lookup table. + * @param channelLookup Instance of the channel lookup class. + * @param verbose Flag indicating whether verbose logging is enabled. + */ + AffiliationLookup(const std::string name, ::lookups::ChannelLookup* chLookup, bool verbose); + /** + * @brief Finalizes a instance of the AffiliationLookup class. + */ + ~AffiliationLookup() override; + + /** @name Unit Registrations */ + /** + * @brief Helper to register a source ID. + * @param srcId Source Radio ID. + * @param ssrc Originating Peer ID. + */ + void unitReg(uint32_t srcId, uint32_t ssrc); + /** + * @brief Helper to deregister a source ID. + * @param srcId Source Radio ID. + * @param automatic Flag indicating the deregistration is a result of an automated timer. + * @returns bool True, if source ID is deregistered, otherwise false. + */ + bool unitDereg(uint32_t srcId, bool automatic = false) override; + /** + * @brief Helper to get the originating network peer ID by the registered source ID. + * @param srcId Source Radio ID. + * @returns uint32_t Originating Peer ID. + */ + uint32_t getSSRCByUnitReg(uint32_t srcId); + /** + * @brief Helper to release unit registrations. + */ + void clearUnitReg() override; + /** @} */ + + protected: + concurrent::unordered_map m_unitRegPeerTable; + }; +} // namespace fne_lookups + +#endif // __FNE_AFFILIATION_LOOKUP_H__ diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index ea76f7971..ac7f97c90 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1352,7 +1352,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) FNEPeerConnection* connection = network->m_peers[peerId]; if (connection != nullptr) { std::string ip = udp::Socket::address(req->address); - lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; + fne_lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); @@ -1361,7 +1361,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // validate peer (simple validation really) if (connection->connected() && connection->address() == ip && aff != nullptr) { uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - aff->unitReg(srcId); + aff->unitReg(srcId, ssrc); // attempt to repeat traffic to Peer-Link masters if (network->m_host->m_peerNetworks.size() > 0) { @@ -1606,7 +1606,7 @@ void FNENetwork::createPeerAffiliations(uint32_t peerId, std::string peerName) erasePeerAffiliations(peerId); lookups::ChannelLookup* chLookup = new lookups::ChannelLookup(); - m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, chLookup, m_verbose); + m_peerAffiliations[peerId] = new fne_lookups::AffiliationLookup(peerName, chLookup, m_verbose); m_peerAffiliations[peerId]->setDisableUnitRegTimeout(true); // FNE doesn't allow unit registration timeouts (notification must come from the peers) } @@ -1662,6 +1662,21 @@ void FNENetwork::erasePeer(uint32_t peerId) erasePeerAffiliations(peerId); } +/* Helper to find the unit registration for the given source ID. */ + +uint32_t FNENetwork::findPeerUnitReg(uint32_t srcId) +{ + for (auto it = m_peerAffiliations.begin(); it != m_peerAffiliations.end(); ++it) { + fne_lookups::AffiliationLookup* aff = it->second; + if (aff != nullptr) { + if (aff->isUnitReg(srcId)) { + return aff->getSSRCByUnitReg(srcId); + } + } + } + + return 0U; +} /* Helper to create a JSON representation of a FNE peer connection. */ diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 5c520a242..3d241d070 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -28,7 +28,6 @@ #include "common/concurrent/unordered_map.h" #include "common/network/BaseNetwork.h" #include "common/network/json/json.h" -#include "common/lookups/AffiliationLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" @@ -36,6 +35,7 @@ #include "common/network/Network.h" #include "common/network/PacketBuffer.h" #include "common/ThreadPool.h" +#include "fne/lookups/AffiliationLookup.h" #include "fne/network/influxdb/InfluxDB.h" #include "fne/CryptoContainer.h" @@ -572,7 +572,7 @@ namespace network concurrent::unordered_map m_peers; concurrent::unordered_map m_peerLinkPeers; typedef std::pair PeerAffiliationMapPair; - concurrent::unordered_map m_peerAffiliations; + concurrent::unordered_map m_peerAffiliations; concurrent::unordered_map> m_ccPeerMap; static std::timed_mutex m_keyQueueMutex; std::unordered_map m_peerLinkKeyQueue; @@ -680,6 +680,13 @@ namespace network */ void erasePeer(uint32_t peerId); + /** + * @brief Helper to find the unit registration for the given source ID. + * @param srcId Source Radio ID. + * @returns uint32_t Peer ID, or 0 if not found. + */ + uint32_t findPeerUnitReg(uint32_t srcId); + /** * @brief Helper to resolve the peer ID to its identity string. * @param peerId Peer ID. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 8543aff4e..59a277081 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -290,15 +290,18 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a private call? if (flco == FLCO::PRIVATE) { - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { - if (x.second.dstId == dstId) { - return true; - } - return false; - }); - if (it == m_statusPVCall.end()) { - m_statusPVCall[dstId] = m_status[dstId]; - } + m_statusPVCall[dstId].callStartTime = pktTime; + m_statusPVCall[dstId].srcId = srcId; + m_statusPVCall[dstId].dstId = dstId; + m_statusPVCall[dstId].slotNo = slotNo; + m_statusPVCall[dstId].streamId = streamId; + m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].activeCall = true; + + // find the SSRC of the peer that registered this unit + uint32_t regSSRC = m_network->findPeerUnitReg(srcId); + m_statusPVCall[dstId].dstPeerId = regSSRC; + LogMessage(LOG_NET, "DMR, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); } diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 70f72737c..c1b8a326d 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -235,27 +235,6 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this a private call? if (!group) { - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { - if (x.second.dstId == dstId) { - if (x.second.activeCall) - return true; - } - return false; - }); - if (it != m_statusPVCall.end()) { - RxStatus status = m_statusPVCall[dstId]; - uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); - if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "NXDN, Private Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_statusPVCall[dstId].reset(); - } - else { - LogWarning(LOG_NET, "NXDN, Private Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); - return false; - } - } - m_statusPVCall[dstId].callStartTime = pktTime; m_statusPVCall[dstId].srcId = srcId; m_statusPVCall[dstId].dstId = dstId; @@ -263,6 +242,10 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_statusPVCall[dstId].peerId = peerId; m_statusPVCall[dstId].activeCall = true; + // find the SSRC of the peer that registered this unit + uint32_t regSSRC = m_network->findPeerUnitReg(srcId); + m_statusPVCall[dstId].dstPeerId = regSSRC; + LogMessage(LOG_NET, "NXDN, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index fe86bb4ea..f480a5a6b 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -326,27 +326,6 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a private call? if (lco == LCO::PRIVATE) { - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { - if (x.second.dstId == dstId) { - if (x.second.activeCall) - return true; - } - return false; - }); - if (it != m_statusPVCall.end()) { - RxStatus status = m_statusPVCall[dstId]; - uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); - if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "P25, Private Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_statusPVCall[dstId].reset(); - } - else { - LogWarning(LOG_NET, "P25, Private Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); - return false; - } - } - m_statusPVCall[dstId].callStartTime = pktTime; m_statusPVCall[dstId].srcId = srcId; m_statusPVCall[dstId].dstId = dstId; @@ -354,6 +333,10 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_statusPVCall[dstId].peerId = peerId; m_statusPVCall[dstId].activeCall = true; + // find the SSRC of the peer that registered this unit + uint32_t regSSRC = m_network->findPeerUnitReg(srcId); + m_statusPVCall[dstId].dstPeerId = regSSRC; + LogMessage(LOG_NET, "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); } From f1990e5a70b8ae67d8516d8cd4a5e11b5fe71fe4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 14:23:51 -0400 Subject: [PATCH 116/133] initial experimental implementation of private call routing: this introduces a new configuration flag "restrictPrivateCallToRegOnly", when set, this flag will influence how private calls are routed through the system private calls using restrictPrivateCallToRegOnly require both the source and destination subscribers to be unit registered with the system, when a private call occurs the system will utilize the source peer ID to restrict repeating the private call traffic only to the 2 peers involved in the private call FNEs will *always* receive private call traffic --- configs/fne-config.example.yml | 2 ++ src/fne/network/FNENetwork.cpp | 3 +++ src/fne/network/FNENetwork.h | 1 + src/fne/network/callhandler/TagDMRData.cpp | 26 +++++++++++++++++++++ src/fne/network/callhandler/TagNXDNData.cpp | 26 +++++++++++++++++++++ src/fne/network/callhandler/TagP25Data.cpp | 26 +++++++++++++++++++++ 6 files changed, 84 insertions(+) diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 7a2a87183..3ef1f78f8 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -93,6 +93,8 @@ master: # Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating. restrictGrantToAffiliatedOnly: false + # Flag indicating whether or not a private call will only be routed to the network peers the RID registers with. + restrictPrivateCallToRegOnly: false # Flag indicating whether or not a adjacent site broadcasts will pass to any peers. disallowAdjStsBcast: false diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index ac7f97c90..a70464cc8 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -97,6 +97,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_allowConvSiteAffOverride(false), m_disallowCallTerm(false), m_restrictGrantToAffOnly(false), + m_restrictPVCallToRegOnly(false), m_enableInCallCtrl(true), m_rejectUnknownRID(false), m_maskOutboundPeerID(false), @@ -178,6 +179,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_parrotOnlyOriginating = conf["parrotOnlyToOrginiatingPeer"].as(false); m_restrictGrantToAffOnly = conf["restrictGrantToAffiliatedOnly"].as(false); + m_restrictPVCallToRegOnly = conf["restrictPrivateCallToRegOnly"].as(false); m_filterTerminators = conf["filterTerminators"].as(true); m_disablePacketData = conf["disablePacketData"].as(false); @@ -221,6 +223,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Mask Outbound Traffic Peer ID for Non-Peer Link: yes"); } LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); + LogInfo(" Restrict private call to registered units: %s", m_restrictPVCallToRegOnly ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); LogInfo(" Disallow Unit-to-Unit: %s", m_disallowU2U ? "yes" : "no"); LogInfo(" InfluxDB Reporting Enabled: %s", m_enableInfluxDB ? "yes" : "no"); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 3d241d070..a0a230b2a 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -608,6 +608,7 @@ namespace network bool m_allowConvSiteAffOverride; bool m_disallowCallTerm; bool m_restrictGrantToAffOnly; + bool m_restrictPVCallToRegOnly; bool m_enableInCallCtrl; bool m_rejectUnknownRID; diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 59a277081..01affed85 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -350,11 +350,37 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; if (ssrc == peer.first) { // skip the peer if it is the source peer continue; } + if (m_network->m_restrictPVCallToRegOnly) { + // is this peer an external peer? + bool external = false; + if (conn != nullptr) { + external = conn->isExternalPeer(); + } + + // is this a private call? + if ((flco == FLCO::PRIVATE) && !external) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + if (peer.first != m_statusPVCall[dstId].dstPeerId) { + continue; + } + } + } + } + // is this peer ignored? if (!isPeerPermitted(peer.first, dmrData, streamId)) { continue; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index c1b8a326d..85fe5e38a 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -289,11 +289,37 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; if (ssrc == peer.first) { // skip the peer if it is the source peer continue; } + if (m_network->m_restrictPVCallToRegOnly) { + // is this peer an external peer? + bool external = false; + if (conn != nullptr) { + external = conn->isExternalPeer(); + } + + // is this a private call? + if (!group && !external) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + if (peer.first != m_statusPVCall[dstId].dstPeerId) { + continue; + } + } + } + } + // is this peer ignored? if (!isPeerPermitted(peer.first, lc, messageType, streamId)) { continue; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index f480a5a6b..75acc59a9 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -385,11 +385,37 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; if (ssrc == peer.first) { // skip the peer if it is the source peer continue; } + if (m_network->m_restrictPVCallToRegOnly) { + // is this peer an external peer? + bool external = false; + if (conn != nullptr) { + external = conn->isExternalPeer(); + } + + // is this a private call? + if ((lco == LCO::PRIVATE) && !external) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + if (peer.first != m_statusPVCall[dstId].dstPeerId) { + continue; + } + } + } + } + // is this peer ignored? if (!isPeerPermitted(peer.first, control, duid, streamId)) { continue; From 86226ee9a84922b55528e7270590a34eff71086b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 15:09:11 -0400 Subject: [PATCH 117/133] rework stream validation for U2U calls slightly for P25; ensure P25 always sends the U2U control byte for private calls; --- src/fne/network/callhandler/TagP25Data.cpp | 43 ++++++++++++++++++---- src/host/p25/packet/Voice.cpp | 9 ++++- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 75acc59a9..b81f61e88 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -84,6 +84,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint32_t srcId = GET_UINT24(data, 5U); uint32_t dstId = GET_UINT24(data, 8U); + uint8_t controlByte = data[14U]; + uint8_t MFId = data[15U]; uint32_t sysId = (data[11U] << 8) | (data[12U] << 0); @@ -148,6 +150,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId control.setDstId(dstId); control.setMFId(MFId); + // set the LC group flag based on the control byte + control.setGroup((controlByte & NET_CTRL_U2U) != NET_CTRL_U2U); + lsd.setLSD1(lsd1); lsd.setLSD2(lsd2); @@ -334,7 +339,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_statusPVCall[dstId].activeCall = true; // find the SSRC of the peer that registered this unit - uint32_t regSSRC = m_network->findPeerUnitReg(srcId); + uint32_t regSSRC = m_network->findPeerUnitReg(dstId); m_statusPVCall[dstId].dstPeerId = regSSRC; LogMessage(LOG_NET, "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", @@ -1226,6 +1231,18 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const // always validate a terminator if the source is valid if (m_network->m_filterTerminators) { if ((duid == DUID::TDU || duid == DUID::TDULC) && control.getDstId() != 0U) { + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == control.getDstId()) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + return true; + } + // is this a group call? lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); if (!tg.isInvalid()) { @@ -1237,12 +1254,6 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const return true; } - // is this a U2U call? - lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); - if (!rid.radioDefault() && rid.radioEnabled()) { - return true; - } - //LogDebugEx(LOG_NET, "TagP25Data::validate()", "TDU for invalid destination, dropped, dstId = %u", control.getDstId()); return false; } @@ -1254,8 +1265,24 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const return true; } + // validate private call in-progress + bool privateCallInProgress = false; + if ((control.getLCO() != LCO::PRIVATE) && !control.getGroup()) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == control.getDstId()) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + privateCallInProgress = true; + } + } + // is this a private call? - if (control.getLCO() == LCO::PRIVATE) { + if ((control.getLCO() == LCO::PRIVATE) || privateCallInProgress) { // is the destination ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); if (!rid.radioDefault()) { diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 1ce88fb5e..ef1e32dbf 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -1458,18 +1458,23 @@ void Voice::writeNetwork(const uint8_t *data, defines::DUID::E duid, defines::Fr if (m_p25->m_rfTimeout.isRunning() && m_p25->m_rfTimeout.hasExpired()) return; + uint8_t controlByte = 0U; switch (duid) { case DUID::HDU: // ignore HDU break; case DUID::LDU1: - m_p25->m_network->writeP25LDU1(m_rfLC, m_rfLSD, data, frameType); + if (!m_rfLC.getGroup()) + controlByte = network::NET_CTRL_U2U; + m_p25->m_network->writeP25LDU1(m_rfLC, m_rfLSD, data, frameType, controlByte); break; case DUID::VSELP1: // ignore VSELP1 break; case DUID::LDU2: - m_p25->m_network->writeP25LDU2(m_rfLC, m_rfLSD, data); + if (!m_rfLC.getGroup()) + controlByte = network::NET_CTRL_U2U; + m_p25->m_network->writeP25LDU2(m_rfLC, m_rfLSD, data, controlByte); break; case DUID::VSELP2: // ignore VSELP2 From 8a6040ad169a3ecf56794e2fb7f3fe68d290bc7e Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 16:41:09 -0400 Subject: [PATCH 118/133] enhance handling of NXDN LC on the FNE; fix issue where dvmhost would not process a network RCCH burst; --- src/CompilerOptions.cmake | 5 ++ src/common/nxdn/lc/RTCH.cpp | 14 ++++ src/fne/network/callhandler/TagNXDNData.cpp | 91 +++++++++++++++++++++ src/host/nxdn/Control.cpp | 24 ++++-- 4 files changed, 125 insertions(+), 9 deletions(-) diff --git a/src/CompilerOptions.cmake b/src/CompilerOptions.cmake index 6c6670718..0c8eb7c41 100644 --- a/src/CompilerOptions.cmake +++ b/src/CompilerOptions.cmake @@ -35,6 +35,7 @@ option(DEBUG_NXDN_SACCH "" off) option(DEBUG_NXDN_UDCH "" off) option(DEBUG_NXDN_LICH "" off) option(DEBUG_NXDN_CAC "" off) +option(DEBUG_NXDN_RTCH "" off) option(DEBUG_P25_PDU_DATA "" off) option(DEBUG_P25_HDU "" off) option(DEBUG_P25_LDU1 "" off) @@ -96,6 +97,10 @@ if (DEBUG_NXDN_CAC) message(CHECK_START "NXDN CAC Debug") add_definitions(-DDEBUG_NXDN_CAC) endif (DEBUG_NXDN_CAC) +if (DEBUG_NXDN_RTCH) + message(CHECK_START "NXDN RTCH Debug") + add_definitions(-DDEBUG_NXDN_RTCH) +endif (DEBUG_NXDN_RTCH) if (DEBUG_P25_PDU_DATA) message(CHECK_START "P25 PDU Data Debug") add_definitions(-DDEBUG_P25_PDU_DATA) diff --git a/src/common/nxdn/lc/RTCH.cpp b/src/common/nxdn/lc/RTCH.cpp index a6758b0df..a476dc62a 100644 --- a/src/common/nxdn/lc/RTCH.cpp +++ b/src/common/nxdn/lc/RTCH.cpp @@ -181,6 +181,10 @@ bool RTCH::decodeLC(const uint8_t* data) { assert(data != nullptr); +#if DEBUG_NXDN_RTCH + Utils::dump(2U, "NXDN, RTCH::decodeLC(), RTCH", data, NXDN_RTCH_LC_LENGTH_BYTES); +#endif + m_messageType = data[0U] & 0x3FU; // Message Type // message type opcodes @@ -286,6 +290,12 @@ bool RTCH::decodeLC(const uint8_t* data) return false; } + // is this a private call? + if (m_callType == CallType::INDIVIDUAL) + m_group = false; + else + m_group = true; + return true; } @@ -405,6 +415,10 @@ void RTCH::encodeLC(uint8_t* data) LogError(LOG_NXDN, "RTCH::encodeRTCH(), unknown RTCH value, messageType = $%02X", m_messageType); return; } + +#if DEBUG_NXDN_RTCH + Utils::dump(2U, "NXDN, RTCH::encodeLC(), RTCH", data, NXDN_RTCH_LC_LENGTH_BYTES); +#endif } /* Internal helper to copy the the class. */ diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 85fe5e38a..3460255c2 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -11,6 +11,8 @@ #include "common/nxdn/NXDNDefines.h" #include "common/nxdn/channel/LICH.h" #include "common/nxdn/channel/CAC.h" +#include "common/nxdn/channel/FACCH1.h" +#include "common/nxdn/channel/UDCH.h" #include "common/nxdn/lc/rcch/RCCHFactory.h" #include "common/nxdn/lc/RTCH.h" #include "common/nxdn/NXDNUtils.h" @@ -91,6 +93,95 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI bool group = (data[15U] & 0x40U) == 0x40U ? false : true; lc.setGroup(group); + // process raw NXDN data bytes + UInt8Array frame; + uint8_t frameLength = buffer[23U]; + if (frameLength <= 24) { + frame = std::unique_ptr(new uint8_t[frameLength]); + ::memset(frame.get(), 0x00U, frameLength); + } + else { + frame = std::unique_ptr(new uint8_t[frameLength]); + ::memset(frame.get(), 0x00U, frameLength); + ::memcpy(frame.get(), buffer + 24U, frameLength); + } + + NXDNUtils::scrambler(frame.get() + 2U); + + channel::LICH lich; + bool valid = lich.decode(frame.get() + 2U); + + std::unique_ptr rcch; + if (valid) { + RFChannelType::E rfct = lich.getRFCT(); + FuncChannelType::E fct = lich.getFCT(); + ChOption::E option = lich.getOption(); + + if (rfct == RFChannelType::RCCH) { + rcch = lc::rcch::RCCHFactory::createRCCH(frame.get(), frameLength); + } + else if (rfct == RFChannelType::RTCH || rfct == RFChannelType::RDCH) { + // forward onto the specific processor for final processing and delivery + switch (fct) { + case FuncChannelType::USC_UDCH: + { + channel::UDCH udch; + bool validUDCH = udch.decode(data + 2U); + if (validUDCH) { + // The layer3 data will only be correct if valid is true + uint8_t buffer[NXDN_RTCH_LC_LENGTH_BYTES]; + udch.getData(buffer); + + lc.decode(buffer, NXDN_UDCH_LENGTH_BITS); + } + } + break; + default: + { + if (fct == FuncChannelType::USC_SACCH_NS) { + // the SACCH on a non-superblock frame is usually an idle and not interesting apart from the RAN. + channel::FACCH1 facch; + bool valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS); + if (!valid) + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); + if (valid) { + uint8_t buffer[10U]; + facch.getData(buffer); + + lc.decode(buffer, NXDN_FACCH1_FEC_LENGTH_BITS); + } + } else { + channel::FACCH1 facch; + bool valid = false; + switch (option) { + case ChOption::STEAL_FACCH: + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS); + if (!valid) + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); + break; + case ChOption::STEAL_FACCH1_1: + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS); + break; + case ChOption::STEAL_FACCH1_2: + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); + break; + default: + break; + } + + if (valid) { + uint8_t buffer[10U]; + facch.getData(buffer); + + lc.decode(buffer, NXDN_FACCH1_FEC_LENGTH_BITS); + } + } + } + break; + } + } + } + // is the stream valid? if (validate(peerId, lc, messageType, streamId)) { // is this peer ignored? diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index 5916b8286..306b9d529 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -990,17 +990,23 @@ void Control::processNetwork() if (valid) m_rfLastLICH = lich; - FuncChannelType::E usc = m_rfLastLICH.getFCT(); + RFChannelType::E rfct = m_rfLastLICH.getRFCT(); + FuncChannelType::E fct = m_rfLastLICH.getFCT(); ChOption::E option = m_rfLastLICH.getOption(); - // forward onto the specific processor for final processing and delivery - switch (usc) { - case FuncChannelType::USC_UDCH: - ret = m_data->processNetwork(option, lc, data.get(), frameLength); - break; - default: - ret = m_voice->processNetwork(usc, option, lc, data.get(), frameLength); - break; + if (rfct == RFChannelType::RCCH) { + m_control->processNetwork(fct, option, lc, data.get(), frameLength); + } + else if (rfct == RFChannelType::RTCH || rfct == RFChannelType::RDCH) { + // forward onto the specific processor for final processing and delivery + switch (fct) { + case FuncChannelType::USC_UDCH: + m_data->processNetwork(option, lc, data.get(), frameLength); + break; + default: + m_voice->processNetwork(fct, option, lc, data.get(), frameLength); + break; + } } } From 45d662f47c4ec414795a02ed98a147ed3b06866b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 22:56:30 -0400 Subject: [PATCH 119/133] refactor NXDN CC handling a bit; --- src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp | 4 + src/host/nxdn/Control.cpp | 27 ++-- src/host/nxdn/Control.h | 2 +- src/host/nxdn/packet/ControlSignaling.cpp | 131 ++++++++++++++----- src/host/nxdn/packet/ControlSignaling.h | 26 ++-- 5 files changed, 129 insertions(+), 61 deletions(-) diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp index b6f496ed6..3b3002f90 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp @@ -22,12 +22,14 @@ using namespace nxdn::lc::rcch; // --------------------------------------------------------------------------- /* Initializes a new instance of the MESSAGE_TYPE_REG class. */ + MESSAGE_TYPE_REG::MESSAGE_TYPE_REG() : RCCH() { m_messageType = MessageType::RCCH_REG; } /* Decode RCCH data. */ + void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t offset) { assert(data != nullptr); @@ -49,6 +51,7 @@ void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t off } /* Encode RCCH data. */ + void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) { assert(data != nullptr); @@ -73,6 +76,7 @@ void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) } /* Returns a string that represents the current RCCH. */ + std::string MESSAGE_TYPE_REG::toString(bool isp) { return (isp) ? std::string("RCCH_REG (Registration Request)") : diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index 306b9d529..5d4f21922 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -106,8 +106,8 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_interval(), m_frameLossCnt(0U), m_frameLossThreshold(DEFAULT_FRAME_LOSS_THRESHOLD), - m_ccFrameCnt(0U), m_ccSeq(0U), + m_ccIteration(0U), m_siteData(), m_rssiMapper(rssiMapper), m_rssi(0U), @@ -256,7 +256,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw uint8_t siteInfo1 = SiteInformation1::VOICE_CALL_SVC | SiteInformation1::DATA_CALL_SVC; uint8_t siteInfo2 = SiteInformation2::SHORT_DATA_CALL_SVC; if (m_enableControl) { - siteInfo1 |= SiteInformation1::LOC_REG_SVC;//| SiteInformation1::GRP_REG_SVC; + siteInfo1 |= SiteInformation1::LOC_REG_SVC; } /* @@ -328,7 +328,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Disable Grant Source ID Check: yes"); } if (m_supervisor) - LogMessage(LOG_DMR, "Host is configured to operate as a NXDN control channel, site controller mode."); + LogMessage(LOG_NXDN, "Host is configured to operate as a NXDN control channel, site controller mode."); } if (m_defaultNetIdleTalkgroup != 0U) { @@ -1271,10 +1271,6 @@ bool Control::writeRF_ControlData() if (!m_enableControl) return false; - if (m_ccFrameCnt == 254U) { - m_ccFrameCnt = 0U; - } - // don't add any frames if the queue is full uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; uint32_t space = m_txQueue.freeSpace(); @@ -1282,20 +1278,21 @@ bool Control::writeRF_ControlData() return false; } - const uint8_t maxSeq = m_control->m_bcchCnt + (m_control->m_ccchPagingCnt + m_control->m_ccchMultiCnt) * - m_control->m_rcchGroupingCnt * m_control->m_rcchIterateCnt; - if (m_ccSeq == maxSeq) { + if (m_ccIteration > m_control->m_rcchIterateCnt) { + m_ccIteration = 0U; m_ccSeq = 0U; } + const uint8_t maxSeq = (m_control->m_ccchPagingCnt + m_control->m_ccchMultiCnt) * m_control->m_rcchGroupingCnt; + if (m_ccSeq > maxSeq) { + m_ccSeq = 1U; + m_ccIteration++; + } + if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { - m_control->writeRF_ControlData(m_ccFrameCnt, m_ccSeq, true); + m_control->writeRF_ControlData(m_ccSeq, m_ccSeq == 0U); m_ccSeq++; - if (m_ccSeq == maxSeq) { - m_ccFrameCnt++; - } - return true; } diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index 37bd23c83..f65fa1dbe 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -330,8 +330,8 @@ namespace nxdn uint8_t m_frameLossCnt; uint8_t m_frameLossThreshold; - uint8_t m_ccFrameCnt; uint8_t m_ccSeq; + uint8_t m_ccIteration; SiteData m_siteData; diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index d7c274a33..f538c6f57 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -287,10 +287,12 @@ bool ControlSignaling::processNetwork(FuncChannelType::E fct, ChOption::E option ControlSignaling::ControlSignaling(Control* nxdn, bool debug, bool verbose) : m_nxdn(nxdn), m_bcchCnt(1U), - m_rcchGroupingCnt(1U), m_ccchPagingCnt(2U), m_ccchMultiCnt(2U), - m_rcchIterateCnt(2U), + m_rcchGroupingCnt(1U), + m_rcchIterateCnt(4U), + m_pgRCCHQueue((NXDN_FRAME_LENGTH_BYTES + 2U) * 16U, "RCCH Paging Frame"), + m_mpRCCHQueue((NXDN_FRAME_LENGTH_BYTES + 2U) * 16U, "RCCH Multipurpose Frame"), m_verifyAff(false), m_verifyReg(false), m_disableGrantSrcIdCheck(false), @@ -326,7 +328,7 @@ void ControlSignaling::writeNetwork(const uint8_t *data, uint32_t len) /* Helper to write a single-block RCCH packet. */ -void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool imm) +void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool paging) { if (!m_nxdn->m_enableControl) return; @@ -365,9 +367,10 @@ void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool imm) if (!noNetwork) writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); - if (m_nxdn->m_duplex) { - m_nxdn->addFrame(data, false, imm); - } + if (paging) + m_pgRCCHQueue.addData(data, NXDN_FRAME_LENGTH_BYTES + 2U); + else + m_mpRCCHQueue.addData(data, NXDN_FRAME_LENGTH_BYTES + 2U); } /* @@ -376,10 +379,8 @@ void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool imm) /* Helper to write control channel packet data. */ -void ControlSignaling::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS) +void ControlSignaling::writeRF_ControlData(uint8_t n, bool first) { - uint8_t i = 0U, seqCnt = 0U; - if (!m_nxdn->m_enableControl) return; @@ -400,26 +401,42 @@ void ControlSignaling::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adj return; } - do - { - if (m_debug) { - LogDebugEx(LOG_NXDN, "ControlSignaling::writeRF_ControlData()", "frameCnt = %u, seq = %u", frameCnt, n); - } + if (m_debug) { + LogDebugEx(LOG_NXDN, "ControlSignaling::writeRF_ControlData()", "seq = %u", n); + } - switch (n) - { - case 0: - writeRF_CC_Message_Site_Info(); - break; - default: - writeRF_CC_Message_Service_Info(); - break; + if (first) { + // transmit the BCCH frame + writeRF_CC_Message_Site_Info(); + } + else { + if (n > 0U && n <= m_ccchPagingCnt - 1U) { + // transmit the next paging frame + if (!m_pgRCCHQueue.isEmpty()) { + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + m_pgRCCHQueue.get(data, NXDN_FRAME_LENGTH_BYTES + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data); + } + } + else + writeRF_CC_Message_Service_Info(); } + else { + // transmit the next multipurpose frame + if (!m_mpRCCHQueue.isEmpty()) { + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + m_mpRCCHQueue.get(data, NXDN_FRAME_LENGTH_BYTES + 2U); - if (seqCnt > 0U) - n++; - i++; - } while (i <= seqCnt); + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data); + } + } + else + writeRF_CC_Message_Idle(); + } + } lc::RCCH::setVerbose(rcchVerbose); m_nxdn->m_debug = m_debug = controlDebug; @@ -433,7 +450,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin bool encryption = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag uint8_t priority = ((serviceOptions & 0xFFU) & 0x07U); // Priority - std::unique_ptr rcch = std::make_unique(); + std::unique_ptr rcch = std::make_unique(); // are we skipping checking? if (!skip) { @@ -559,11 +576,19 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin if (!net) { ::ActivityLog("NXDN", true, "group grant request from %u to TG %u", srcId, dstId); } + + rcch->setCallType(CallType::CONFERENCE); + rcch->setDuplex(false); + rcch->setTransmissionMode(TransmissionMode::MODE_4800); } else { if (!net) { ::ActivityLog("NXDN", true, "unit-to-unit grant request from %u to %u", srcId, dstId); } + + rcch->setCallType(CallType::INDIVIDUAL); + rcch->setDuplex(false); + rcch->setTransmissionMode(TransmissionMode::MODE_4800); } // callback RPC to permit the granted TG on the specified voice channel @@ -610,7 +635,6 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } - rcch->setMessageType(MessageType::RTCH_VCALL); rcch->setGrpVchNo(chNo); rcch->setGroup(grp); rcch->setSrcId(srcId); @@ -626,7 +650,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } // transmit group grant - writeRF_Message_Imm(rcch.get(), net); + writeRF_Message(rcch.get(), net, true); return true; } @@ -653,7 +677,7 @@ void ControlSignaling::writeRF_Message_Deny(uint32_t srcId, uint32_t dstId, uint service, srcId, dstId); } - writeRF_Message_Imm(rcch.get(), false); + writeRF_Message(rcch.get(), false); } /* Helper to write a group registration response packet. */ @@ -711,7 +735,7 @@ bool ControlSignaling::writeRF_Message_Grp_Reg_Rsp(uint32_t srcId, uint32_t dstI m_nxdn->m_network->announceGroupAffiliation(srcId, dstId); } - writeRF_Message_Imm(rcch.get(), false); + writeRF_Message(rcch.get(), false); return ret; } @@ -768,7 +792,7 @@ void ControlSignaling::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t dstId, rcch->setSrcId(srcId); rcch->setDstId(dstId); - writeRF_Message_Imm(rcch.get(), true); + writeRF_Message(rcch.get(), true); } /* Helper to write a CC SITE_INFO broadcast packet on the RF interface. */ @@ -861,3 +885,46 @@ void ControlSignaling::writeRF_CC_Message_Service_Info() m_nxdn->addFrame(data); } } + +/* Helper to write a CC IDLE broadcast packet on the RF interface. */ + +void ControlSignaling::writeRF_CC_Message_Idle() +{ + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); + + Sync::addNXDNSync(data + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(RFChannelType::RCCH); + lich.setFCT(FuncChannelType::CAC_OUTBOUND); + lich.setOption(ChOption::DATA_IDLE); + lich.setOutbound(true); + lich.encode(data + 2U); + + uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + std::unique_ptr rcch = std::make_unique(); + DEBUG_LOG_MSG(rcch->toString()); + rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS / 2U); + //rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS / 2U, NXDN_RCCH_LC_LENGTH_BITS / 2U); + + // generate the CAC + channel::CAC cac; + cac.setRAN(m_nxdn->m_ran); + cac.setStructure(ChStructure::SR_RCCH_SINGLE); + cac.setData(buffer); + cac.encode(data + 2U); + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + NXDNUtils::scrambler(data + 2U); + NXDNUtils::addPostBits(data + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data); + } +} diff --git a/src/host/nxdn/packet/ControlSignaling.h b/src/host/nxdn/packet/ControlSignaling.h index 67fec18a2..f66a295f2 100644 --- a/src/host/nxdn/packet/ControlSignaling.h +++ b/src/host/nxdn/packet/ControlSignaling.h @@ -74,11 +74,14 @@ namespace nxdn Control* m_nxdn; uint8_t m_bcchCnt; - uint8_t m_rcchGroupingCnt; uint8_t m_ccchPagingCnt; uint8_t m_ccchMultiCnt; + uint8_t m_rcchGroupingCnt; uint8_t m_rcchIterateCnt; + RingBuffer m_pgRCCHQueue; + RingBuffer m_mpRCCHQueue; + bool m_verifyAff; bool m_verifyReg; @@ -112,19 +115,13 @@ namespace nxdn ** Modem Frame Queuing */ - /** - * @brief Helper to write a immediate single-block RCCH packet. - * @param rcch RCCH to write to the modem. - * @param noNetwork Flag indicating not to write the TSBK to the network. - */ - void writeRF_Message_Imm(lc::RCCH *rcch, bool noNetwork) { writeRF_Message(rcch, noNetwork, true); } /** * @brief Helper to write a single-block RCCH packet. * @param rcch RCCH to write to the modem. - * @param noNetwork Flag indicating not to write the TSBK to the network. - * @param imm Flag indicating the TSBK should be written to the immediate queue. + * @param noNetwork Flag indicating not to write the RCCH to the network. + * @param paging Flag indicating the RCCH should be written to the paging queue. */ - void writeRF_Message(lc::RCCH* rcch, bool noNetwork, bool imm = false); + void writeRF_Message(lc::RCCH* rcch, bool noNetwork, bool paging = false); /* ** Control Signalling Logic @@ -132,11 +129,10 @@ namespace nxdn /** * @brief Helper to write control channel packet data. - * @param frameCnt Frame counter. * @param n - * @param adjSS Flag indicating whether or not adjacent site status should be broadcast. + * @param first */ - void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS); + void writeRF_ControlData(uint8_t n, bool first); /** * @brief Helper to write a grant packet. @@ -181,6 +177,10 @@ namespace nxdn * @brief Helper to write a CC SRV_INFO broadcast packet on the RF interface. */ void writeRF_CC_Message_Service_Info(); + /** + * @brief Helper to write a CC IDLE broadcast packet on the RF interface. + */ + void writeRF_CC_Message_Idle(); }; } // namespace packet } // namespace nxdn From a5cfeb7b0566dcd90a3f595a05caf6c873769072 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 23:33:32 -0400 Subject: [PATCH 120/133] add support to translate a raw DENY/QUEUE/CAUSE/REASON value for DMR/P25/NXDN into a human readable string; --- src/common/dmr/DMRUtils.cpp | 85 +++++++++++++++++++++ src/common/dmr/DMRUtils.h | 9 ++- src/common/nxdn/NXDNUtils.cpp | 43 +++++++++++ src/common/nxdn/NXDNUtils.h | 9 ++- src/common/p25/P25Utils.cpp | 70 ++++++++++++++++- src/common/p25/P25Utils.h | 15 +++- src/fne/network/callhandler/TagDMRData.cpp | 10 ++- src/fne/network/callhandler/TagDMRData.h | 3 +- src/fne/network/callhandler/TagNXDNData.cpp | 4 +- src/fne/network/callhandler/TagP25Data.cpp | 10 ++- src/host/dmr/packet/ControlSignaling.cpp | 12 +++ src/host/nxdn/packet/ControlSignaling.cpp | 4 +- src/host/p25/packet/ControlSignaling.cpp | 10 ++- 13 files changed, 265 insertions(+), 19 deletions(-) create mode 100644 src/common/dmr/DMRUtils.cpp diff --git a/src/common/dmr/DMRUtils.cpp b/src/common/dmr/DMRUtils.cpp new file mode 100644 index 000000000..964f68cf2 --- /dev/null +++ b/src/common/dmr/DMRUtils.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/DMRUtils.h" +#include "Utils.h" + +using namespace dmr; +using namespace dmr::defines; + +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +/* Helper to convert a denial reason code to a string. */ + +std::string DMRUtils::rsnToString(uint8_t reason) +{ + switch (reason) { + case ReasonCode::TS_ACK_RSN_MSG: + return std::string("TS_ACK_RSN_MSG (Message Accepted)"); + case ReasonCode::TS_ACK_RSN_REG: + return std::string("TS_ACK_RSN_REG (Registration Accepted)"); + case ReasonCode::TS_ACK_RSN_AUTH_RESP: + return std::string("TS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)"); + case ReasonCode::TS_ACK_RSN_REG_SUB_ATTACH: + return std::string("TS_ACK_RSN_REG_SUB_ATTACH (Registration Response with subscription)"); + case ReasonCode::MS_ACK_RSN_MSG: + return std::string("MS_ACK_RSN_MSG (Message Accepted)"); + case ReasonCode::MS_ACK_RSN_AUTH_RESP: + return std::string("MS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)"); + + case ReasonCode::TS_DENY_RSN_SYS_UNSUPPORTED_SVC: + return std::string("TS_DENY_RSN_SYS_UNSUPPORTED_SVC (System Unsupported Service)"); + case ReasonCode::TS_DENY_RSN_PERM_USER_REFUSED: + return std::string("TS_DENY_RSN_PERM_USER_REFUSED (User Permenantly Refused)"); + case ReasonCode::TS_DENY_RSN_TEMP_USER_REFUSED: + return std::string("TS_DENY_RSN_TEMP_USER_REFUSED (User Temporarily Refused)"); + case ReasonCode::TS_DENY_RSN_TRSN_SYS_REFUSED: + return std::string("TS_DENY_RSN_TRSN_SYS_REFUSED (System Refused)"); + case ReasonCode::TS_DENY_RSN_TGT_NOT_REG: + return std::string("TS_DENY_RSN_TGT_NOT_REG (Target Not Registered)"); + case ReasonCode::TS_DENY_RSN_TGT_UNAVAILABLE: + return std::string("TS_DENY_RSN_TGT_UNAVAILABLE (Target Unavailable)"); + case ReasonCode::TS_DENY_RSN_SYS_BUSY: + return std::string("TS_DENY_RSN_SYS_BUSY (System Busy)"); + case ReasonCode::TS_DENY_RSN_SYS_NOT_READY: + return std::string("TS_DENY_RSN_SYS_NOT_READY (System Not Ready)"); + case ReasonCode::TS_DENY_RSN_CALL_CNCL_REFUSED: + return std::string("TS_DENY_RSN_CALL_CNCL_REFUSED (Call Cancel Refused)"); + case ReasonCode::TS_DENY_RSN_REG_REFUSED: + return std::string("TS_DENY_RSN_REG_REFUSED (Registration Refused)"); + case ReasonCode::TS_DENY_RSN_REG_DENIED: + return std::string("TS_DENY_RSN_REG_DENIED (Registration Denied)"); + case ReasonCode::TS_DENY_RSN_MS_NOT_REG: + return std::string("TS_DENY_RSN_MS_NOT_REG (MS Not Registered)"); + case ReasonCode::TS_DENY_RSN_TGT_BUSY: + return std::string("TS_DENY_RSN_TGT_BUSY (Target Busy)"); + case ReasonCode::TS_DENY_RSN_TGT_GROUP_NOT_VALID: + return std::string("TS_DENY_RSN_TGT_GROUP_NOT_VALID (Group Not Valid)"); + + case ReasonCode::TS_QUEUED_RSN_NO_RESOURCE: + return std::string("TS_QUEUED_RSN_NO_RESOURCE (No Resources Available)"); + case ReasonCode::TS_QUEUED_RSN_SYS_BUSY: + return std::string("TS_QUEUED_RSN_SYS_BUSY (System Busy)"); + + case ReasonCode::TS_WAIT_RSN: + return std::string("TS_WAIT_RSN (Wait)"); + + case ReasonCode::MS_DENY_RSN_UNSUPPORTED_SVC: + return std::string("MS_DENY_RSN_UNSUPPORTED_SVC (Service Unsupported)"); + + default: + return std::string(); + } +} diff --git a/src/common/dmr/DMRUtils.h b/src/common/dmr/DMRUtils.h index 471c4447c..aa4aaa9a6 100644 --- a/src/common/dmr/DMRUtils.h +++ b/src/common/dmr/DMRUtils.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -136,6 +136,13 @@ namespace dmr return id; } + + /** + * @brief Helper to convert a reason code to a string. + * @param reason Reason code. + * @returns std::string Reason code string. + */ + static std::string rsnToString(uint8_t reason); }; } // namespace dmr diff --git a/src/common/nxdn/NXDNUtils.cpp b/src/common/nxdn/NXDNUtils.cpp index c94e32afd..0c0691149 100644 --- a/src/common/nxdn/NXDNUtils.cpp +++ b/src/common/nxdn/NXDNUtils.cpp @@ -55,3 +55,46 @@ void NXDNUtils::addPostBits(uint8_t* data) WRITE_BIT(data, n, b); } } + +/* Helper to convert a cause code to a string. */ + +std::string NXDNUtils::causeToString(uint8_t cause) +{ + switch (cause) { + case CauseResponse::RSRC_NOT_AVAIL_NETWORK: + return std::string("RSRC_NOT_AVAIL_NETWORK (Resource Not Available - Network)"); + case CauseResponse::RSRC_NOT_AVAIL_TEMP: + return std::string("RSRC_NOT_AVAIL_TEMP (Resource Not Available - Temporary)"); + case CauseResponse::RSRC_NOT_AVAIL_QUEUED: + return std::string("RSRC_NOT_AVAIL_QUEUED (Resource Not Available - Queued)"); + case CauseResponse::SVC_UNAVAILABLE: + return std::string("SVC_UNAVAILABLE (Service Unavailable)"); + case CauseResponse::PROC_ERROR: + return std::string("PROC_ERROR (Procedure Error - Lack of packet data)"); + case CauseResponse::PROC_ERROR_UNDEF: + return std::string("PROC_ERROR_UNDEF (Procedure Error - Invalid packet data)"); + + case CauseResponse::VD_GRP_NOT_PERM: + return std::string("VD_GRP_NOT_PERM (Voice Group Not Permitted)"); + case CauseResponse::VD_REQ_UNIT_NOT_PERM: + return std::string("VD_REQ_UNIT_NOT_PERM (Voice Requesting Unit Not Permitted)"); + case CauseResponse::VD_TGT_UNIT_NOT_PERM: + return std::string("VD_TGT_UNIT_NOT_PERM (Voice Target Unit Not Permitted)"); + case CauseResponse::VD_REQ_UNIT_NOT_REG: + return std::string("VD_REQ_UNIT_NOT_REG (Voice Requesting Unit Not Registered)"); + case CauseResponse::VD_QUE_CHN_RESOURCE_NOT_AVAIL: + return std::string("VD_QUE_CHN_RESOURCE_NOT_AVAIL (Voice Channel Resources Unavailable)"); + case CauseResponse::VD_QUE_TGT_UNIT_BUSY: + return std::string("VD_QUE_TGT_UNIT_BUSY (Voice Target Unit Busy)"); + case CauseResponse::VD_QUE_GRP_BUSY: + return std::string("VD_QUE_GRP_BUSY (Voice Group Busy)"); + + case CauseResponse::DISC_USER: + return std::string("DISC_USER (Disconnect by User)"); + case CauseResponse::DISC_OTHER: + return std::string("DISC_OTHER (Other Disconnect)"); + + default: + return std::string(); + } +} diff --git a/src/common/nxdn/NXDNUtils.h b/src/common/nxdn/NXDNUtils.h index 6ceafb136..264931b2f 100644 --- a/src/common/nxdn/NXDNUtils.h +++ b/src/common/nxdn/NXDNUtils.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -41,6 +41,13 @@ namespace nxdn * @param data Buffer to apply post field bits to. */ static void addPostBits(uint8_t* data); + + /** + * @brief Helper to convert a cause code to a string. + * @param cause Cause code. + * @returns std::string Cause code string. + */ + static std::string causeToString(uint8_t cause); }; } // namespace nxdn diff --git a/src/common/p25/P25Utils.cpp b/src/common/p25/P25Utils.cpp index e7b7c1ff2..6ac0adf90 100644 --- a/src/common/p25/P25Utils.cpp +++ b/src/common/p25/P25Utils.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -242,3 +242,71 @@ uint32_t P25Utils::compare(const uint8_t* data1, const uint8_t* data2, uint32_t return errs; } + +/* Helper to convert a denial reason code to a string. */ + +std::string P25Utils::denyRsnToString(uint8_t reason) +{ + switch (reason) { + case ReasonCode::DENY_REQ_UNIT_NOT_VALID: + return std::string("DENY_REQ_UNIT_NOT_VALID (Requesting Unit Not Valid)"); + case ReasonCode::DENY_REQ_UNIT_NOT_AUTH: + return std::string("DENY_REQ_UNIT_NOT_AUTH (Requesting Unit Not Authenticated)"); + + case ReasonCode::DENY_TGT_UNIT_NOT_VALID: + return std::string("DENY_TGT_UNIT_NOT_VALID (Target Unit Not Valid)"); + case ReasonCode::DENY_TGT_UNIT_NOT_AUTH: + return std::string("DENY_TGT_UNIT_NOT_AUTH (Target Unit Not Authenticated)"); + case ReasonCode::DENY_SU_FAILED_AUTH: + return std::string("DENY_SU_FAILED_AUTH (Target Unit Failed Authentication)"); + case ReasonCode::DENY_TGT_UNIT_REFUSED: + return std::string("DENY_TGT_UNIT_REFUSED (Target Unit Refused)"); + + case ReasonCode::DENY_TGT_GROUP_NOT_VALID: + return std::string("DENY_TGT_GROUP_NOT_VALID (Target Group Not Valid)"); + case ReasonCode::DENY_TGT_GROUP_NOT_AUTH: + return std::string("DENY_TGT_GROUP_NOT_AUTH (Target Group Not Authenticated)"); + + case ReasonCode::DENY_NO_NET_RSRC_AVAIL: + return std::string("DENY_NO_NET_RSRC_AVAIL (Requested Network Resources Not Available)"); + case ReasonCode::DENY_NO_RF_RSRC_AVAIL: + return std::string("DENY_NO_RF_RSRC_AVAIL (Requested RF Resources Not Available)"); + case ReasonCode::DENY_SVC_IN_USE: + return std::string("DENY_SVC_IN_USE (Service In Use)"); + + case ReasonCode::DENY_SITE_ACCESS_DENIAL: + return std::string("DENY_SITE_ACCESS_DENIAL (Site Access Denial)"); + + case ReasonCode::DENY_PTT_COLLIDE: + return std::string("DENY_PTT_COLLIDE (Push-to-Talk Collision)"); + case ReasonCode::DENY_PTT_BONK: + return std::string("DENY_PTT_BONK (Push-to-Talk Denial/Bonk)"); + + case ReasonCode::DENY_SYS_UNSUPPORTED_SVC: + return std::string("DENY_SYS_UNSUPPORTED_SVC (Service Unsupported)"); + + default: + return std::string(); + } +} + +/* Helper to convert a queue reason code to a string. */ + +std::string P25Utils::queueRsnToString(uint8_t reason) +{ + switch (reason) { + case ReasonCode::QUE_REQ_ACTIVE_SERVICE: + return std::string("QUE_REQ_ACTIVE_SERVICE (Requested Service Active)"); + case ReasonCode::QUE_TGT_ACTIVE_SERVICE: + return std::string("QUE_TGT_ACTIVE_SERVICE (Target Service Active)"); + + case ReasonCode::QUE_TGT_UNIT_QUEUED: + return std::string("QUE_TGT_UNIT_QUEUED (Target Unit Queued)"); + + case ReasonCode::QUE_CHN_RESOURCE_NOT_AVAIL: + return std::string("QUE_CHN_RESOURCE_NOT_AVAIL (Channel Resource Not Available)"); + + default: + return std::string(); + } +} diff --git a/src/common/p25/P25Utils.h b/src/common/p25/P25Utils.h index bc030a43a..66b4cb27c 100644 --- a/src/common/p25/P25Utils.h +++ b/src/common/p25/P25Utils.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2021 Bryan Biedenkapp, N2PLL + * Copyright (C) 2021-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -198,6 +198,19 @@ namespace p25 * @returns uint32_t */ static uint32_t compare(const uint8_t* data1, const uint8_t* data2, uint32_t length); + + /** + * @brief Helper to convert a denial reason code to a string. + * @param reason Reason code. + * @returns std::string Reason code string. + */ + static std::string denyRsnToString(uint8_t reason); + /** + * @brief Helper to convert a queue reason code to a string. + * @param reason Reason code. + * @returns std::string Reason code string. + */ + static std::string queueRsnToString(uint8_t reason); }; } // namespace p25 diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 01affed85..31f0b8e68 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -1209,7 +1209,7 @@ bool TagDMRData::write_CSBK_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI /* Helper to write a NACK RSP packet. */ -void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t reason, uint8_t service) +void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t slot, uint8_t reason, uint8_t service) { std::unique_ptr csbk = std::make_unique(); csbk->setServiceKind(service); @@ -1217,7 +1217,13 @@ void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t re csbk->setSrcId(WUID_ALL); // hmmm... csbk->setDstId(dstId); - write_CSBK(peerId, 1U, csbk.get()); + if (m_network->m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + slot, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), + csbk->getSrcId(), csbk->getDstId()); + } + + write_CSBK(peerId, slot, csbk.get()); } /* Helper to write a network CSBK. */ diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index 43f424920..c1313b2e3 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -273,9 +273,10 @@ namespace network * @param peerId Peer ID. * @param dstId Destination ID. * @param reason Denial Reason. + * @param slot DMR slot number. * @param service Service being denied. */ - void write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t reason, uint8_t service); + void write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t slot, uint8_t reason, uint8_t service); /** * @brief Helper to write a network CSBK. diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 3460255c2..d44213626 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -1006,8 +1006,8 @@ void TagNXDNData::write_Message_Deny(uint32_t peerId, uint32_t srcId, uint32_t d rcch->setDstId(dstId); if (m_network->m_verbose) { - LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X, service = $%02X, srcId = %u, dstId = %u", - service, srcId, dstId); + LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", + reason, NXDNUtils::causeToString(reason), service, srcId, dstId); } write_Message(peerId, rcch.get()); diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index b81f61e88..dbcecdbc1 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1596,8 +1596,9 @@ void TagP25Data::write_TSDU_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId osp->setGroup(grp); if (m_network->m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::denyRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } write_TSDU(peerId, osp.get()); @@ -1616,8 +1617,9 @@ void TagP25Data::write_TSDU_Queue(uint32_t peerId, uint32_t srcId, uint32_t dstI osp->setGroup(grp); if (m_network->m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::queueRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } write_TSDU(peerId, osp.get()); diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 615299291..33389b0ec 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -776,6 +776,12 @@ void ControlSignaling::writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint csbk->setSrcId(WUID_ALL); // hmmm... csbk->setDstId(dstId); + if (m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), + csbk->getSrcId(), csbk->getDstId()); + } + writeRF_CSBK_Imm(csbk.get()); } @@ -789,6 +795,12 @@ void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uin csbk->setSrcId(WUID_ALL); // hmmm... csbk->setDstId(dstId); + if (m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), + csbk->getSrcId(), csbk->getDstId()); + } + writeRF_CSBK_Imm(csbk.get()); } diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index f538c6f57..59b9a1b17 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -673,8 +673,8 @@ void ControlSignaling::writeRF_Message_Deny(uint32_t srcId, uint32_t dstId, uint rcch->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X, service = $%02X, srcId = %u, dstId = %u", - service, srcId, dstId); + LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", + reason, NXDNUtils::causeToString(reason), service, srcId, dstId); } writeRF_Message(rcch.get(), false); diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index d0c5edd79..7cd0971fc 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2770,8 +2770,9 @@ void ControlSignaling::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t osp->setGroup(grp); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::denyRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } writeRF_TSDU_SBF_Imm(osp.get(), false); @@ -2969,8 +2970,9 @@ void ControlSignaling::writeRF_TSDU_Queue(uint32_t srcId, uint32_t dstId, uint8_ osp->setGroup(grp); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::queueRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } writeRF_TSDU_SBF_Imm(osp.get(), false); From 0858ad8623f0097b1759ed58da489ab24634ea8c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 15 Aug 2025 23:35:25 -0400 Subject: [PATCH 121/133] whoops hastily missed std::string -> c_str conversion; --- src/host/nxdn/packet/ControlSignaling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 59b9a1b17..e4e1fb862 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -674,7 +674,7 @@ void ControlSignaling::writeRF_Message_Deny(uint32_t srcId, uint32_t dstId, uint if (m_verbose) { LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", - reason, NXDNUtils::causeToString(reason), service, srcId, dstId); + reason, NXDNUtils::causeToString(reason).c_str(), service, srcId, dstId); } writeRF_Message(rcch.get(), false); From 651818f5340aeec83e3e648415eedf28f5c07757 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 16 Aug 2025 10:45:12 -0400 Subject: [PATCH 122/133] ensure SSRC is maintained for unit registration announcements; --- src/fne/network/FNENetwork.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index a70464cc8..7f04225c2 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1372,7 +1372,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isPeerLink()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false, 0U, ssrc); } } } From 01eed3bc36f23cb97a4f6d13e88c6fd07f2525a5 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 16 Aug 2025 11:05:39 -0400 Subject: [PATCH 123/133] more implementation for private call routing; --- src/fne/network/callhandler/TagDMRData.cpp | 47 +++++++++++++++- src/fne/network/callhandler/TagNXDNData.cpp | 49 ++++++++++++++++- src/fne/network/callhandler/TagP25Data.cpp | 61 ++++++++++++++++++++- 3 files changed, 153 insertions(+), 4 deletions(-) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 31f0b8e68..87e0c7930 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -345,8 +345,47 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].lastPacket = hrc::now(); + bool noConnectedPeerRepeat = false; + bool privateCallInProgress = false; + + // is this a private call in-progress? + if (m_network->m_restrictPVCallToRegOnly) { + if (flco == FLCO::PRIVATE) { + privateCallInProgress = true; + } + + if (privateCallInProgress) { + // if we've not determined the destination peer, we have to repeat it everywhere + if (m_statusPVCall[dstId].dstPeerId == 0U) { + noConnectedPeerRepeat = false; + privateCallInProgress = false; // trick the system to repeat everywhere + } else { + // if this is a private call, check if the destination peer is one directly connected to us, if not + // flag the call so it only repeats to external peers + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { + noConnectedPeerRepeat = true; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + if (conn->isExternalPeer()) { + continue; + } + } + + if (m_statusPVCall[dstId].dstPeerId == peer.first) { + noConnectedPeerRepeat = false; + break; + } + } + } + } + } + } + } + // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { @@ -411,6 +450,12 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_frameQueue->flushQueue(); } + // if this is a private call, and we have already repeated to the connected peer that registered + // the unit, don't repeat to any external peers + if (privateCallInProgress && !noConnectedPeerRepeat) { + return true; + } + // repeat traffic to external peers if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index d44213626..cfd7a9ae4 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -375,8 +375,47 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_status[dstId].lastPacket = hrc::now(); + bool noConnectedPeerRepeat = false; + bool privateCallInProgress = false; + + // is this a private call in-progress? + if (m_network->m_restrictPVCallToRegOnly) { + if (!group) { + privateCallInProgress = true; + } + + if (privateCallInProgress) { + // if we've not determined the destination peer, we have to repeat it everywhere + if (m_statusPVCall[dstId].dstPeerId == 0U) { + noConnectedPeerRepeat = false; + privateCallInProgress = false; // trick the system to repeat everywhere + } else { + // if this is a private call, check if the destination peer is one directly connected to us, if not + // flag the call so it only repeats to external peers + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { + noConnectedPeerRepeat = true; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + if (conn->isExternalPeer()) { + continue; + } + } + + if (m_statusPVCall[dstId].dstPeerId == peer.first) { + noConnectedPeerRepeat = false; + break; + } + } + } + } + } + } + } + // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { @@ -441,6 +480,12 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_network->m_frameQueue->flushQueue(); } + // if this is a private call, and we have already repeated to the connected peer that registered + // the unit, don't repeat to any external peers + if (privateCallInProgress && !noConnectedPeerRepeat) { + return true; + } + // repeat traffic to external peers if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { @@ -1007,7 +1052,7 @@ void TagNXDNData::write_Message_Deny(uint32_t peerId, uint32_t srcId, uint32_t d if (m_network->m_verbose) { LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", - reason, NXDNUtils::causeToString(reason), service, srcId, dstId); + reason, NXDNUtils::causeToString(reason).c_str(), service, srcId, dstId); } write_Message(peerId, rcch.get()); diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index dbcecdbc1..4728f7d4c 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -385,8 +385,61 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].lastPacket = hrc::now(); + bool noConnectedPeerRepeat = false; + bool privateCallInProgress = false; + + // is this a private call in-progress? + if (m_network->m_restrictPVCallToRegOnly) { + if ((control.getLCO() != LCO::PRIVATE) && !control.getGroup()) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == control.getDstId()) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + privateCallInProgress = true; + } + } else { + if (lco == LCO::PRIVATE) { + privateCallInProgress = true; + } + } + + if (privateCallInProgress) { + // if we've not determined the destination peer, we have to repeat it everywhere + if (m_statusPVCall[dstId].dstPeerId == 0U) { + noConnectedPeerRepeat = false; + privateCallInProgress = false; // trick the system to repeat everywhere + } else { + // if this is a private call, check if the destination peer is one directly connected to us, if not + // flag the call so it only repeats to external peers + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { + noConnectedPeerRepeat = true; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + if (conn->isExternalPeer()) { + continue; + } + } + + if (m_statusPVCall[dstId].dstPeerId == peer.first) { + noConnectedPeerRepeat = false; + break; + } + } + } + } + } + } + } + // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { @@ -456,6 +509,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_frameQueue->flushQueue(); } + // if this is a private call, and we have already repeated to the connected peer that registered + // the unit, don't repeat to any external peers + if (privateCallInProgress && !noConnectedPeerRepeat) { + return true; + } + // repeat traffic to external peers if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { From 78110c718eadaf056531b738f24ddf4d271ed052 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 18 Aug 2025 07:24:15 -0400 Subject: [PATCH 124/133] add missing clear flag to DMR payload activate RPC; --- src/host/dmr/packet/ControlSignaling.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 33389b0ec..e458c1ed5 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -1033,6 +1033,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ req["group"].set(grp); bool voice = true; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } @@ -1124,6 +1126,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ req["group"].set(grp); bool voice = true; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } @@ -1276,6 +1280,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u req["group"].set(grp); bool voice = false; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } @@ -1322,6 +1328,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u req["group"].set(grp); bool voice = false; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } From e5e0f262870e2677ab6ab3e1af036aa84f2545f0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 21 Aug 2025 14:34:00 -0400 Subject: [PATCH 125/133] correct crash for fsc set to true when not using DFSI TIA-102/UDP; --- src/host/Host.Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 2e2ac38b6..3c4bb617b 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -750,7 +750,7 @@ bool Host::createModem() m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(Host::rmtPortModemHandler, this)); } - if (m_isModemDFSI && useFSCForUDP) { + if (g_remoteModemMode && m_isModemDFSI && useFSCForUDP) { modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDFSIRemotePort); udpPort->openFSC(); } From b758099be2211b62f732135ade41fb69429fb519 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 22 Aug 2025 17:14:03 -0400 Subject: [PATCH 126/133] add support to disable a failed CRC-32 for P25 PDU data; ignore CRC-32 errors for AMBT PDUs; --- configs/config.example.yml | 2 ++ src/host/p25/Control.cpp | 4 ++++ src/host/p25/Control.h | 2 ++ src/host/p25/packet/Data.cpp | 6 +++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index 19c2e903e..14359fc37 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -275,6 +275,8 @@ protocols: dumpDataPacket: false # Flag indicating whether or not this host will repeat P25 data traffic. repeatDataPacket: true + # Flag indicating whether or not the host will ignore CRC errors in P25 PDU data packets. + ignoreDataCRC: false # Flag indicating whether verbose dumping of P25 TSBK data is enabled. dumpTsbkData: false # Amount of time to hang after a voice call. diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 3e937362c..29c3d4680 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -65,6 +65,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_modem(modem), m_isModemDFSI(false), m_network(network), + m_ignorePDUCRC(false), m_inhibitUnauth(false), m_legacyGroupGrnt(true), m_legacyGroupReg(false), @@ -277,6 +278,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw generateLLA_AM1_Parameters(); } + m_ignorePDUCRC = p25Protocol["ignoreDataCRC"].as(false); + m_inhibitUnauth = p25Protocol["inhibitUnauthorized"].as(false); m_legacyGroupGrnt = p25Protocol["legacyGroupGrnt"].as(true); m_legacyGroupReg = p25Protocol["legacyGroupReg"].as(false); @@ -545,6 +548,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" SNDCP Support: %s", m_sndcpSupport ? "yes" : "no"); + LogInfo(" Ignore Data PDU CRC Error: %s", m_ignorePDUCRC ? "yes" : "no"); LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); LogInfo(" No Status ACK: %s", m_control->m_noStatusAck ? "yes" : "no"); LogInfo(" No Message ACK: %s", m_control->m_noMessageAck ? "yes" : "no"); diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 4daf06d15..8ebc30385 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -294,6 +294,8 @@ namespace p25 bool m_isModemDFSI; network::Network* m_network; + bool m_ignorePDUCRC; + bool m_inhibitUnauth; bool m_legacyGroupGrnt; bool m_legacyGroupReg; diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index 4e70e8b34..d0d47c7a8 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -290,7 +290,11 @@ bool Data::process(uint8_t* data, uint32_t len) bool crcRet = edac::CRC::checkCRC32(m_rfPduUserData, m_rfPduUserDataLength); if (!crcRet) { LogWarning(LOG_RF, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", m_rfDataHeader.getBlocksToFollow(), m_rfPduUserDataLength); - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); + if (!m_p25->m_ignorePDUCRC) { + uint8_t sap = (m_rfExtendedAddress) ? m_rfDataHeader.getEXSAP() : m_rfDataHeader.getSAP(); + if (sap != PDUSAP::TRUNK_CTRL) // ignore CRC errors on TSBK data + writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); + } } } From e26a024a3de782cc7de98839ecc8943dc3dfc288 Mon Sep 17 00:00:00 2001 From: "Lorenzo L. Romero" Date: Sat, 23 Aug 2025 10:34:48 -0400 Subject: [PATCH 127/133] Add support in dvmbridge for a serial PTT activation switch. RTS is asserted on the serial port defined in bridge-config.yml for the duration of audio received, then is removed. (#102) --- configs/bridge-config.example.yml | 6 + src/bridge/HostBridge.cpp | 84 ++++++++++ src/bridge/HostBridge.h | 21 +++ src/bridge/RtsPttController.cpp | 264 ++++++++++++++++++++++++++++++ src/bridge/RtsPttController.h | 91 ++++++++++ src/host/modem/port/ISerialPort.h | 11 ++ src/host/modem/port/UARTPort.cpp | 60 +++++++ src/host/modem/port/UARTPort.h | 11 ++ 8 files changed, 548 insertions(+) create mode 100644 src/bridge/RtsPttController.cpp create mode 100644 src/bridge/RtsPttController.h diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 24cb43c5d..436b5e1bf 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -184,3 +184,9 @@ system: trace: false # Flag indicating whether or not debug logging is enabled. debug: false + + # RTS PTT Configuration + # Flag indicating whether RTS PTT control is enabled. + rtsPttEnable: false + # Serial port device for RTS PTT control (e.g., /dev/ttyUSB0). + rtsPttPort: "/dev/ttyUSB0" diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index a2ac9a939..75b04c3fc 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -113,6 +113,13 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } + + // Assert RTS PTT when audio is being sent to output + bridge->assertRtsPtt(); + } + else { + // Deassert RTS PTT when no audio is being sent to output + bridge->deassertRtsPtt(); } } @@ -243,6 +250,10 @@ HostBridge::HostBridge(const std::string& confFile) : m_running(false), m_trace(false), m_debug(false), + m_rtsPttEnable(false), + m_rtsPttPort(), + m_rtsPttController(nullptr), + m_rtsPttActive(false), m_rtpSeqNo(0U), m_rtpTimestamp(INVALID_TS), m_usrpSeqNo(0U) @@ -283,6 +294,12 @@ HostBridge::HostBridge(const std::string& confFile) : HostBridge::~HostBridge() { + if (m_rtsPttController != nullptr) { + m_rtsPttController->close(); + delete m_rtsPttController; + m_rtsPttController = nullptr; + } + delete[] m_ambeBuffer; delete[] m_netLDU1; delete[] m_netLDU2; @@ -390,6 +407,11 @@ int HostBridge::run() if (!ret) return EXIT_FAILURE; + // initialize RTS PTT control + ret = initializeRtsPtt(); + if (!ret) + return EXIT_FAILURE; + ma_result result; if (m_localAudio) { // initialize audio devices @@ -927,6 +949,10 @@ bool HostBridge::readParams() m_trace = systemConf["trace"].as(false); m_debug = systemConf["debug"].as(false); + // RTS PTT Configuration + m_rtsPttEnable = systemConf["rtsPttEnable"].as(false); + m_rtsPttPort = systemConf["rtsPttPort"].as("/dev/ttyUSB0"); + std::string txModeStr = "DMR"; if (m_txMode == TX_MODE_P25) txModeStr = "P25"; @@ -952,6 +978,10 @@ bool HostBridge::readParams() LogInfo(" Grant Demands: %s", m_grantDemand ? "yes" : "no"); LogInfo(" Local Audio: %s", m_localAudio ? "yes" : "no"); LogInfo(" UDP Audio: %s", m_udpAudio ? "yes" : "no"); + LogInfo(" RTS PTT Enable: %s", m_rtsPttEnable ? "yes" : "no"); + if (m_rtsPttEnable) { + LogInfo(" RTS PTT Port: %s", m_rtsPttPort.c_str()); + } if (m_debug) { LogInfo(" Debug: yes"); @@ -1568,6 +1598,8 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst if (m_localAudio) { m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); + // Assert RTS PTT when audio is being sent to output + assertRtsPtt(); } if (m_udpAudio) { @@ -2223,6 +2255,8 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI if (m_localAudio) { m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); + // Assert RTS PTT when audio is being sent to output + assertRtsPtt(); } if (m_udpAudio) { @@ -3690,3 +3724,53 @@ void* HostBridge::threadCallWatchdog(void* arg) return nullptr; } + +/* Helper to initialize RTS PTT control. */ + +bool HostBridge::initializeRtsPtt() +{ + if (!m_rtsPttEnable) + return true; + + if (m_rtsPttPort.empty()) { + ::LogError(LOG_HOST, "RTS PTT port is not specified"); + return false; + } + + m_rtsPttController = new RtsPttController(m_rtsPttPort); + if (!m_rtsPttController->open()) { + ::LogError(LOG_HOST, "Failed to open RTS PTT port %s", m_rtsPttPort.c_str()); + delete m_rtsPttController; + m_rtsPttController = nullptr; + return false; + } + + ::LogInfo(LOG_HOST, "RTS PTT Controller initialized on %s", m_rtsPttPort.c_str()); + return true; +} + +/* Helper to assert RTS PTT (start transmission). */ + +void HostBridge::assertRtsPtt() +{ + if (!m_rtsPttEnable || m_rtsPttController == nullptr || m_rtsPttActive) + return; + + if (m_rtsPttController->setPTT()) { + m_rtsPttActive = true; + ::LogDebug(LOG_HOST, "RTS PTT asserted"); + } +} + +/* Helper to deassert RTS PTT (stop transmission). */ + +void HostBridge::deassertRtsPtt() +{ + if (!m_rtsPttEnable || m_rtsPttController == nullptr || !m_rtsPttActive) + return; + + if (m_rtsPttController->clearPTT()) { + m_rtsPttActive = false; + ::LogDebug(LOG_HOST, "RTS PTT deasserted"); + } +} diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 72e3c308e..dbd6485fc 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -32,6 +32,7 @@ #include "audio/miniaudio.h" #include "mdc/mdc_decode.h" #include "network/PeerNetwork.h" +#include "RtsPttController.h" #include #include @@ -258,6 +259,12 @@ class HOST_SW_API HostBridge { bool m_trace; bool m_debug; + // RTS PTT Control + bool m_rtsPttEnable; + std::string m_rtsPttPort; + RtsPttController* m_rtsPttController; + bool m_rtsPttActive; + uint16_t m_rtpSeqNo; uint32_t m_rtpTimestamp; @@ -511,6 +518,20 @@ class HOST_SW_API HostBridge { */ void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); + /** + * @brief Helper to initialize RTS PTT control. + * @returns bool True, if RTS PTT was initialized successfully, otherwise false. + */ + bool initializeRtsPtt(); + /** + * @brief Helper to assert RTS PTT (start transmission). + */ + void assertRtsPtt(); + /** + * @brief Helper to deassert RTS PTT (stop transmission). + */ + void deassertRtsPtt(); + /** * @brief Entry point to audio processing thread. * @param arg Instance of the thread_t structure. diff --git a/src/bridge/RtsPttController.cpp b/src/bridge/RtsPttController.cpp new file mode 100644 index 000000000..e82be9c99 --- /dev/null +++ b/src/bridge/RtsPttController.cpp @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + * + */ +#include "Defines.h" +#include "RtsPttController.h" + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the RtsPttController class. */ + +RtsPttController::RtsPttController(const std::string& port) : + m_port(port), + m_isOpen(false), +#if defined(_WIN32) + m_fd(INVALID_HANDLE_VALUE) +#else + m_fd(-1) +#endif // defined(_WIN32) +{ + assert(!port.empty()); +} + +/* Finalizes a instance of the RtsPttController class. */ + +RtsPttController::~RtsPttController() +{ + close(); +} + +/* Opens the serial port for RTS control. */ + +bool RtsPttController::open() +{ + if (m_isOpen) + return true; + +#if defined(_WIN32) + assert(m_fd == INVALID_HANDLE_VALUE); + + std::string deviceName = m_port; + if (deviceName.find("\\\\.\\") == std::string::npos) { + deviceName = "\\\\.\\" + m_port; + } + + m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (m_fd == INVALID_HANDLE_VALUE) { + ::LogError(LOG_HOST, "Cannot open RTS PTT device - %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } + + DCB dcb; + if (::GetCommState(m_fd, &dcb) == 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + + dcb.BaudRate = 9600; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + dcb.StopBits = ONESTOPBIT; + dcb.fInX = FALSE; + dcb.fOutX = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDsrSensitivity = FALSE; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + + if (::SetCommState(m_fd, &dcb) == 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + + // Clear RTS initially + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + +#else + assert(m_fd == -1); + + m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + ::LogError(LOG_HOST, "Cannot open RTS PTT device - %s", m_port.c_str()); + return false; + } + + if (::isatty(m_fd) == 0) { + ::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str()); + ::close(m_fd); + m_fd = -1; + return false; + } + + if (!setTermios()) { + ::close(m_fd); + m_fd = -1; + return false; + } +#endif // defined(_WIN32) + + ::LogInfo(LOG_HOST, "RTS PTT Controller opened on %s", m_port.c_str()); + m_isOpen = true; + return true; +} + +/* Closes the serial port. */ + +void RtsPttController::close() +{ + if (!m_isOpen) + return; + + // Clear RTS before closing + clearPTT(); + +#if defined(_WIN32) + if (m_fd != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + } +#else + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +#endif // defined(_WIN32) + + m_isOpen = false; + ::LogInfo(LOG_HOST, "RTS PTT Controller closed"); +} + +/* Sets RTS signal high (asserts RTS) to trigger PTT. */ + +bool RtsPttController::setPTT() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, SETRTS) == 0) { + ::LogError(LOG_HOST, "Cannot set RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set RTS PTT for %s", m_port.c_str()); + return false; + } +#endif // defined(_WIN32) + + ::LogDebug(LOG_HOST, "RTS PTT asserted on %s", m_port.c_str()); + return true; +} + +/* Sets RTS signal low (clears RTS) to release PTT. */ + +bool RtsPttController::clearPTT() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS PTT for %s", m_port.c_str()); + return false; + } +#endif // defined(_WIN32) + + ::LogDebug(LOG_HOST, "RTS PTT cleared on %s", m_port.c_str()); + return true; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Sets the termios settings on the serial port. */ + +bool RtsPttController::setTermios() +{ +#if !defined(_WIN32) + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str()); + return false; + } + + termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); + termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); + termios.c_iflag &= ~(IXON | IXOFF | IXANY); + termios.c_oflag &= ~(OPOST); + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= (CS8 | CLOCAL | CREAD); + termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); + termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; + + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str()); + return false; + } + + // Clear RTS initially + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s", m_port.c_str()); + return false; + } +#endif // !defined(_WIN32) + + return true; +} diff --git a/src/bridge/RtsPttController.h b/src/bridge/RtsPttController.h new file mode 100644 index 000000000..5cc8860c4 --- /dev/null +++ b/src/bridge/RtsPttController.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + * + */ +/** + * @file RtsPttController.h + * @ingroup bridge + * @file RtsPttController.cpp + * @ingroup bridge + */ +#if !defined(__RTS_PTT_CONTROLLER_H__) +#define __RTS_PTT_CONTROLLER_H__ + +#include "Defines.h" +#include "common/Log.h" + +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#include +#include +#endif // defined(_WIN32) + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements RTS PTT control for the bridge. + * @ingroup bridge + */ +class HOST_SW_API RtsPttController { +public: + /** + * @brief Initializes a new instance of the RtsPttController class. + * @param port Serial port device (e.g., /dev/ttyUSB0). + */ + RtsPttController(const std::string& port); + /** + * @brief Finalizes a instance of the RtsPttController class. + */ + ~RtsPttController(); + + /** + * @brief Opens the serial port for RTS control. + * @returns bool True, if port was opened successfully, otherwise false. + */ + bool open(); + /** + * @brief Closes the serial port. + */ + void close(); + + /** + * @brief Sets RTS signal high (asserts RTS) to trigger PTT. + * @returns bool True, if RTS was set successfully, otherwise false. + */ + bool setPTT(); + /** + * @brief Sets RTS signal low (clears RTS) to release PTT. + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + bool clearPTT(); + +private: + std::string m_port; + bool m_isOpen; +#if defined(_WIN32) + HANDLE m_fd; +#else + int m_fd; +#endif // defined(_WIN32) + + /** + * @brief Sets the termios settings on the serial port. + * @returns bool True, if settings are set, otherwise false. + */ + bool setTermios(); +}; + +#endif // __RTS_PTT_CONTROLLER_H__ diff --git a/src/host/modem/port/ISerialPort.h b/src/host/modem/port/ISerialPort.h index 3407f827c..256b94523 100644 --- a/src/host/modem/port/ISerialPort.h +++ b/src/host/modem/port/ISerialPort.h @@ -62,6 +62,17 @@ namespace modem * @brief Closes the connection to the port. */ virtual void close() = 0; + + /** + * @brief Sets RTS signal high (asserts RTS). + * @returns bool True, if RTS was set successfully, otherwise false. + */ + virtual bool setRTS() = 0; + /** + * @brief Sets RTS signal low (clears RTS). + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + virtual bool clearRTS() = 0; }; } // namespace port } // namespace modem diff --git a/src/host/modem/port/UARTPort.cpp b/src/host/modem/port/UARTPort.cpp index 4657a1bfa..83b736682 100644 --- a/src/host/modem/port/UARTPort.cpp +++ b/src/host/modem/port/UARTPort.cpp @@ -544,3 +544,63 @@ bool UARTPort::setTermios() m_isOpen = true; return true; } + +/* Sets RTS signal high (asserts RTS). */ + +bool UARTPort::setRTS() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, SETRTS) == 0) { + ::LogError(LOG_HOST, "Cannot set RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set RTS for %s", m_device.c_str()); + return false; + } +#endif // defined(_WIN32) + + return true; +} + +/* Sets RTS signal low (clears RTS). */ + +bool UARTPort::clearRTS() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s", m_device.c_str()); + return false; + } +#endif // defined(_WIN32) + + return true; +} diff --git a/src/host/modem/port/UARTPort.h b/src/host/modem/port/UARTPort.h index b5a4a1a7d..c8411b97b 100644 --- a/src/host/modem/port/UARTPort.h +++ b/src/host/modem/port/UARTPort.h @@ -108,6 +108,17 @@ namespace modem */ void close() override; + /** + * @brief Sets RTS signal high (asserts RTS). + * @returns bool True, if RTS was set successfully, otherwise false. + */ + bool setRTS() override; + /** + * @brief Sets RTS signal low (clears RTS). + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + bool clearRTS() override; + #if defined(__APPLE__) /** * @brief Helper on Apple to set serial port to non-blocking. From cbccf3920b88f50439396255be82ddd7bae13331 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 28 Aug 2025 12:03:34 -0400 Subject: [PATCH 128/133] P25 PDU packet handling refactor (for future use); --- src/fne/HostFNE.cpp | 77 ++------ src/fne/HostFNE.h | 6 - .../callhandler/packetdata/P25PacketData.cpp | 168 +++++++++--------- .../callhandler/packetdata/P25PacketData.h | 26 ++- 4 files changed, 113 insertions(+), 164 deletions(-) diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 4d32f8967..247690fd2 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -217,8 +217,6 @@ int HostFNE::run() #if !defined(_WIN32) if (!Thread::runAsThread(this, threadVirtualNetworking)) return EXIT_FAILURE; - if (!Thread::runAsThread(this, threadVirtualNetworkingClock)) - return EXIT_FAILURE; #endif // !defined(_WIN32) /* ** Main execution loop @@ -256,6 +254,20 @@ int HostFNE::run() } } +#if !defined(_WIN32) + if (m_vtunEnabled) { + switch (m_packetDataMode) { + case PacketDataMode::DMR: + // TODO: not supported yet + break; + + case PacketDataMode::PROJECT25: + m_network->p25TrafficHandler()->packetData()->clock(ms); + break; + } + } +#endif // !defined(_WIN32) + if (ms < 2U) Thread::sleep(1U); } @@ -989,67 +1001,6 @@ void* HostFNE::threadVirtualNetworking(void* arg) return nullptr; } - -/* Entry point to virtual networking clocking thread. */ - -void* HostFNE::threadVirtualNetworkingClock(void* arg) -{ - thread_t* th = (thread_t*)arg; - if (th != nullptr) { - ::pthread_detach(th->thread); - - std::string threadName("fne:vt-clock"); - HostFNE* fne = static_cast(th->obj); - if (fne == nullptr) { - g_killed = true; - LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); - } - - if (g_killed) { - delete th; - return nullptr; - } - - if (!fne->m_vtunEnabled) { - delete th; - return nullptr; - } - - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); -#ifdef _GNU_SOURCE - ::pthread_setname_np(th->thread, threadName.c_str()); -#endif // _GNU_SOURCE - - if (fne->m_tun != nullptr) { - StopWatch stopWatch; - stopWatch.start(); - - while (!g_killed) { - uint32_t ms = stopWatch.elapsed(); - stopWatch.start(); - - // clock traffic handler - switch (fne->m_packetDataMode) { - case PacketDataMode::DMR: - // TODO: not supported yet - break; - - case PacketDataMode::PROJECT25: - fne->m_network->p25TrafficHandler()->packetData()->clock(ms); - break; - } - - if (ms < THREAD_CYCLE_THRESHOLD) - Thread::sleep(THREAD_CYCLE_THRESHOLD); - } - } - - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); - delete th; - } - - return nullptr; -} #endif // !defined(_WIN32) /* Processes DMR peer network traffic. */ diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index ef675d2c2..4c76c47e6 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -173,12 +173,6 @@ class HOST_SW_API HostFNE { * @returns void* (Ignore) */ static void* threadVirtualNetworking(void* arg); - /** - * @brief Entry point to virtual networking clocking thread. - * @param arg Instance of the thread_t structure. - * @returns void* (Ignore) - */ - static void* threadVirtualNetworkingClock(void* arg); #endif // !defined(_WIN32) /** * @brief Processes DMR peer network traffic. diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index e787e3766..a7607bbfc 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -41,12 +41,7 @@ using namespace p25::sndcp; // --------------------------------------------------------------------------- const uint8_t DATA_CALL_COLL_TIMEOUT = 60U; - -// --------------------------------------------------------------------------- -// Static Class Members -// --------------------------------------------------------------------------- - -std::timed_mutex P25PacketData::m_vtunMutex; +const uint8_t MAX_PKT_RETRY_CNT = 5U; // --------------------------------------------------------------------------- // Public Class Members @@ -57,7 +52,7 @@ std::timed_mutex P25PacketData::m_vtunMutex; P25PacketData::P25PacketData(FNENetwork* network, TagP25Data* tag, bool debug) : m_network(network), m_tag(tag), - m_dataFrames(), + m_queuedFrames(), m_status(), m_arpTable(), m_readyForNextPkt(), @@ -371,30 +366,63 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a Utils::dump(1U, "P25, P25PacketData::processPacketFrame() packet", data, pktLen); #endif - VTUNDataFrame* dataFrame = new VTUNDataFrame(); - dataFrame->buffer = new uint8_t[len]; - ::memcpy(dataFrame->buffer, data, len); - dataFrame->bufferLen = len; - dataFrame->pktLen = pktLen; - dataFrame->proto = proto; - uint32_t dstLlId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); - dataFrame->srcHWAddr = WUID_FNE; - dataFrame->srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); - dataFrame->tgtHWAddr = dstLlId; - dataFrame->tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); + dstLlId = 1234567U; - dataFrame->timestamp = now; + uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); + uint32_t tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); if (dstLlId == 0U) { LogMessage(LOG_NET, "P25, no ARP entry for, dstIp = %s", dstIp); write_PDU_ARP(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); } - m_vtunMutex.try_lock_for(std::chrono::milliseconds(60)); - m_dataFrames.push_back(dataFrame); - m_vtunMutex.unlock(); + std::string srcIpStr = __IP_FROM_UINT(srcProtoAddr); + std::string tgtIpStr = __IP_FROM_UINT(tgtProtoAddr); + + LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", + srcIpStr.c_str(), WUID_FNE, tgtIpStr.c_str(), dstLlId, pktLen, proto); + + // assemble a P25 PDU frame header for transport... + data::DataHeader* pktHeader = new data::DataHeader(); + pktHeader->setFormat(PDUFormatType::CONFIRMED); + pktHeader->setMFId(MFG_STANDARD); + pktHeader->setAckNeeded(true); + pktHeader->setOutbound(true); + pktHeader->setSAP(PDUSAP::EXT_ADDR); + pktHeader->setLLId(dstLlId); + pktHeader->setBlocksToFollow(1U); + + pktHeader->setEXSAP(PDUSAP::PACKET_DATA); + pktHeader->setSrcLLId(WUID_FNE); + + pktHeader->calculateLength(pktLen); + uint32_t pduLength = pktHeader->getPDULength(); + if (pduLength < pktLen) { + LogWarning(LOG_NET, "P25, VTUN, data truncated!"); + pktLen = pduLength; // don't overflow the buffer + } + + DECLARE_UINT8_ARRAY(pduUserData, pduLength); + ::memcpy(pduUserData + 4U, data, pktLen); +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, P25PacketData::processPacketFrame(), pduUserData", pduUserData, pduLength); +#endif + + // queue frame for dispatch + QueuedDataFrame* qf = new QueuedDataFrame(); + qf->timestamp = now; + qf->header = pktHeader; + qf->extendedAddress = true; + qf->llId = dstLlId; + qf->tgtProtoAddr = tgtProtoAddr; + + qf->userData = new uint8_t[pduLength]; + ::memcpy(qf->userData, pduUserData, pduLength); + qf->userDataLen = pduLength; + + m_queuedFrames.push_back(qf); #endif // !defined(_WIN32) } @@ -404,93 +432,73 @@ void P25PacketData::clock(uint32_t ms) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - if (m_dataFrames.size() == 0U) { + if (m_queuedFrames.size() == 0U) { return; } // transmit queued data frames bool processed = false; - bool locked = m_vtunMutex.try_lock_for(std::chrono::milliseconds(60)); - - auto& dataFrame = m_dataFrames[0]; - - if (locked) - m_vtunMutex.unlock(); - - if (dataFrame != nullptr) { - if (now > dataFrame->timestamp + 500U) { + + auto& frame = m_queuedFrames[0]; + if (frame != nullptr) { + if (now > frame->timestamp) { processed = true; + if (frame->retryCnt >= MAX_PKT_RETRY_CNT) { + LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); + goto pkt_clock_abort; + } + + std::string tgtIpStr = __IP_FROM_UINT(frame->tgtProtoAddr); + LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, dstIp = %s (%u), userDataLen = %u, retries = %u", + tgtIpStr.c_str(), frame->llId, frame->userDataLen, frame->retryCnt); + // do we have a valid target address? - if (dataFrame->tgtHWAddr == 0U) { - uint32_t dstLlId = getLLIdAddress(dataFrame->tgtProtoAddr); - if (dstLlId == 0U) { + if (frame->llId == 0U) { + frame->llId = getLLIdAddress(frame->tgtProtoAddr); + if (frame->llId == 0U) { + LogWarning(LOG_NET, "P25, no ARP entry for, dstIp = %s", tgtIpStr.c_str()); + write_PDU_ARP(frame->tgtProtoAddr); + processed = false; + frame->timestamp = now + 250U; // retry in 250ms + frame->retryCnt++; goto pkt_clock_abort; } - - dataFrame->tgtHWAddr = dstLlId; + else { + frame->header->setLLId(frame->llId); + } } // is the SU ready for the next packet? - auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == dataFrame->tgtHWAddr; }); + /*auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == frame->llId; }); if (ready != m_readyForNextPkt.end()) { if (!ready->second) { + LogWarning(LOG_NET, "P25, subscriber not ready, dstIp = %s", tgtIpStr.c_str()); processed = false; + frame->timestamp = now + 100U; // retry in 100ms + frame->retryCnt++; goto pkt_clock_abort; } - } else { - processed = false; - goto pkt_clock_abort; } - m_readyForNextPkt[dataFrame->tgtHWAddr] = false; - - std::string srcIp = __IP_FROM_UINT(dataFrame->srcProtoAddr); - std::string tgtIp = __IP_FROM_UINT(dataFrame->tgtProtoAddr); - - LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", - srcIp.c_str(), dataFrame->srcHWAddr, tgtIp.c_str(), dataFrame->tgtHWAddr, dataFrame->pktLen, dataFrame->proto); - - // assemble a P25 PDU frame header for transport... - data::DataHeader rspHeader = data::DataHeader(); - rspHeader.setFormat(PDUFormatType::CONFIRMED); - rspHeader.setMFId(MFG_STANDARD); - rspHeader.setAckNeeded(true); - rspHeader.setOutbound(true); - rspHeader.setSAP(PDUSAP::EXT_ADDR); - rspHeader.setLLId(dataFrame->tgtHWAddr); - rspHeader.setBlocksToFollow(1U); - - rspHeader.setEXSAP(PDUSAP::PACKET_DATA); - rspHeader.setSrcLLId(WUID_FNE); - - rspHeader.calculateLength(dataFrame->pktLen); - uint32_t pduLength = rspHeader.getPDULength(); - - DECLARE_UINT8_ARRAY(pduUserData, pduLength); - ::memcpy(pduUserData + 4U, dataFrame->buffer, dataFrame->pktLen); -#if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25, P25PacketData::clock(), pduUserData", pduUserData, pduLength); -#endif - dispatchUserFrameToFNE(rspHeader, true, pduUserData); + m_readyForNextPkt[frame->llId] = false;*/ + dispatchUserFrameToFNE(*frame->header, frame->extendedAddress, frame->userData); } } pkt_clock_abort: - locked = m_vtunMutex.try_lock_for(std::chrono::milliseconds(60)); - m_dataFrames.pop_front(); + m_queuedFrames.pop_front(); if (processed) { - if (dataFrame->buffer != nullptr) - delete[] dataFrame->buffer; - delete dataFrame; + if (frame->userData != nullptr) + delete[] frame->userData; + if (frame->header != nullptr) + delete frame->header; + delete frame; } else { // requeue packet - m_dataFrames.push_back(dataFrame); + m_queuedFrames.push_back(frame); } - - if (locked) - m_vtunMutex.unlock(); } // --------------------------------------------------------------------------- diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index 04d496e36..bba07461b 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -85,27 +85,25 @@ namespace network private: FNENetwork* m_network; - TagP25Data *m_tag; + TagP25Data* m_tag; /** * @brief Represents a queued data frame from the VTUN. */ - class VTUNDataFrame { + class QueuedDataFrame { public: - uint32_t srcHWAddr; //! Source Hardware Address - uint32_t srcProtoAddr; //! Source Protocol Address - uint32_t tgtHWAddr; //! Target Hardware Address - uint32_t tgtProtoAddr; //! Target Protocol Address + p25::data::DataHeader* header; //! Instance of a PDU data header. + bool extendedAddress; //! Flag indicating whether or not to extended addressing is in use. + uint32_t llId; //! Logical Link ID + uint32_t tgtProtoAddr; //! Target Protocol Address - uint8_t* buffer; //! Raw data buffer - uint32_t bufferLen; //! Length of raw data buffer - - uint16_t pktLen; //! Packet Length - uint8_t proto; //! Packet Protocol + uint8_t* userData; //! Raw data buffer + uint32_t userDataLen; //! Length of raw data buffer - uint64_t timestamp; //! Timestamp in milliseconds + uint64_t timestamp; //! Timestamp in milliseconds + uint8_t retryCnt; //! Packet Retry Counter }; - concurrent::deque m_dataFrames; + concurrent::deque m_queuedFrames; /** * @brief Represents the receive status of a call. @@ -179,8 +177,6 @@ namespace network bool m_debug; - static std::timed_mutex m_vtunMutex; - /** * @brief Helper to dispatch PDU user data. * @param peerId Peer ID. From 1f899a1f18ff0d12fd8b77b69b0f025039eae950 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 28 Aug 2025 20:43:03 -0400 Subject: [PATCH 129/133] remove test code; --- src/fne/network/callhandler/packetdata/P25PacketData.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index a7607bbfc..f98a98ee8 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -368,8 +368,6 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a uint32_t dstLlId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); - dstLlId = 1234567U; - uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); uint32_t tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); @@ -471,7 +469,7 @@ void P25PacketData::clock(uint32_t ms) } // is the SU ready for the next packet? - /*auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == frame->llId; }); + auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == frame->llId; }); if (ready != m_readyForNextPkt.end()) { if (!ready->second) { LogWarning(LOG_NET, "P25, subscriber not ready, dstIp = %s", tgtIpStr.c_str()); @@ -482,7 +480,7 @@ void P25PacketData::clock(uint32_t ms) } } - m_readyForNextPkt[frame->llId] = false;*/ + m_readyForNextPkt[frame->llId] = false; dispatchUserFrameToFNE(*frame->header, frame->extendedAddress, frame->userData); } } From 119b4ffce9b5b9682dd8998ee529dc225821bcf9 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 29 Aug 2025 09:22:41 -0400 Subject: [PATCH 130/133] fix issue where PDU RSPs weren't being sent to the FNE; correct timing around ARP and packet retry when subscriber is not ready; --- .../callhandler/packetdata/P25PacketData.cpp | 13 ++++--------- src/host/p25/packet/Data.cpp | 3 +++ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index f98a98ee8..c5686395f 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -371,11 +371,6 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); uint32_t tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); - if (dstLlId == 0U) { - LogMessage(LOG_NET, "P25, no ARP entry for, dstIp = %s", dstIp); - write_PDU_ARP(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); - } - std::string srcIpStr = __IP_FROM_UINT(srcProtoAddr); std::string tgtIpStr = __IP_FROM_UINT(tgtProtoAddr); @@ -459,7 +454,7 @@ void P25PacketData::clock(uint32_t ms) write_PDU_ARP(frame->tgtProtoAddr); processed = false; - frame->timestamp = now + 250U; // retry in 250ms + frame->timestamp = now + 1000U; // retry in 1s frame->retryCnt++; goto pkt_clock_abort; } @@ -474,7 +469,7 @@ void P25PacketData::clock(uint32_t ms) if (!ready->second) { LogWarning(LOG_NET, "P25, subscriber not ready, dstIp = %s", tgtIpStr.c_str()); processed = false; - frame->timestamp = now + 100U; // retry in 100ms + frame->timestamp = now + 500U; // retry in 500ms frame->retryCnt++; goto pkt_clock_abort; } @@ -541,8 +536,8 @@ void P25PacketData::dispatch(uint32_t peerId) m_readyForNextPkt[status->header.getSrcLLId()] = true; } - write_PDU_Ack_Response(status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), - status->header.getLLId(), status->header.getSrcLLId()); + /*write_PDU_Ack_Response(status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), + status->header.getLLId(), status->header.getSrcLLId());*/ return; } diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index d0d47c7a8..80992a98f 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -323,6 +323,9 @@ bool Data::process(uint8_t* data, uint32_t len) m_retryPDUBitLength = 0U; m_retryCount = 0U; } + + // rewrite the response to the network + writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, true); } else { if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { switch (m_rfDataHeader.getResponseType()) { From 28984387193ad5b4b22d9d2c490b0a05f53785dd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 29 Aug 2025 09:26:16 -0400 Subject: [PATCH 131/133] add some more verbose logging; --- src/host/p25/packet/Data.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index 80992a98f..2f4b7abd5 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -314,8 +314,8 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u", - m_rfDataHeader.getLLId()); + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { delete m_retryPDUData; m_retryPDUData = nullptr; @@ -334,17 +334,17 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfDataHeader.getLLId()); break; case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u", - m_rfDataHeader.getLLId()); + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); break; case PDUAckType::NACK_SEQ: case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u", - m_rfDataHeader.getLLId()); + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); break; case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u", - m_rfDataHeader.getLLId()); + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); break; default: From a7edc2159ec5a0b619fbba3aba1d761ea298b797 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 29 Aug 2025 13:40:35 -0400 Subject: [PATCH 132/133] experimental changes to PDU data handling; --- src/common/p25/data/DataHeader.h | 2 + .../callhandler/packetdata/P25PacketData.cpp | 156 +++++++++++++---- .../callhandler/packetdata/P25PacketData.h | 11 +- src/host/p25/Control.cpp | 10 +- src/host/p25/packet/Data.cpp | 164 +++++++++--------- src/host/p25/packet/Data.h | 4 +- 6 files changed, 230 insertions(+), 117 deletions(-) diff --git a/src/common/p25/data/DataHeader.h b/src/common/p25/data/DataHeader.h index 97164d6b2..0a4409106 100644 --- a/src/common/p25/data/DataHeader.h +++ b/src/common/p25/data/DataHeader.h @@ -160,6 +160,8 @@ namespace p25 DECLARE_PROPERTY(uint8_t, padLength, PadLength); /** * @brief Flag indicating whether or not this data packet is a full message. + * @note This is used on extended addressing response packets to indicate whether or not + * the response is for a extended addressing request. */ DECLARE_PROPERTY(bool, F, FullMessage); /** diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index c5686395f..a626b996e 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -43,6 +43,10 @@ using namespace p25::sndcp; const uint8_t DATA_CALL_COLL_TIMEOUT = 60U; const uint8_t MAX_PKT_RETRY_CNT = 5U; +const uint32_t INTERPACKET_DELAY = 100U; // milliseconds +const uint32_t ARP_RETRY_MS = 5000U; // milliseconds +const uint32_t SUBSCRIBER_READY_RETRY_MS = 1000U; // milliseconds + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -170,11 +174,6 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee return true; } - if (status->header.getSAP() != PDUSAP::EXT_ADDR && - status->header.getFormat() != PDUFormatType::UNCONFIRMED) { - m_suSendSeq[status->llId] = 0U; - } - LogMessage(LOG_NET, "P25, Data Call Start, peer = %u, llId = %u, streamId = %u, external = %u", peerId, status->llId, streamId, external); return true; } @@ -242,7 +241,6 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee status->extendedAddress = true; status->llId = status->header.getSrcLLId(); - m_suSendSeq[status->llId] = 0U; offset += P25_PDU_FEC_LENGTH_BYTES; blocksToFollow--; @@ -405,7 +403,10 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a // queue frame for dispatch QueuedDataFrame* qf = new QueuedDataFrame(); - qf->timestamp = now; + qf->retryCnt = 0U; + qf->extendRetry = false; + qf->timestamp = now + INTERPACKET_DELAY; + qf->header = pktHeader; qf->extendedAddress = true; qf->llId = dstLlId; @@ -437,11 +438,17 @@ void P25PacketData::clock(uint32_t ms) if (now > frame->timestamp) { processed = true; - if (frame->retryCnt >= MAX_PKT_RETRY_CNT) { + if (frame->retryCnt >= MAX_PKT_RETRY_CNT && !frame->extendRetry) { LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); goto pkt_clock_abort; } + if (frame->retryCnt >= (MAX_PKT_RETRY_CNT * 2U) && frame->extendRetry) { + LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); + m_readyForNextPkt[frame->llId] = true; // force ready for next packet + goto pkt_clock_abort; + } + std::string tgtIpStr = __IP_FROM_UINT(frame->tgtProtoAddr); LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, dstIp = %s (%u), userDataLen = %u, retries = %u", tgtIpStr.c_str(), frame->llId, frame->userDataLen, frame->retryCnt); @@ -454,7 +461,7 @@ void P25PacketData::clock(uint32_t ms) write_PDU_ARP(frame->tgtProtoAddr); processed = false; - frame->timestamp = now + 1000U; // retry in 1s + frame->timestamp = now + ARP_RETRY_MS; frame->retryCnt++; goto pkt_clock_abort; } @@ -469,7 +476,8 @@ void P25PacketData::clock(uint32_t ms) if (!ready->second) { LogWarning(LOG_NET, "P25, subscriber not ready, dstIp = %s", tgtIpStr.c_str()); processed = false; - frame->timestamp = now + 500U; // retry in 500ms + frame->timestamp = now + SUBSCRIBER_READY_RETRY_MS; + frame->extendRetry = true; frame->retryCnt++; goto pkt_clock_abort; } @@ -512,35 +520,68 @@ void P25PacketData::dispatch(uint32_t peerId) bool crcValid = false; if (status->header.getBlocksToFollow() > 0U) { if (status->pduUserDataLength < 4U) { - LogError(LOG_NET, P25_PDU_STR ", illegal PDU packet length, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduUserDataLength); + LogError(LOG_NET, P25_PDU_STR ", illegal PDU packet length, peer = %u, llId = %u, blocks %u, len %u", + peerId, status->header.getLLId(), status->header.getBlocksToFollow(), status->pduUserDataLength); return; } crcValid = edac::CRC::checkCRC32(status->pduUserData, status->pduUserDataLength); if (!crcValid) { - LogError(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduUserDataLength); + LogError(LOG_NET, P25_PDU_STR ", failed CRC-32 check, peer = %u, llId = %u, blocks %u, len %u", + peerId, status->header.getLLId(), status->header.getBlocksToFollow(), status->pduUserDataLength); return; } } if (m_network->m_dumpPacketData && status->dataBlockCnt > 0U) { Utils::dump(1U, "P25, ISP PDU Packet", status->pduUserData, status->pduUserDataLength); - } + } if (status->header.getFormat() == PDUFormatType::RSP) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, peer = %u, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + peerId, status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), status->header.getLLId(), status->header.getSrcLLId()); + // bryanb: this is naive and possibly error prone + m_readyForNextPkt[status->header.getSrcLLId()] = true; + if (status->header.getResponseClass() == PDUAckClass::ACK && status->header.getResponseType() == PDUAckType::ACK) { - m_readyForNextPkt[status->header.getSrcLLId()] = true; + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, peer = %u, llId = %u, all blocks received OK, n = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + } else { + if (status->header.getResponseClass() == PDUAckClass::NACK) { + switch (status->header.getResponseType()) { + case PDUAckType::NACK_ILLEGAL: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, peer = %u, llId = %u", + peerId, status->header.getLLId()); + break; + case PDUAckType::NACK_PACKET_CRC: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, peer = %u, llId = %u, n = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + break; + case PDUAckType::NACK_SEQ: + case PDUAckType::NACK_OUT_OF_SEQ: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, peer = %u, llId = %u, seqNo = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + break; + case PDUAckType::NACK_UNDELIVERABLE: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, peer = %u, llId = %u, n = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + break; + + default: + break; + } + } } - /*write_PDU_Ack_Response(status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), - status->header.getLLId(), status->header.getSrcLLId());*/ return; } + if (status->header.getFormat() == PDUFormatType::UNCONFIRMED) { + m_readyForNextPkt[status->header.getSrcLLId()] = true; + } + uint8_t sap = (status->extendedAddress) ? status->header.getEXSAP() : status->header.getSAP(); // don't dispatch SNDCP control, conventional data registration or ARP @@ -675,15 +716,24 @@ void P25PacketData::dispatch(uint32_t peerId) // if the packet is unhandled and sent off to VTUN; ack the packet so the sender knows we received it if (!handled) { - write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), status->header.getSrcLLId(), status->header.getLLId()); + write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), status->header.getSrcLLId(), + true, status->header.getLLId()); } #endif // !defined(_WIN32) } break; + case PDUSAP::CONV_DATA_REG: + { + LogMessage(LOG_NET, P25_PDU_STR ", CONV_DATA_REG (Conventional Data Registration), peer = %u, blocksToFollow = %u", + peerId, status->header.getBlocksToFollow()); + + processConvDataReg(status); + } + break; case PDUSAP::SNDCP_CTRL_DATA: { - LogMessage(LOG_NET, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), blocksToFollow = %u", - status->header.getBlocksToFollow()); + LogMessage(LOG_NET, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), peer = %u, blocksToFollow = %u", + peerId, status->header.getBlocksToFollow()); processSNDCPControl(status); } @@ -691,8 +741,8 @@ void P25PacketData::dispatch(uint32_t peerId) case PDUSAP::UNENC_KMM: case PDUSAP::ENC_KMM: { - LogMessage(LOG_NET, P25_PDU_STR ", KMM (Key Management Message), blocksToFollow = %u", - status->header.getBlocksToFollow()); + LogMessage(LOG_NET, P25_PDU_STR ", KMM (Key Management Message), peer = %u, blocksToFollow = %u", + peerId, status->header.getBlocksToFollow()); processKMM(status); } @@ -767,16 +817,15 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo uint32_t srcId = (extendedAddress) ? dataHeader.getSrcLLId() : dataHeader.getLLId(); uint32_t dstId = dataHeader.getLLId(); - uint8_t sendSeqNo = m_suSendSeq[srcId]; - if (sendSeqNo == 0U) { + // update the sequence number + m_suSendSeq[srcId]++; + if (m_suSendSeq[srcId] >= 8U) + { + m_suSendSeq[srcId] = 0U; dataHeader.setSynchronize(true); } - dataHeader.setNs(sendSeqNo); - ++sendSeqNo; - if (sendSeqNo > 7U) - sendSeqNo = 0U; - m_suSendSeq[srcId] = sendSeqNo; + dataHeader.setNs(m_suSendSeq[srcId]); // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { @@ -799,6 +848,45 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo } } + +/* Helper used to process conventional data registration from PDU data. */ + +bool P25PacketData::processConvDataReg(RxStatus* status) +{ + uint8_t regType = (status->pduUserData[0U] >> 4) & 0x0F; + switch (regType) { + case PDURegType::CONNECT: + { + uint32_t llId = (status->pduUserData[1U] << 16) + (status->pduUserData[2U] << 8) + status->pduUserData[3U]; + uint32_t ipAddr = (status->pduUserData[8U] << 24) + (status->pduUserData[9U] << 16) + (status->pduUserData[10U] << 8) + status->pduUserData[11U]; + + if (ipAddr == 0U) { + LogWarning(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect) with zero IP address, llId = %u", llId); + return false; + } + + LogMessage(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + + m_arpTable[llId] = ipAddr; // update ARP table + } + break; + case PDURegType::DISCONNECT: + { + uint32_t llId = (status->pduUserData[1U] << 16) + (status->pduUserData[2U] << 8) + status->pduUserData[3U]; + + LogMessage(LOG_NET, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); + + m_arpTable.erase(llId); + } + break; + default: + LogError(LOG_RF, "P25 unhandled PDU registration type, regType = $%02X", regType); + break; + } + + return true; +} + /* Helper used to process SNDCP control data from PDU data. */ bool P25PacketData::processSNDCPControl(RxStatus* status) @@ -999,7 +1087,7 @@ void P25PacketData::write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorL /* Helper to write a PDU acknowledge response. */ -void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId) +void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, uint32_t srcLlId) { if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) return; @@ -1015,7 +1103,11 @@ void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, ui if (srcLlId > 0U) { rspHeader.setSrcLLId(srcLlId); } - rspHeader.setFullMessage(true); + + if (!extendedAddress) + rspHeader.setFullMessage(true); + else + rspHeader.setFullMessage(false); rspHeader.setBlocksToFollow(0U); diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index bba07461b..46eaf0e84 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -102,6 +102,7 @@ namespace network uint64_t timestamp; //! Timestamp in milliseconds uint8_t retryCnt; //! Packet Retry Counter + bool extendRetry; //! Flag indicating whether or not to extend the retry count for this packet. }; concurrent::deque m_queuedFrames; @@ -195,6 +196,12 @@ namespace network */ void dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData); + /** + * @brief Helper used to process conventional data registration from PDU data. + * @param status Instance of the RxStatus class. + * @returns bool True, if conventional data registration data was processed, otherwise false. + */ + bool processConvDataReg(RxStatus* status); /** * @brief Helper used to process SNDCP control data from PDU data. * @param status Instance of the RxStatus class. @@ -229,9 +236,11 @@ namespace network * @param ackType Acknowledgement Type. * @param ackStatus * @param llId Logical Link ID. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. * @param srcLlId Source Logical Link ID. */ - void write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U); + void write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, + uint32_t srcLlId = 0U); /** * @brief Helper to write user data as a P25 PDU packet. diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 29c3d4680..edbefed48 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1396,10 +1396,12 @@ void Control::processNetwork() return; } - // don't process network frames if the RF modem isn't in a listening state - if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { - m_network->resetP25(); - return; + if (m_netState != RS_NET_DATA) { + // don't process network frames if the RF modem isn't in a listening state + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { + m_network->resetP25(); + return; + } } bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index 2f4b7abd5..017c2c478 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -293,7 +293,8 @@ bool Data::process(uint8_t* data, uint32_t len) if (!m_p25->m_ignorePDUCRC) { uint8_t sap = (m_rfExtendedAddress) ? m_rfDataHeader.getEXSAP() : m_rfDataHeader.getSAP(); if (sap != PDUSAP::TRUNK_CTRL) // ignore CRC errors on TSBK data - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); + writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId(), + m_rfExtendedAddress, WUID_FNE); } } } @@ -308,77 +309,75 @@ bool Data::process(uint8_t* data, uint32_t len) // did we receive a response header? if (m_rfDataHeader.getFormat() == PDUFormatType::RSP) { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - m_rfDataHeader.getFormat(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), - m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); - - if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + m_rfDataHeader.getFormat(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), + m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); + + if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { + delete m_retryPDUData; + m_retryPDUData = nullptr; + + m_retryPDUBitLength = 0U; + m_retryCount = 0U; + } + } else { + if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { + switch (m_rfDataHeader.getResponseType()) { + case PDUAckType::NACK_ILLEGAL: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", + m_rfDataHeader.getLLId()); + break; + case PDUAckType::NACK_PACKET_CRC: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + break; + case PDUAckType::NACK_SEQ: + case PDUAckType::NACK_OUT_OF_SEQ: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + break; + case PDUAckType::NACK_UNDELIVERABLE: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + break; + + default: + break; + } + } else if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK_RETRY) { + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u", + m_rfDataHeader.getLLId()); + + // really this is supposed to check the bit field in the included response + // and only return those bits -- but we're responding with the entire previous packet... if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { - delete m_retryPDUData; - m_retryPDUData = nullptr; + if (m_retryCount < MAX_PDU_RETRY_CNT) { + m_p25->writeRF_Preamble(); + writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true); + m_retryCount++; + } + else { + delete m_retryPDUData; + m_retryPDUData = nullptr; - m_retryPDUBitLength = 0U; - m_retryCount = 0U; - } + m_retryPDUBitLength = 0U; + m_retryCount = 0U; + + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", + m_rfDataHeader.getLLId()); - // rewrite the response to the network - writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, true); - } else { - if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { - switch (m_rfDataHeader.getResponseType()) { - case PDUAckType::NACK_ILLEGAL: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", - m_rfDataHeader.getLLId()); - break; - case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - break; - case PDUAckType::NACK_SEQ: - case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - break; - case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - break; - - default: - break; - } - } else if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK_RETRY) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u", - m_rfDataHeader.getLLId()); - - // really this is supposed to check the bit field in the included response - // and only return those bits -- but we're responding with the entire previous packet... - if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { - if (m_retryCount < MAX_PDU_RETRY_CNT) { - m_p25->writeRF_Preamble(); - writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true); - m_retryCount++; - } - else { - delete m_retryPDUData; - m_retryPDUData = nullptr; - - m_retryPDUBitLength = 0U; - m_retryCount = 0U; - - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", - m_rfDataHeader.getLLId()); - - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); - } + writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); } } } } + // rewrite the response to the network + writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, true); + // only repeat the PDU locally if the packet isn't for the FNE if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { writeRF_PDU_Ack_Response(m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), @@ -423,6 +422,7 @@ bool Data::process(uint8_t* data, uint32_t len) } processSNDCPControl(m_rfPduUserData); + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); } break; case PDUSAP::CONV_DATA_REG: @@ -433,6 +433,7 @@ bool Data::process(uint8_t* data, uint32_t len) } processConvDataReg(m_rfPduUserData); + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); } break; case PDUSAP::UNENC_KMM: @@ -508,6 +509,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) ::memset(m_netPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); m_p25->m_netState = RS_NET_DATA; + m_inbound = false; uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); @@ -526,7 +528,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", + LogMessage(LOG_NET, P25_PDU_STR ", ISP, ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", m_netDataHeader.getAckNeeded(), m_netDataHeader.getOutbound(), m_netDataHeader.getFormat(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMessage(), m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadLength(), m_netDataHeader.getPacketLength(), m_netDataHeader.getSynchronize(), m_netDataHeader.getNs(), m_netDataHeader.getFSN(), m_netDataHeader.getHeaderOffset(), m_netDataHeader.getLLId()); @@ -570,8 +572,8 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId()); if (m_netDataHeader.getResponseClass() == PDUAckClass::ACK && m_netDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); } else { if (m_netDataHeader.getResponseClass() == PDUAckClass::NACK) { switch (m_netDataHeader.getResponseType()) { @@ -580,17 +582,17 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) m_netDataHeader.getLLId()); break; case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); break; case PDUAckType::NACK_SEQ: case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); break; case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); break; default: @@ -601,7 +603,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } writeRF_PDU_Ack_Response(m_netDataHeader.getResponseClass(), m_netDataHeader.getResponseType(), m_netDataHeader.getResponseStatus(), - m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId()); + m_netDataHeader.getLLId(), (m_netDataHeader.getSrcLLId() > 0U), m_netDataHeader.getSrcLLId()); m_netDataHeader.reset(); m_netExtendedAddress = false; @@ -695,7 +697,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) m_netExtendedAddress = true; } else { - LogMessage(LOG_NET, P25_PDU_STR ", block %u, fmt = $%02X, lastBlock = %u", + LogMessage(LOG_NET, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u", (m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? m_netData[i].getSerialNo() : m_netDataBlockCnt, m_netData[i].getFormat(), m_netData[i].getLastBlock()); } @@ -1262,7 +1264,7 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) } // acknowledge - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId); + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId, false); if (hasLLIdFNEReg(llId)) { // remove dynamic FNE registration table entry @@ -1415,7 +1417,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) isp->getDeactType()); } - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId); + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId, false); sndcpReset(llId, true); } break; @@ -1728,7 +1730,7 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA /* Helper to write a PDU acknowledge response. */ -void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId) +void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, uint32_t srcLlId) { if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) return; @@ -1752,7 +1754,11 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a if (srcLlId > 0U) { rspHeader.setSrcLLId(srcLlId); } - rspHeader.setFullMessage(true); + + if (extendedAddress) + rspHeader.setFullMessage(false); + else + rspHeader.setFullMessage(true); rspHeader.setBlocksToFollow(0U); diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 671a782c8..21b94ef8f 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -234,9 +234,11 @@ namespace p25 * @param ackType Acknowledgement Type. * @param ackStatus * @param llId Logical Link ID. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. * @param srcLlId Source Logical Link ID. */ - void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U); + void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, + uint32_t srcLlId = 0U); }; } // namespace packet } // namespace p25 From 79ae3eb63cd73c1dc703eea5db4855e00287a197 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 29 Aug 2025 17:36:04 -0400 Subject: [PATCH 133/133] correct CSV parsing for iden, peer list and RID lookup tables (we would skip parameters if they were empty); make the FNE P25 packet data handler operate in the TIA-102 asymmetric addressing mode (as would be required with FNE configurations); --- src/common/lookups/IdenTableLookup.cpp | 17 ++----- src/common/lookups/PeerListLookup.cpp | 17 ++----- src/common/lookups/RadioIdLookup.cpp | 18 +++----- src/common/p25/data/DataHeader.cpp | 3 ++ src/common/p25/data/DataHeader.h | 9 ++-- .../callhandler/packetdata/P25PacketData.cpp | 45 +++++++++++++------ .../callhandler/packetdata/P25PacketData.h | 1 - 7 files changed, 54 insertions(+), 56 deletions(-) diff --git a/src/common/lookups/IdenTableLookup.cpp b/src/common/lookups/IdenTableLookup.cpp index 0c38016aa..50bb082e2 100644 --- a/src/common/lookups/IdenTableLookup.cpp +++ b/src/common/lookups/IdenTableLookup.cpp @@ -113,22 +113,13 @@ bool IdenTableLookup::load() continue; // tokenize line - std::string next; std::vector parsed; + std::stringstream ss(line); + std::string field; char delim = ','; - for (auto it = line.begin(); it != line.end(); it++) { - if (*it == delim) { - if (!next.empty()) { - parsed.push_back(next); - next.clear(); - } - } - else - next += *it; - } - if (!next.empty()) - parsed.push_back(next); + while (std::getline(ss, field, delim)) + parsed.push_back(field); // ensure we have at least 5 fields if (parsed.size() < 5) { diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index 4eb39df47..9215e3b9c 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -206,22 +206,13 @@ bool PeerListLookup::load() continue; // tokenize line - std::string next; std::vector parsed; + std::stringstream ss(line); + std::string field; char delim = ','; - for (char c : line) { - if (c == delim) { - //if (!next.empty()) { - parsed.push_back(next); - next.clear(); - //} - } - else - next += c; - } - if (!next.empty()) - parsed.push_back(next); + while (std::getline(ss, field, delim)) + parsed.push_back(field); // parse tokenized line uint32_t id = ::atoi(parsed[0].c_str()); diff --git a/src/common/lookups/RadioIdLookup.cpp b/src/common/lookups/RadioIdLookup.cpp index 55c168899..8cb5fc129 100644 --- a/src/common/lookups/RadioIdLookup.cpp +++ b/src/common/lookups/RadioIdLookup.cpp @@ -192,22 +192,13 @@ bool RadioIdLookup::load() continue; // tokenize line - std::string next; std::vector parsed; + std::stringstream ss(line); + std::string field; char delim = ','; - for (char c : line) { - if (c == delim) { - if (!next.empty()) { - parsed.push_back(next); - next.clear(); - } - } - else - next += c; - } - if (!next.empty()) - parsed.push_back(next); + while (std::getline(ss, field, delim)) + parsed.push_back(field); // ensure we have at least 2 fields if (parsed.size() < 2) { @@ -232,6 +223,7 @@ bool RadioIdLookup::load() } m_table[id] = RadioId(radioEnabled, false, alias, ipAddress); + //::LogInfoEx(LOG_HOST, "Radio NAME: %s RID: %u ENABLED: %u IPADDR: %s", alias.c_str(), id, radioEnabled, ipAddress.c_str()); } } diff --git a/src/common/p25/data/DataHeader.cpp b/src/common/p25/data/DataHeader.cpp index 923ded10e..211953dac 100644 --- a/src/common/p25/data/DataHeader.cpp +++ b/src/common/p25/data/DataHeader.cpp @@ -225,6 +225,9 @@ void DataHeader::encode(uint8_t* data, bool noTrellis) header[1U] = ((m_rspClass & 0x03U) << 6) + // Response Class ((m_rspType & 0x07U) << 3) + // Response Type ((m_rspStatus & 0x07U)); // Response Status + + // the "full message" flag in a response PDU indicates whether or not the response is to an + // extended addressing PDU if (!m_F) { header[7U] = (m_srcLlId >> 16) & 0xFFU; // Source Logical Link ID header[8U] = (m_srcLlId >> 8) & 0xFFU; diff --git a/src/common/p25/data/DataHeader.h b/src/common/p25/data/DataHeader.h index 0a4409106..d59c8c5c4 100644 --- a/src/common/p25/data/DataHeader.h +++ b/src/common/p25/data/DataHeader.h @@ -185,7 +185,7 @@ namespace p25 */ DECLARE_PROPERTY(uint8_t, headerOffset, HeaderOffset); - // Extended Addressing Data + /** @name Symmetric Addressing Data */ /** * @brief Service access point. */ @@ -194,8 +194,9 @@ namespace p25 * @brief Source Logical link ID. */ DECLARE_PROPERTY(uint32_t, srcLlId, SrcLLId); + /** @} */ - // Response Data + /** @name Response Packet Data */ /** * @brief Response class. */ @@ -208,8 +209,9 @@ namespace p25 * @brief Response status. */ DECLARE_PROPERTY(uint8_t, rspStatus, ResponseStatus); + /** @} */ - // AMBT Data + /** @name AMBT Packet Data */ /** * @brief Alternate Trunking Block Opcode */ @@ -222,6 +224,7 @@ namespace p25 * @brief Alternate Trunking Block Field 9 */ DECLARE_PROPERTY(uint8_t, ambtField9, AMBTField9); + /** @} */ private: edac::Trellis m_trellis; diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index a626b996e..53fa07728 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -364,7 +364,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a Utils::dump(1U, "P25, P25PacketData::processPacketFrame() packet", data, pktLen); #endif - uint32_t dstLlId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); + uint32_t llId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); uint32_t tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); @@ -373,7 +373,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a std::string tgtIpStr = __IP_FROM_UINT(tgtProtoAddr); LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", - srcIpStr.c_str(), WUID_FNE, tgtIpStr.c_str(), dstLlId, pktLen, proto); + srcIpStr.c_str(), WUID_FNE, tgtIpStr.c_str(), llId, pktLen, proto); // assemble a P25 PDU frame header for transport... data::DataHeader* pktHeader = new data::DataHeader(); @@ -381,13 +381,10 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a pktHeader->setMFId(MFG_STANDARD); pktHeader->setAckNeeded(true); pktHeader->setOutbound(true); - pktHeader->setSAP(PDUSAP::EXT_ADDR); - pktHeader->setLLId(dstLlId); + pktHeader->setSAP(PDUSAP::PACKET_DATA); + pktHeader->setLLId(llId); pktHeader->setBlocksToFollow(1U); - pktHeader->setEXSAP(PDUSAP::PACKET_DATA); - pktHeader->setSrcLLId(WUID_FNE); - pktHeader->calculateLength(pktLen); uint32_t pduLength = pktHeader->getPDULength(); if (pduLength < pktLen) { @@ -396,7 +393,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a } DECLARE_UINT8_ARRAY(pduUserData, pduLength); - ::memcpy(pduUserData + 4U, data, pktLen); + ::memcpy(pduUserData, data, pktLen); #if DEBUG_P25_PDU_DATA Utils::dump(1U, "P25, P25PacketData::processPacketFrame(), pduUserData", pduUserData, pduLength); #endif @@ -408,8 +405,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a qf->timestamp = now + INTERPACKET_DELAY; qf->header = pktHeader; - qf->extendedAddress = true; - qf->llId = dstLlId; + qf->llId = llId; qf->tgtProtoAddr = tgtProtoAddr; qf->userData = new uint8_t[pduLength]; @@ -484,7 +480,7 @@ void P25PacketData::clock(uint32_t ms) } m_readyForNextPkt[frame->llId] = false; - dispatchUserFrameToFNE(*frame->header, frame->extendedAddress, frame->userData); + dispatchUserFrameToFNE(*frame->header, false, frame->userData); } } @@ -862,11 +858,10 @@ bool P25PacketData::processConvDataReg(RxStatus* status) if (ipAddr == 0U) { LogWarning(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect) with zero IP address, llId = %u", llId); - return false; + ipAddr = getIPAddress(llId); } LogMessage(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); - m_arpTable[llId] = ipAddr; // update ARP table } break; @@ -1259,6 +1254,16 @@ uint32_t P25PacketData::getIPAddress(uint32_t llId) if (hasARPEntry(llId)) { return m_arpTable[llId]; + } else { + // do we have a static entry for this LLID? + lookups::RadioId rid = m_network->m_ridLookup->find(llId); + if (!rid.radioDefault()) { + if (rid.radioEnabled()) { + std::string addr = rid.radioIPAddress(); + uint32_t ipAddr = __IP_FROM_STR(addr); + return ipAddr; + } + } } return 0U; @@ -1279,5 +1284,19 @@ uint32_t P25PacketData::getLLIdAddress(uint32_t addr) } } + // lookup IP from static RID table + std::string ipAddr = __IP_FROM_UINT(addr); + std::unordered_map ridTable = m_network->m_ridLookup->table(); + auto it = std::find_if(ridTable.begin(), ridTable.end(), [&](std::pair x) { + if (x.second.radioIPAddress() == ipAddr) { + if (x.second.radioEnabled() && !x.second.radioDefault()) + return true; + } + return false; + }); + if (it != ridTable.end()) { + return it->first; + } + return 0U; } diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index 46eaf0e84..30392adef 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -93,7 +93,6 @@ namespace network class QueuedDataFrame { public: p25::data::DataHeader* header; //! Instance of a PDU data header. - bool extendedAddress; //! Flag indicating whether or not to extended addressing is in use. uint32_t llId; //! Logical Link ID uint32_t tgtProtoAddr; //! Target Protocol Address