From 917f23cb88e2a79d5d65ad6bf7f61764c6b285a6 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 17 Apr 2025 22:22:22 -0400 Subject: [PATCH 01/41] implement support for LC_GROUP_UPDT (this includes the RPC between the CC and VC's to announce active TG lists); completely and entirely refactor how packet handling threads are done, use a new method introduced for this, thread pool resources. this will ultimately be more resource consuming depending on configuration as the worker threads for packet processing stay alive along side the main process. this should be more performant (because we're not constantly creating and destroying threads) and will prevent error conditions that can cause an extreme number of threads to spawn; --- configs/fne-config.example.yml | 3 + src/common/Defines.h | 4 +- src/common/Thread.h | 2 +- src/common/ThreadPool.cpp | 225 ++++++++++++++++++ src/common/ThreadPool.h | 158 ++++++++++++ src/fne/HostFNE.cpp | 18 +- src/fne/network/DiagNetwork.cpp | 44 ++-- src/fne/network/DiagNetwork.h | 9 +- src/fne/network/FNENetwork.cpp | 93 ++++---- src/fne/network/FNENetwork.h | 12 +- src/fne/network/influxdb/InfluxDB.cpp | 2 +- src/fne/network/influxdb/InfluxDB.h | 56 ++--- .../modem/port/specialized/V24UDPPort.cpp | 71 +++--- src/host/modem/port/specialized/V24UDPPort.h | 15 +- src/host/network/RPCDefines.h | 1 + src/host/p25/Control.cpp | 72 ++++++ src/host/p25/Control.h | 10 + src/host/p25/packet/Voice.cpp | 96 ++++++-- src/host/p25/packet/Voice.h | 3 +- 19 files changed, 718 insertions(+), 176 deletions(-) create mode 100644 src/common/ThreadPool.cpp create mode 100644 src/common/ThreadPool.h diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 66594e5a..72b5c900 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -46,6 +46,9 @@ master: # Flag indicating whether or not verbose debug logging is enabled. debug: false + # Maximum number of concurrent packet processing workers. + workers: 16 + # Maximum permitted connections (hard maximum is 250 peers). connectionLimit: 100 diff --git a/src/common/Defines.h b/src/common/Defines.h index dc7bfdba..8a7c5157 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -108,8 +108,8 @@ typedef unsigned long long ulong64_t; #define __EXE_NAME__ "" #define VERSION_MAJOR "04" -#define VERSION_MINOR "21" -#define VERSION_REV "G" +#define VERSION_MINOR "22" +#define VERSION_REV "H" #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/Thread.h b/src/common/Thread.h index 66a0c81e..9b5cf31b 100644 --- a/src/common/Thread.h +++ b/src/common/Thread.h @@ -101,7 +101,7 @@ class HOST_SW_API Thread { /** * @brief Executes the specified start routine to run as a thread. * On POSIX, if the thread fails to spawn for any reason, the caller should clean up the passed thread_t - * argument. + * argument. This shouldn't be used for spawning threads used for things like packet processing. * @param obj Instance of a object to pass to the threaded function. * @param startRoutine Represents the function that executes on a thread. * @param[out] thread Instance of the thread data. diff --git a/src/common/ThreadPool.cpp b/src/common/ThreadPool.cpp new file mode 100644 index 00000000..a30351e3 --- /dev/null +++ b/src/common/ThreadPool.cpp @@ -0,0 +1,225 @@ +// 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 "ThreadPool.h" +#include "Log.h" + +#include +#include +#if !defined(_WIN32) +#include +#endif // !defined(_WIN32) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define MIN_WORKER_CNT 4U + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the ThreadPool class. */ + +ThreadPool::ThreadPool(uint16_t workerCnt, std::string name) : + m_maxWorkerCnt(workerCnt), + m_maxQueuedTasks(0U), + m_poolState(STOP), + m_workers(), + m_tasks(), + m_workerMutex(), + m_queueMutex(), + m_cond(), + m_name(name) +{ + if (m_maxWorkerCnt < MIN_WORKER_CNT) + m_maxWorkerCnt = MIN_WORKER_CNT; +} + +/* Finalizes a instance of the ThreadPool class. */ + +ThreadPool::~ThreadPool() = default; + +/* Enqueue a thread pool task. */ + +bool ThreadPool::enqueue(ThreadPoolCallback* task) +{ + // scope is intentional + { + std::unique_lock lock(m_workerMutex); + if (m_poolState == RUNNING && m_workers.size() < m_maxWorkerCnt) { + thread_t* thread = new thread_t(); + thread->obj = this; + +#if defined(_WIN32) + HANDLE hnd = ::CreateThread(NULL, 0, worker, thread, CREATE_SUSPENDED, NULL); + if (hnd == NULL) { + LogError(LOG_HOST, "Error returned from CreateThread, err: %lu", ::GetLastError()); + return false; + } + + thread->thread = hnd; + ::ResumeThread(hnd); +#else + if (::pthread_create(&thread->thread, NULL, worker, thread) != 0) { + LogError(LOG_HOST, "Error returned from pthread_create, err: %d", errno); + return false; + } +#endif // defined(_WIN32) + + m_workers.emplace_back(thread->thread); + } + } + + // scope is intentional + { + std::unique_lock lock(m_queueMutex); + if (m_poolState == STOP) { + LogError(LOG_HOST, "Cannot enqueue task on a stopped thread pool!"); + return false; + } + + if (m_maxQueuedTasks > 0U && m_tasks.size() >= m_maxQueuedTasks) { + LogError(LOG_HOST, "Cannot enqueue task, thread pool queue is full!"); + return false; + } + + m_tasks.emplace(std::unique_ptr(task)); + } + + m_cond.notify_one(); + return true; +} + +/* Starts the thread pool. */ + +void ThreadPool::start() +{ + // scope is intentional + { + std::unique_lock lock(m_queueMutex); + m_poolState = RUNNING; + + uint32_t numWorkingThreads = m_maxWorkerCnt < m_tasks.size() ? m_maxWorkerCnt : m_tasks.size(); + for (uint32_t i = m_workers.size(); i < numWorkingThreads; i++) { + thread_t* thread = new thread_t(); + thread->obj = this; + +#if defined(_WIN32) + HANDLE hnd = ::CreateThread(NULL, 0, worker, thread, CREATE_SUSPENDED, NULL); + if (hnd == NULL) { + LogError(LOG_HOST, "Error returned from CreateThread, err: %lu", ::GetLastError()); + continue; + } + + thread->thread = hnd; + ::ResumeThread(hnd); +#else + if (::pthread_create(&thread->thread, NULL, worker, thread) != 0) { + LogError(LOG_HOST, "Error returned from pthread_create, err: %d", errno); + continue; + } +#endif // defined(_WIN32) + + m_workers.emplace_back(thread->thread); + } + } + + m_cond.notify_all(); +} + +/* Stops the thread pool. */ + +void ThreadPool::stop() +{ + // scope is intentional + { + std::unique_lock lock(m_queueMutex); + m_poolState = STOP; + } + + m_cond.notify_all(); +} + +/* Make calling thread wait for termination of any remaining thread pool tasks. */ + +void ThreadPool::wait() +{ + m_cond.notify_all(); + + for (pthread_t worker : m_workers) { +#if defined(_WIN32) + ::WaitForSingleObject(worker, INFINITE); + ::CloseHandle(worker); +#else + ::pthread_join(worker, NULL); +#endif // defined(_WIN32) + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal worker thread that is used to execute task functions. */ + +#if defined(_WIN32) +DWORD ThreadPool::worker(LPVOID arg) +#else +void* ThreadPool::worker(void* arg) +#endif // defined(_WIN32) +{ + thread_t* thread = (thread_t*)arg; + if (thread == nullptr) { + LogError(LOG_HOST, "Fatal error starting thread pool worker! No thread!"); + return nullptr; + } + + ThreadPool* threadPool = (ThreadPool*)thread->obj; + if (threadPool == nullptr) { + LogError(LOG_HOST, "Fatal error starting thread pool worker! No thread pool owner!"); + delete thread; + return nullptr; + } + + std::stringstream threadName; + threadName << threadPool->m_name << ":worker"; +#ifdef _GNU_SOURCE + ::pthread_setname_np(thread->thread, threadName.str().c_str()); +#endif // _GNU_SOURCE + + ThreadPoolCallback* callback; + while (threadPool->m_poolState != STOP) { + // scope is intentional + { + std::unique_lock lock(threadPool->m_queueMutex); + threadPool->m_cond.wait(lock, [=] { return threadPool->m_poolState == STOP || !threadPool->m_tasks.empty(); }); + if (threadPool->m_poolState == STOP && threadPool->m_tasks.empty()) { + delete thread; +#if defined(_WIN32) + return 0UL; +#else + return nullptr; +#endif // defined(_WIN32) + } + + callback = (threadPool->m_tasks.front()).release(); + threadPool->m_tasks.pop(); + } + + callback->run(); + } + +#if defined(_WIN32) + return 0UL; +#else + return nullptr; +#endif // defined(_WIN32) +} diff --git a/src/common/ThreadPool.h b/src/common/ThreadPool.h new file mode 100644 index 00000000..f9e91ed6 --- /dev/null +++ b/src/common/ThreadPool.h @@ -0,0 +1,158 @@ +// 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 ThreadPool.h + * @ingroup threading + * @file ThreadPool.cpp + * @ingroup threading + */ +#if !defined(__THREAD_POOL_H__) +#define __THREAD_POOL_H__ + +#include "common/Defines.h" +#include "common/Thread.h" + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief Represents a task run by a thread pool worker thread. + * @ingroup threading + */ +class HOST_SW_API ThreadPoolCallback { +public: + /** + * @brief Initializes a new instance of the ThreadPoolCallback class. + * @tparam F + * @tparam Args + * @param f + * @param args + */ + template + ThreadPoolCallback(F&& f, Args&&... args) + { + task = std::bind(std::forward(f), std::forward(args)...); + } + virtual ~ThreadPoolCallback() = default; + + /** + * @brief Starts the task function. + */ + void run() { task(); } + +private: + std::function task; +}; + +/** + * @brief + * @tparam F + * @tparam Args + * @param f + * @param args + * @return ThreadPoolCallback* + */ +template +ThreadPoolCallback* new_pooltask(F&& f, Args&&... args) { return new ThreadPoolCallback(f, args...); } + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief Creates and controls a thread pool. + * @ingroup threading + */ +class HOST_SW_API ThreadPool { +public: + /** + * @brief Initializes a new instance of the Thread class. + * @param workerCnt Number of worker threads to create. + * @param poolName Name of the thread pool. + */ + ThreadPool(uint16_t workerCnt = 4U, std::string poolName = "pool"); + /** + * @brief Finalizes a instance of the Thread class. + */ + virtual ~ThreadPool(); + + /** + * @brief Enqueues a thread pool task. + * @param task Task to enqueue. + * @returns bool True, if task enqueued otherwise false. + */ + bool enqueue(ThreadPoolCallback* task); + + /** + * @brief Starts the thread pool. + */ + void start(); + /** + * @brief Stops the thread pool. + */ + void stop(); + + /** + * @brief Make calling thread wait for termination of any remaining thread pool tasks. + */ + void wait(); + +public: + /** + * @brief Maximum number of worker threads. + */ + __PROPERTY(uint16_t, maxWorkerCnt, MaxWorkerCnt); + /** + * @brief Maximum number of queued tasks. + */ + __PROPERTY(uint16_t, maxQueuedTasks, MaxQueuedTasks); + +private: + + /** + * @brief Thread pool state. + */ + enum PoolState { + STOP = 0, + IDLE, + RUNNING + }; + + PoolState m_poolState; + + std::vector m_workers; + std::queue> m_tasks; + + std::mutex m_workerMutex; + std::mutex m_queueMutex; + std::condition_variable m_cond; + + std::string m_name; + + /** + * @brief Internal worker thats used as the entry point for the worker threads. + * @param arg + * @returns void* + */ +#if defined(_WIN32) + static DWORD __stdcall worker(LPVOID arg); +#else + static void* worker(void* arg); +#endif // defined(_WIN32) +}; + +#endif // __THREAD_POOL_H__ diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 1343d9d2..f99070a7 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -39,6 +39,9 @@ using namespace lookups; // Constants // --------------------------------------------------------------------------- +#define MIN_WORKER_CNT 4U +#define MAX_WORKER_CNT 128U + #define THREAD_CYCLE_THRESHOLD 2U #define IDLE_WARMUP_MS 5U @@ -502,6 +505,15 @@ bool HostFNE::createMasterNetwork() std::string password = masterConf["password"].as(); bool verbose = masterConf["verbose"].as(false); bool debug = masterConf["debug"].as(false); + uint16_t workerCnt = (uint16_t)masterConf["workers"].as(16U); + + // clamp worker thread count properly + if (workerCnt > MAX_WORKER_CNT) + workerCnt = MAX_WORKER_CNT; + if (workerCnt > 64U) + LogWarning(LOG_HOST, "Packet worker thread count >64 threads, this is excessive and may result in poor performance."); + if (workerCnt < MIN_WORKER_CNT) + workerCnt = MIN_WORKER_CNT; bool reportPeerPing = masterConf["reportPeerPing"].as(false); @@ -563,6 +575,8 @@ bool HostFNE::createMasterNetwork() LogInfo(" Parrot Repeat Delay: %u ms", parrotDelay); LogInfo(" Parrot Grant Demand: %s", parrotGrantDemand ? "yes" : "no"); + LogInfo(" Worker Threads: %u", workerCnt); + LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); LogInfo(" Report Peer Pings: %s", reportPeerPing ? "yes" : "no"); @@ -577,7 +591,7 @@ bool HostFNE::createMasterNetwork() // initialize networking m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, - parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime); + 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); @@ -600,7 +614,7 @@ bool HostFNE::createMasterNetwork() // setup alternate port for diagnostics/activity logging if (m_useAlternatePortForDiagnostics) { - m_diagNetwork = new DiagNetwork(this, m_network, address, port + 1U); + m_diagNetwork = new DiagNetwork(this, m_network, address, port + 1U, workerCnt); bool ret = m_diagNetwork->open(); if (!ret) { diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index dd54690b..1343dd5e 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -26,12 +26,13 @@ using namespace network::callhandler; /* Initializes a new instance of the DiagNetwork class. */ -DiagNetwork::DiagNetwork(HostFNE* host, FNENetwork* fneNetwork, const std::string& address, uint16_t port) : +DiagNetwork::DiagNetwork(HostFNE* host, FNENetwork* fneNetwork, const std::string& address, uint16_t port, uint16_t workerCnt) : BaseNetwork(fneNetwork->m_peerId, true, fneNetwork->m_debug, true, true, fneNetwork->m_allowActivityTransfer, fneNetwork->m_allowDiagnosticTransfer), m_fneNetwork(fneNetwork), m_host(host), m_address(address), - m_port(port) + m_port(port), + m_threadPool(workerCnt, "diag") { assert(fneNetwork != nullptr); assert(host != nullptr); @@ -73,6 +74,7 @@ void DiagNetwork::processNetwork() uint32_t peerId = fneHeader.getPeerId(); NetPacketRequest* req = new NetPacketRequest(); + req->obj = m_fneNetwork; req->peerId = peerId; req->address = address; @@ -84,11 +86,14 @@ void DiagNetwork::processNetwork() req->buffer = new uint8_t[length]; ::memcpy(req->buffer, buffer.get(), length); - if (!Thread::runAsThread(m_fneNetwork, threadedNetworkRx, req)) { - if (req->buffer != nullptr) - delete[] req->buffer; - delete req; - return; + if (!m_threadPool.enqueue(new_pooltask(taskNetworkRx, req))) { + LogError(LOG_NET, "Failed to task enqueue network packet request, peerId = %u, %s:%u", peerId, + udp::Socket::address(address).c_str(), udp::Socket::port(address)); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } } } } @@ -109,6 +114,8 @@ bool DiagNetwork::open() if (m_debug) LogMessage(LOG_NET, "Opening Network"); + m_threadPool.start(); + m_status = NET_STAT_MST_RUNNING; m_socket = new udp::Socket(m_address, m_port); @@ -134,6 +141,9 @@ void DiagNetwork::close() if (m_debug) LogMessage(LOG_NET, "Closing Network"); + m_threadPool.stop(); + m_threadPool.wait(); + m_socket->close(); m_status = NET_STAT_INVALID; @@ -145,16 +155,10 @@ void DiagNetwork::close() /* Process a data frames from the network. */ -void* DiagNetwork::threadedNetworkRx(void* arg) +void DiagNetwork::taskNetworkRx(void* arg) { NetPacketRequest* req = (NetPacketRequest*)arg; if (req != nullptr) { -#if defined(_WIN32) - ::CloseHandle(req->thread); -#else - ::pthread_detach(req->thread); -#endif // defined(_WIN32) - FNENetwork* network = static_cast(req->obj); if (network == nullptr) { if (req != nullptr) { @@ -163,21 +167,15 @@ void* DiagNetwork::threadedNetworkRx(void* arg) delete req; } - return nullptr; + return; } if (req == nullptr) - return nullptr; + return; if (req->length > 0) { uint32_t peerId = req->fneHeader.getPeerId(); - std::stringstream peerName; - peerName << peerId << ":diag-rx-pckt"; -#ifdef _GNU_SOURCE - ::pthread_setname_np(req->thread, peerName.str().c_str()); -#endif // _GNU_SOURCE - // process incoming message function opcodes switch (req->fneHeader.getFunction()) { case NET_FUNC::TRANSFER: // Transfer @@ -422,6 +420,4 @@ void* DiagNetwork::threadedNetworkRx(void* arg) delete[] req->buffer; delete req; } - - return nullptr; } diff --git a/src/fne/network/DiagNetwork.h b/src/fne/network/DiagNetwork.h index 220cab46..c42f50e1 100644 --- a/src/fne/network/DiagNetwork.h +++ b/src/fne/network/DiagNetwork.h @@ -18,6 +18,7 @@ #include "fne/Defines.h" #include "common/network/BaseNetwork.h" +#include "common/ThreadPool.h" #include "fne/network/FNENetwork.h" #include @@ -46,8 +47,9 @@ namespace network * @param network Instance of the FNENetwork class. * @param address Network Hostname/IP address to listen on. * @param port Network port number. + * @param workerCnt Number of worker threads. */ - DiagNetwork(HostFNE* host, FNENetwork* fneNetwork, const std::string& address, uint16_t port); + DiagNetwork(HostFNE* host, FNENetwork* fneNetwork, const std::string& address, uint16_t port, uint16_t workerCnt); /** * @brief Finalizes a instance of the DiagNetwork class. */ @@ -97,12 +99,13 @@ namespace network NET_CONN_STATUS m_status; + ThreadPool m_threadPool; + /** * @brief Entry point to process a given network packet. * @param arg Instance of the NetPacketRequest structure. - * @returns void* (Ignore) */ - static void* threadedNetworkRx(void* arg); + static void taskNetworkRx(void* arg); }; } // namespace network diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 5e75a8ce..dc970804 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -53,7 +53,7 @@ std::timed_mutex FNENetwork::m_keyQueueMutex; 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 allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime) : + 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), @@ -100,6 +100,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_influxOrg("dvm"), m_influxBucket("dvm"), m_influxLogRawData(false), + m_threadPool(workerCnt, "fne"), m_disablePacketData(false), m_dumpPacketData(false), m_verbosePacketData(false), @@ -253,6 +254,7 @@ void FNENetwork::processNetwork() uint32_t peerId = fneHeader.getPeerId(); NetPacketRequest* req = new NetPacketRequest(); + req->obj = this; req->peerId = peerId; req->address = address; @@ -264,11 +266,15 @@ void FNENetwork::processNetwork() req->buffer = new uint8_t[length]; ::memcpy(req->buffer, buffer.get(), length); - if (!Thread::runAsThread(this, threadedNetworkRx, req)) { - if (req->buffer != nullptr) - delete[] req->buffer; - delete req; - return; + // enqueue the task + if (!m_threadPool.enqueue(new_pooltask(taskNetworkRx, req))) { + LogError(LOG_NET, "Failed to task enqueue network packet request, peerId = %u, %s:%u", peerId, + udp::Socket::address(address).c_str(), udp::Socket::port(address)); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } } } } @@ -426,6 +432,14 @@ bool FNENetwork::open() if (m_debug) LogMessage(LOG_NET, "Opening Network"); + // start thread pool + m_threadPool.start(); + + // start FluxQL thread pool + if (m_enableInfluxDB) { + influxdb::detail::TSCaller::start(); + } + m_status = NET_STAT_MST_RUNNING; m_maintainenceTimer.start(); @@ -462,10 +476,20 @@ void FNENetwork::close() } } - m_socket->close(); - m_maintainenceTimer.stop(); + // stop thread pool + m_threadPool.stop(); + m_threadPool.wait(); + + // stop FluxQL thread pool + if (m_enableInfluxDB) { + influxdb::detail::TSCaller::stop(); + influxdb::detail::TSCaller::wait(); + } + + m_socket->close(); + m_status = NET_STAT_INVALID; } @@ -475,16 +499,10 @@ void FNENetwork::close() /* Process a data frames from the network. */ -void* FNENetwork::threadedNetworkRx(void* arg) +void FNENetwork::taskNetworkRx(void* arg) { NetPacketRequest* req = (NetPacketRequest*)arg; if (req != nullptr) { -#if defined(_WIN32) - ::CloseHandle(req->thread); -#else - ::pthread_detach(req->thread); -#endif // defined(_WIN32) - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); FNENetwork* network = static_cast(req->obj); @@ -495,22 +513,16 @@ void* FNENetwork::threadedNetworkRx(void* arg) delete req; } - return nullptr; + return; } if (req == nullptr) - return nullptr; + return; if (req->length > 0) { uint32_t peerId = req->fneHeader.getPeerId(); uint32_t streamId = req->fneHeader.getStreamId(); - std::stringstream peerName; - peerName << peerId << ":rx-pckt"; -#ifdef _GNU_SOURCE - ::pthread_setname_np(req->thread, peerName.str().c_str()); -#endif // _GNU_SOURCE - // update current peer packet sequence and stream ID if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end()) && streamId != 0U) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -548,7 +560,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) delete[] req->buffer; delete req; - return nullptr; + return; } // process incoming message function opcodes @@ -1606,8 +1618,6 @@ void* FNENetwork::threadedNetworkRx(void* arg) delete[] req->buffer; delete req; } - - return nullptr; } /* Checks if the passed peer ID is blocked from unit-to-unit traffic. */ @@ -1810,43 +1820,32 @@ void FNENetwork::setupRepeaterLogin(uint32_t peerId, uint32_t streamId, FNEPeerC void FNENetwork::peerACLUpdate(uint32_t peerId) { ACLUpdateRequest* req = new ACLUpdateRequest(); + req->obj = this; req->peerId = peerId; - std::stringstream peerName; - peerName << peerId << ":acl-update"; - - if (!Thread::runAsThread(this, threadedACLUpdate, req)) { - delete req; - return; + // enqueue the task + if (!m_threadPool.enqueue(new_pooltask(taskACLUpdate, req))) { + LogError(LOG_NET, "Failed to task enqueue ACL update, peerId = %u", peerId); + if (req != nullptr) + delete req; } - - // pthread magic to rename the thread properly -#ifdef _GNU_SOURCE - ::pthread_setname_np(req->thread, peerName.str().c_str()); -#endif // _GNU_SOURCE } /* Helper to send the ACL lists to the specified peer in a separate thread. */ -void* FNENetwork::threadedACLUpdate(void* arg) +void FNENetwork::taskACLUpdate(void* arg) { ACLUpdateRequest* req = (ACLUpdateRequest*)arg; if (req != nullptr) { -#if defined(_WIN32) - ::CloseHandle(req->thread); -#else - ::pthread_detach(req->thread); -#endif // defined(_WIN32) - FNENetwork* network = static_cast(req->obj); if (network == nullptr) { if (req != nullptr) delete req; - return nullptr; + return; } if (req == nullptr) - return nullptr; + return; std::string peerIdentity = network->resolvePeerIdentity(req->peerId); @@ -1875,8 +1874,6 @@ void* FNENetwork::threadedACLUpdate(void* arg) delete req; } - - return nullptr; } /* Helper to send the list of whitelisted RIDs to the specified peer. */ diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 8f4103f2..393c5fb1 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -32,6 +32,7 @@ #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" #include "common/network/Network.h" +#include "common/ThreadPool.h" #include "fne/network/influxdb/InfluxDB.h" #include "fne/CryptoContainer.h" @@ -422,10 +423,11 @@ namespace network * @param allowDiagnosticTransfer Flag indicating that the system diagnostic logs will be sent to the network. * @param pingTime * @param updateLookupTime + * @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 allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime); + bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt); /** * @brief Finalizes a instance of the FNENetwork class. */ @@ -590,6 +592,8 @@ namespace network bool m_influxLogRawData; influxdb::ServerInfo m_influxServer; + ThreadPool m_threadPool; + bool m_disablePacketData; bool m_dumpPacketData; bool m_verbosePacketData; @@ -600,9 +604,8 @@ namespace network /** * @brief Entry point to process a given network packet. * @param arg Instance of the NetPacketRequest structure. - * @returns void* (Ignore) */ - static void* threadedNetworkRx(void* arg); + static void taskNetworkRx(void* arg); /** * @brief Checks if the passed peer ID is blocked from unit-to-unit traffic. @@ -660,9 +663,8 @@ namespace network /** * @brief Entry point to send the ACL lists to the specified peer in a separate thread. * @param arg Instance of the ACLUpdateRequest structure. - * @returns void* (Ignore) */ - static void* threadedACLUpdate(void* arg); + static void taskACLUpdate(void* arg); /** * @brief Helper to send the list of whitelisted RIDs to the specified peer. diff --git a/src/fne/network/influxdb/InfluxDB.cpp b/src/fne/network/influxdb/InfluxDB.cpp index 269fa112..d0c82cbd 100644 --- a/src/fne/network/influxdb/InfluxDB.cpp +++ b/src/fne/network/influxdb/InfluxDB.cpp @@ -29,7 +29,7 @@ using namespace network::influxdb; // Static Class Members // --------------------------------------------------------------------------- -int32_t detail::TSCaller::m_currThreadCnt = 0U; +ThreadPool detail::TSCaller::m_fluxReqThreadPool{MAX_INFLUXQL_THREAD_CNT, "fluxql"}; /* Generates a InfluxDB REST API request. */ diff --git a/src/fne/network/influxdb/InfluxDB.h b/src/fne/network/influxdb/InfluxDB.h index ef909a65..2ee2a9a2 100644 --- a/src/fne/network/influxdb/InfluxDB.h +++ b/src/fne/network/influxdb/InfluxDB.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (c) 2010-2018 - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -21,7 +21,7 @@ #include "fne/Defines.h" #include "common/Log.h" -#include "common/Thread.h" +#include "common/ThreadPool.h" #include #include @@ -63,7 +63,8 @@ namespace network // Constants // --------------------------------------------------------------------------- - #define MAX_INFLUXQL_THREAD_CNT 125 // Extreme maximum number of pending Flux queries + #define MAX_INFLUXQL_THREAD_CNT 64U + #define MAX_INFLUXQL_QUEUED_CNT 256U // --------------------------------------------------------------------------- // Class Declaration @@ -430,54 +431,43 @@ namespace network int request(const ServerInfo& si, std::string* resp = nullptr) { return detail::inner::request("POST", "write", "", m_lines.str(), si, resp); } int requestAsync(const ServerInfo& si) { - if (m_currThreadCnt < 0) { - m_currThreadCnt = 0; - } - - if (m_currThreadCnt >= MAX_INFLUXQL_THREAD_CNT) { - ::LogError(LOG_HOST, "Maximum concurrent FluxQL thread count reached, dropping request!"); - return 1; - } - TSCallerRequest* req = new TSCallerRequest(); req->obj = this; req->si = ServerInfo(si.host(), si.port(), si.org(), si.token(), si.bucket()); req->lines = std::string(m_lines.str()); - if (!Thread::runAsThread(this, threadedRequest, req)) { - delete req; + // enqueue the task + if (!m_fluxReqThreadPool.enqueue(new_pooltask(taskFluxRequest, req))) { + LogError(LOG_NET, "Failed to task enqueue Influx query request"); + if (req != nullptr) + delete req; return 1; - } else { - m_currThreadCnt++; } return 0; } + static void start() + { + m_fluxReqThreadPool.setMaxQueuedTasks(MAX_INFLUXQL_QUEUED_CNT); + m_fluxReqThreadPool.start(); + } + static void stop() { m_fluxReqThreadPool.stop(); } + static void wait() { m_fluxReqThreadPool.wait(); } + private: - static int32_t m_currThreadCnt; + static ThreadPool m_fluxReqThreadPool; /** * @brief */ - static void* threadedRequest(void* arg) + static void taskFluxRequest(void* arg) { TSCallerRequest* req = (TSCallerRequest*)arg; if (req != nullptr) { - #if defined(_WIN32) - ::CloseHandle(req->thread); - #else - ::pthread_detach(req->thread); - #endif // defined(_WIN32) - - #ifdef _GNU_SOURCE - ::pthread_setname_np(req->thread, "fluxql:request"); - #endif // _GNU_SOURCE - if (req == nullptr) { - m_currThreadCnt--; - return nullptr; + return; } TSCaller* caller = static_cast(req->obj); @@ -486,8 +476,7 @@ namespace network delete req; } - m_currThreadCnt--; - return nullptr; + return; } const ServerInfo& si = req->si; @@ -495,9 +484,6 @@ namespace network delete req; } - - m_currThreadCnt--; - return nullptr; } }; diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 1c85194c..f3808b26 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -31,6 +31,7 @@ using namespace network::udp; // Constants // --------------------------------------------------------------------------- +#define MAX_THREAD_CNT 8U #define RTP_END_OF_CALL_SEQ 65535 const uint32_t BUFFER_LENGTH = 2000U; @@ -80,6 +81,8 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_fscState(CS_NOT_CONNECTED), m_modemState(STATE_P25), m_tx(false), + m_ctrlThreadPool(MAX_THREAD_CNT, "v24cc"), + m_vcThreadPool(MAX_THREAD_CNT, "v24vc"), m_debug(debug) { assert(peerId > 0U); @@ -205,6 +208,9 @@ bool V24UDPPort::openFSC() return false; } + m_ctrlThreadPool.start(); + m_vcThreadPool.start(); + if (m_controlSocket != nullptr) { return m_controlSocket->open(m_controlAddr); } @@ -219,6 +225,8 @@ bool V24UDPPort::open() if (m_controlSocket != nullptr) { return true; // FSC mode always returns that the port was opened } else { + m_vcThreadPool.start(); + if (m_addrLen == 0U) { LogError(LOG_NET, "Unable to resolve the address of the modem"); return false; @@ -317,6 +325,12 @@ void V24UDPPort::closeFSC() Thread::sleep(500U); } + m_ctrlThreadPool.stop(); + m_ctrlThreadPool.wait(); + + m_vcThreadPool.stop(); + m_vcThreadPool.wait(); + m_controlSocket->close(); } } @@ -325,6 +339,9 @@ void V24UDPPort::closeFSC() void V24UDPPort::close() { + m_vcThreadPool.stop(); + m_vcThreadPool.wait(); + if (m_socket != nullptr) m_socket->close(); } @@ -348,6 +365,8 @@ void V24UDPPort::processCtrlNetwork() Utils::dump(1U, "FSC Control Network Message", buffer.get(), length); V24PacketRequest* req = new V24PacketRequest(); + req->obj = this; + req->address = address; req->addrLen = addrLen; @@ -355,30 +374,29 @@ void V24UDPPort::processCtrlNetwork() req->buffer = new uint8_t[length]; ::memcpy(req->buffer, buffer.get(), length); - if (!Thread::runAsThread(this, threadedCtrlNetworkRx, req)) { - delete[] req->buffer; - delete req; - return; + // enqueue the task + if (!m_ctrlThreadPool.enqueue(new_pooltask(taskCtrlNetworkRx, req))) { + LogError(LOG_NET, "Failed to task enqueue control network packet request, %s:%u", + udp::Socket::address(address).c_str(), udp::Socket::port(address)); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } } } } /* Process a data frames from the network. */ -void* V24UDPPort::threadedCtrlNetworkRx(void* arg) +void V24UDPPort::taskCtrlNetworkRx(void* arg) { V24PacketRequest* req = (V24PacketRequest*)arg; if (req != nullptr) { -#if defined(_WIN32) - ::CloseHandle(req->thread); -#else - ::pthread_detach(req->thread); -#endif // defined(_WIN32) - V24UDPPort* network = static_cast(req->obj); if (network == nullptr) { delete req; - return nullptr; + return; } if (req->length > 0) { @@ -625,8 +643,6 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg) delete[] req->buffer; delete req; } - - return nullptr; } /* Process voice conveyance frames from the network. */ @@ -652,6 +668,8 @@ void V24UDPPort::processVCNetwork() Utils::dump("!!! Rx Incoming DFSI UDP", data, ret); V24PacketRequest* req = new V24PacketRequest(); + req->obj = this; + req->address = addr; req->addrLen = addrLen; @@ -672,10 +690,15 @@ void V24UDPPort::processVCNetwork() ::memcpy(req->buffer, data + RTP_HEADER_LENGTH_BYTES, req->length); - if (!Thread::runAsThread(this, threadedVCNetworkRx, req)) { - delete[] req->buffer; - delete req; - return; + // enqueue the task + if (!m_vcThreadPool.enqueue(new_pooltask(taskVCNetworkRx, req))) { + LogError(LOG_NET, "Failed to task enqueue voice network packet request, %s:%u", + udp::Socket::address(addr).c_str(), udp::Socket::port(addr)); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } } } } @@ -684,20 +707,14 @@ void V24UDPPort::processVCNetwork() /* Process a data frames from the network. */ -void* V24UDPPort::threadedVCNetworkRx(void* arg) +void V24UDPPort::taskVCNetworkRx(void* arg) { V24PacketRequest* req = (V24PacketRequest*)arg; if (req != nullptr) { -#if defined(_WIN32) - ::CloseHandle(req->thread); -#else - ::pthread_detach(req->thread); -#endif // defined(_WIN32) - V24UDPPort* network = static_cast(req->obj); if (network == nullptr) { delete req; - return nullptr; + return; } if (req->length > 0) { @@ -726,8 +743,6 @@ void* V24UDPPort::threadedVCNetworkRx(void* arg) delete[] req->buffer; delete req; } - - return nullptr; } /* Internal helper to setup the local voice channel port. */ diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 0329f54a..76547d56 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -26,7 +26,7 @@ #include "common/network/RTPHeader.h" #include "common/RingBuffer.h" #include "common/Timer.h" -#include "common/Thread.h" +#include "common/ThreadPool.h" #include "modem/port/IModemPort.h" #include @@ -182,6 +182,9 @@ namespace modem uint8_t m_modemState; bool m_tx; + ThreadPool m_ctrlThreadPool; + ThreadPool m_vcThreadPool; + bool m_debug; static std::mutex m_bufferMutex; @@ -193,10 +196,9 @@ namespace modem /** * @brief Entry point to process a given network packet. - * @param arg Instance of the NetPacketRequest structure. - * @returns void* (Ignore) + * @param arg Instance of the V24PacketRequest structure. */ - static void* threadedCtrlNetworkRx(void* arg); + static void taskCtrlNetworkRx(void* arg); /** * @brief Process voice conveyance frames from the network. @@ -205,10 +207,9 @@ namespace modem /** * @brief Entry point to process a given network packet. - * @param arg Instance of the NetPacketRequest structure. - * @returns void* (Ignore) + * @param arg Instance of the V24PacketRequest structure. */ - static void* threadedVCNetworkRx(void* arg); + static void taskVCNetworkRx(void* arg); /** * @brief Internal helper to setup the local voice channel port. diff --git a/src/host/network/RPCDefines.h b/src/host/network/RPCDefines.h index f8587997..bd8868d9 100644 --- a/src/host/network/RPCDefines.h +++ b/src/host/network/RPCDefines.h @@ -42,6 +42,7 @@ #define RPC_PERMIT_NXDN_TG 0x0003U #define RPC_DMR_TSCC_PAYLOAD_ACT 0x0010U +#define RPC_ACTIVE_P25_TG 0x0020U /** @} */ diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 1080b576..6767d924 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -42,6 +42,7 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U; // --------------------------------------------------------------------------- std::mutex Control::m_queueLock; +std::mutex Control::m_activeTGLock; // --------------------------------------------------------------------------- // Public Class Members @@ -85,6 +86,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_affiliations(this, chLookup, verbose), m_controlChData(), m_idenEntry(), + m_activeTG(), m_txImmQueue(queueSize, "P25 Imm Frame"), m_txQueue(queueSize, "P25 Frame"), m_rfState(RS_RF_LISTENING), @@ -163,6 +165,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q // register RPC handlers g_RPC->registerHandler(RPC_PERMIT_P25_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); + g_RPC->registerHandler(RPC_ACTIVE_P25_TG, RPC_FUNC_BIND(Control::RPC_activeTG, this)); g_RPC->registerHandler(RPC_RELEASE_P25_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this)); g_RPC->registerHandler(RPC_TOUCH_P25_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this)); } @@ -1049,6 +1052,48 @@ void Control::clockSiteData(uint32_t ms) m_control->m_adjSiteUpdateTimer.setTimeout(m_control->m_adjSiteUpdateInterval); m_control->m_adjSiteUpdateTimer.start(); } + + // do we have any granted channels? + if (m_affiliations.getGrantedRFChCnt() > 0U) { + uint8_t activeCnt = m_affiliations.getGrantedRFChCnt(); + std::unordered_map grantTable = m_affiliations.grantTable(); + + // iterate dynamic channel grant table entries + json::array active = json::array(); + for (auto entry : grantTable) { + uint32_t dstId = entry.first; + active.push_back(json::value((double)dstId)); + } + + std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); + for (auto entry : voiceChs) { + ::lookups::VoiceChData voiceChData = entry.second; + + // callback RPC to transmit active TG list to the voice channels + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { + json::object req = json::object(); + req["active"].set(active); + + g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); + return; + } + + int status = req["status"].get(); + if (status != network::NetRPC::OK) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); + }, voiceChData.address(), voiceChData.port()); + } + } + } } } } @@ -1823,6 +1868,33 @@ void Control::RPC_permittedTG(json::object& req, json::object& reply) permittedTG(dstId, dataPermit); } +/* (RPC Handler) Active TGID list from the authoritative CC host. */ + +void Control::RPC_activeTG(json::object& req, json::object& reply) +{ + g_RPC->defaultResponse(reply, "OK", network::NetRPC::OK); + + if (!req["active"].is()) { + g_RPC->defaultResponse(reply, "\"active\" was not a valid JSON array", network::NetRPC::INVALID_ARGS); + return; + } + json::array active = req["active"].get(); + + std::lock_guard lock(m_activeTGLock); + m_activeTG.clear(); + + if (active.size() > 0) { + for (auto entry : active) { + if (!entry.is()) { + g_RPC->defaultResponse(reply, "active TG was not a valid number", network::NetRPC::INVALID_ARGS); + continue; + } + + m_activeTG.push_back(entry.get()); + } + } +} + /* (RPC Handler) Releases a granted TG. */ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index ecb7f652..e42be987 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -320,6 +320,8 @@ namespace p25 ::lookups::IdenTable m_idenEntry; + std::vector m_activeTG; + RingBuffer m_txImmQueue; RingBuffer m_txQueue; static std::mutex m_queueLock; @@ -378,6 +380,8 @@ namespace p25 uint32_t m_aveRSSI; uint32_t m_rssiCount; + static std::mutex m_activeTGLock; + bool m_notifyCC; bool m_ccDebug; @@ -454,6 +458,12 @@ namespace p25 * @param reply JSON response. */ void RPC_permittedTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Active TGID list from the authoritative CC host. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_activeTG(json::object& req, json::object& reply); /** * @brief (RPC Handler) Releases a granted TG. * @param req JSON request. diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index db6c47be..365893d3 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -35,7 +35,7 @@ using namespace p25::packet; // Constants // --------------------------------------------------------------------------- -const uint32_t VOC_LDU1_COUNT = 3U; +const uint32_t PKT_LDU1_COUNT = 3U; const uint32_t ROAM_LDU1_COUNT = 1U; // --------------------------------------------------------------------------- @@ -59,7 +59,8 @@ void Voice::resetRF() m_rfErrs = 0U; m_rfBits = 1U; m_rfUndecodableLC = 0U; - m_vocLDU1Count = 0U; + m_pktLDU1Count = 0U; + m_grpUpdtCount = 0U; m_roamLDU1Count = 0U; m_inbound = false; @@ -80,7 +81,8 @@ void Voice::resetNet() m_netFrames = 0U; m_netLost = 0U; - m_vocLDU1Count = 0U; + m_pktLDU1Count = 0U; + m_grpUpdtCount = 0U; m_roamLDU1Count = 0U; m_p25->m_networkWatchdog.stop(); @@ -599,7 +601,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfErrs = 0U; m_rfBits = 1U; m_rfUndecodableLC = 0U; - m_vocLDU1Count = 0U; + m_pktLDU1Count = 0U; + m_grpUpdtCount = 0U; m_roamLDU1Count = 0U; m_p25->m_rfTimeout.start(); m_lastDUID = DUID::HDU; @@ -693,15 +696,41 @@ bool Voice::process(uint8_t* data, uint32_t len) m_p25->notifyCC_TouchGrant(m_rfLC.getDstId()); } - // conventional registration or DVRS support? - if ((m_p25->m_enableControl && !m_p25->m_dedicatedControl) || m_p25->m_voiceOnControl) { - // per TIA-102.AABD-B transmit RFSS_STS_BCAST every 3 superframes (e.g. every 3 LDU1s) - m_vocLDU1Count++; - if (m_vocLDU1Count > VOC_LDU1_COUNT) { - m_vocLDU1Count = 0U; + // are we swapping the LC out for the RFSS_STS_BCAST or LC_GROUP_UPDT? + m_pktLDU1Count++; + if (m_pktLDU1Count > PKT_LDU1_COUNT) { + m_pktLDU1Count = 0U; + + // conventional registration or DVRS support? + if ((m_p25->m_enableControl && !m_p25->m_dedicatedControl) || m_p25->m_voiceOnControl) { + // per TIA-102.AABD-B transmit RFSS_STS_BCAST every 3 superframes (e.g. every 3 LDU1s) m_rfLC.setMFId(MFG_STANDARD); m_rfLC.setLCO(LCO::RFSS_STS_BCAST); } + else { + std::lock_guard lock(m_p25->m_activeTGLock); + if (m_p25->m_activeTG.size() > 0) { + if (m_grpUpdtCount > m_p25->m_activeTG.size()) + m_grpUpdtCount = 0U; + + if (m_p25->m_activeTG.size() < 2) { + uint32_t dstId = m_p25->m_activeTG.at(0); + m_rfLC.setMFId(MFG_STANDARD); + m_rfLC.setLCO(LCO::GROUP_UPDT); + m_rfLC.setDstId(dstId); + } + else { + uint32_t dstId = m_p25->m_activeTG.at(m_grpUpdtCount); + uint32_t dstIdB = m_p25->m_activeTG.at(m_grpUpdtCount + 1U); + m_rfLC.setMFId(MFG_STANDARD); + m_rfLC.setLCO(LCO::GROUP_UPDT); + m_rfLC.setDstId(dstId); + m_rfLC.setDstIdB(dstIdB); + + m_grpUpdtCount++; + } + } + } } // generate Sync @@ -988,7 +1017,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfErrs = 0U; m_rfBits = 1U; m_rfUndecodableLC = 0U; - m_vocLDU1Count = 0U; + m_pktLDU1Count = 0U; + m_grpUpdtCount = 0U; m_roamLDU1Count = 0U; m_p25->m_rfTimeout.start(); m_lastDUID = DUID::HDU; @@ -1478,7 +1508,8 @@ Voice::Voice(Control* p25, bool debug, bool verbose) : m_hadVoice(false), m_lastRejectId(0U), m_silenceThreshold(DEFAULT_SILENCE_THRESHOLD), - m_vocLDU1Count(0U), + m_pktLDU1Count(0U), + m_grpUpdtCount(0U), m_roamLDU1Count(0U), m_inbound(false), m_verbose(verbose), @@ -1831,7 +1862,8 @@ void Voice::writeNet_LDU1() m_p25->m_netTimeout.start(); m_netFrames = 0U; m_netLost = 0U; - m_vocLDU1Count = 0U; + m_pktLDU1Count = 0U; + m_grpUpdtCount = 0U; m_roamLDU1Count = 0U; if (!m_p25->m_disableNetworkHDU) { @@ -1911,15 +1943,41 @@ void Voice::writeNet_LDU1() sysId = lc::LC::getSiteData().sysId(); } - // conventional registration or DVRS support? - if ((m_p25->m_enableControl && !m_p25->m_dedicatedControl) || m_p25->m_voiceOnControl) { - // per TIA-102.AABD-B transmit RFSS_STS_BCAST every 3 superframes (e.g. every 3 LDU1s) - m_vocLDU1Count++; - if (m_vocLDU1Count > VOC_LDU1_COUNT) { - m_vocLDU1Count = 0U; + // are we swapping the LC out for the RFSS_STS_BCAST or LC_GROUP_UPDT? + m_pktLDU1Count++; + if (m_pktLDU1Count > PKT_LDU1_COUNT) { + m_pktLDU1Count = 0U; + + // conventional registration or DVRS support? + if ((m_p25->m_enableControl && !m_p25->m_dedicatedControl) || m_p25->m_voiceOnControl) { + // per TIA-102.AABD-B transmit RFSS_STS_BCAST every 3 superframes (e.g. every 3 LDU1s) m_netLC.setMFId(MFG_STANDARD); m_netLC.setLCO(LCO::RFSS_STS_BCAST); } + else { + std::lock_guard lock(m_p25->m_activeTGLock); + if (m_p25->m_activeTG.size() > 0) { + if (m_grpUpdtCount > m_p25->m_activeTG.size()) + m_grpUpdtCount = 0U; + + if (m_p25->m_activeTG.size() < 2) { + uint32_t dstId = m_p25->m_activeTG.at(0); + m_netLC.setMFId(MFG_STANDARD); + m_netLC.setLCO(LCO::GROUP_UPDT); + m_netLC.setDstId(dstId); + } + else { + uint32_t dstId = m_p25->m_activeTG.at(m_grpUpdtCount); + uint32_t dstIdB = m_p25->m_activeTG.at(m_grpUpdtCount + 1U); + m_netLC.setMFId(MFG_STANDARD); + m_netLC.setLCO(LCO::GROUP_UPDT); + m_netLC.setDstId(dstId); + m_netLC.setDstIdB(dstIdB); + + m_grpUpdtCount++; + } + } + } } uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; diff --git a/src/host/p25/packet/Voice.h b/src/host/p25/packet/Voice.h index 818b2be7..89de8eb3 100644 --- a/src/host/p25/packet/Voice.h +++ b/src/host/p25/packet/Voice.h @@ -125,7 +125,8 @@ namespace p25 uint32_t m_silenceThreshold; - uint8_t m_vocLDU1Count; + uint8_t m_pktLDU1Count; + uint8_t m_grpUpdtCount; uint8_t m_roamLDU1Count; bool m_inbound; From 0e9ac9a1a755e6b064f19b3f2b0b02214acf35e9 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 17 Apr 2025 23:27:56 -0400 Subject: [PATCH 02/41] reduce influx and V.24 thread pool sizes; add calculation of how long between when a packet was Rx to when it began proper processing; --- src/fne/network/FNENetwork.cpp | 12 ++++++++++++ src/fne/network/FNENetwork.h | 2 ++ src/fne/network/influxdb/InfluxDB.h | 2 +- src/host/modem/port/specialized/V24UDPPort.cpp | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index dc970804..a91e0be3 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -38,6 +38,8 @@ const uint32_t MAX_HARD_CONN_CAP = 250U; const uint8_t MAX_PEER_LIST_BEFORE_FLUSH = 10U; const uint32_t MAX_RID_LIST_CHUNK = 50U; +const uint64_t PACKET_LATE_TIME = 200U; // 200ms + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -262,6 +264,8 @@ void FNENetwork::processNetwork() req->rtpHeader = rtpHeader; req->fneHeader = fneHeader; + 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, buffer.get(), length); @@ -523,6 +527,14 @@ void FNENetwork::taskNetworkRx(void* arg) uint32_t peerId = req->fneHeader.getPeerId(); 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?) + uint64_t dt = req->pktRxTime + PACKET_LATE_TIME; + if (dt < now) { + std::string peerIdentity = network->resolvePeerIdentity(peerId); + LogWarning(LOG_NET, "PEER %u (%s) packet processing latency >200ms, dt = %u, now = %u", peerId, peerIdentity.c_str(), + dt, now); + } + // update current peer packet sequence and stream ID if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end()) && streamId != 0U) { FNEPeerConnection* connection = network->m_peers[peerId]; diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 393c5fb1..5fe6af4c 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -392,6 +392,8 @@ namespace network frame::RTPFNEHeader fneHeader; //! RTP FNE Header int length = 0U; //! Length of raw data buffer uint8_t* buffer = nullptr; //! Raw data buffer + + uint64_t pktRxTime; //! Packet receive time }; // --------------------------------------------------------------------------- diff --git a/src/fne/network/influxdb/InfluxDB.h b/src/fne/network/influxdb/InfluxDB.h index 2ee2a9a2..d424aa80 100644 --- a/src/fne/network/influxdb/InfluxDB.h +++ b/src/fne/network/influxdb/InfluxDB.h @@ -63,7 +63,7 @@ namespace network // Constants // --------------------------------------------------------------------------- - #define MAX_INFLUXQL_THREAD_CNT 64U + #define MAX_INFLUXQL_THREAD_CNT 16U #define MAX_INFLUXQL_QUEUED_CNT 256U // --------------------------------------------------------------------------- diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index f3808b26..003fda0d 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -31,7 +31,7 @@ using namespace network::udp; // Constants // --------------------------------------------------------------------------- -#define MAX_THREAD_CNT 8U +#define MAX_THREAD_CNT 4U #define RTP_END_OF_CALL_SEQ 65535 const uint32_t BUFFER_LENGTH = 2000U; From 1a3b643e6ea75fb4ac88867d488704520511c376 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 17 Apr 2025 23:38:01 -0400 Subject: [PATCH 03/41] don't use void* for the task routines; --- src/fne/network/DiagNetwork.cpp | 3 +-- src/fne/network/DiagNetwork.h | 4 ++-- src/fne/network/FNENetwork.cpp | 6 ++---- src/fne/network/FNENetwork.h | 8 ++++---- src/host/modem/port/specialized/V24UDPPort.cpp | 6 ++---- src/host/modem/port/specialized/V24UDPPort.h | 6 +++--- 6 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 1343dd5e..f085c3a7 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -155,9 +155,8 @@ void DiagNetwork::close() /* Process a data frames from the network. */ -void DiagNetwork::taskNetworkRx(void* arg) +void DiagNetwork::taskNetworkRx(NetPacketRequest* req) { - NetPacketRequest* req = (NetPacketRequest*)arg; if (req != nullptr) { FNENetwork* network = static_cast(req->obj); if (network == nullptr) { diff --git a/src/fne/network/DiagNetwork.h b/src/fne/network/DiagNetwork.h index c42f50e1..2961ede8 100644 --- a/src/fne/network/DiagNetwork.h +++ b/src/fne/network/DiagNetwork.h @@ -103,9 +103,9 @@ namespace network /** * @brief Entry point to process a given network packet. - * @param arg Instance of the NetPacketRequest structure. + * @param req Instance of the NetPacketRequest structure. */ - static void taskNetworkRx(void* arg); + static void taskNetworkRx(NetPacketRequest* req); }; } // namespace network diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index a91e0be3..e1d704d4 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -503,9 +503,8 @@ void FNENetwork::close() /* Process a data frames from the network. */ -void FNENetwork::taskNetworkRx(void* arg) +void FNENetwork::taskNetworkRx(NetPacketRequest* req) { - NetPacketRequest* req = (NetPacketRequest*)arg; if (req != nullptr) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -1845,9 +1844,8 @@ void FNENetwork::peerACLUpdate(uint32_t peerId) /* Helper to send the ACL lists to the specified peer in a separate thread. */ -void FNENetwork::taskACLUpdate(void* arg) +void FNENetwork::taskACLUpdate(ACLUpdateRequest* req) { - ACLUpdateRequest* req = (ACLUpdateRequest*)arg; if (req != nullptr) { FNENetwork* network = static_cast(req->obj); if (network == nullptr) { diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 5fe6af4c..1b68c69e 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -605,9 +605,9 @@ namespace network /** * @brief Entry point to process a given network packet. - * @param arg Instance of the NetPacketRequest structure. + * @param req Instance of the NetPacketRequest structure. */ - static void taskNetworkRx(void* arg); + static void taskNetworkRx(NetPacketRequest* req); /** * @brief Checks if the passed peer ID is blocked from unit-to-unit traffic. @@ -664,9 +664,9 @@ namespace network void peerACLUpdate(uint32_t peerId); /** * @brief Entry point to send the ACL lists to the specified peer in a separate thread. - * @param arg Instance of the ACLUpdateRequest structure. + * @param req Instance of the ACLUpdateRequest structure. */ - static void taskACLUpdate(void* arg); + static void taskACLUpdate(ACLUpdateRequest* req); /** * @brief Helper to send the list of whitelisted RIDs to the specified peer. diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 003fda0d..563ac0bd 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -389,9 +389,8 @@ void V24UDPPort::processCtrlNetwork() /* Process a data frames from the network. */ -void V24UDPPort::taskCtrlNetworkRx(void* arg) +void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) { - V24PacketRequest* req = (V24PacketRequest*)arg; if (req != nullptr) { V24UDPPort* network = static_cast(req->obj); if (network == nullptr) { @@ -707,9 +706,8 @@ void V24UDPPort::processVCNetwork() /* Process a data frames from the network. */ -void V24UDPPort::taskVCNetworkRx(void* arg) +void V24UDPPort::taskVCNetworkRx(V24PacketRequest* req) { - V24PacketRequest* req = (V24PacketRequest*)arg; if (req != nullptr) { V24UDPPort* network = static_cast(req->obj); if (network == nullptr) { diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 76547d56..76788fca 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -198,7 +198,7 @@ namespace modem * @brief Entry point to process a given network packet. * @param arg Instance of the V24PacketRequest structure. */ - static void taskCtrlNetworkRx(void* arg); + static void taskCtrlNetworkRx(V24PacketRequest* req); /** * @brief Process voice conveyance frames from the network. @@ -207,9 +207,9 @@ namespace modem /** * @brief Entry point to process a given network packet. - * @param arg Instance of the V24PacketRequest structure. + * @param req Instance of the V24PacketRequest structure. */ - static void taskVCNetworkRx(void* arg); + static void taskVCNetworkRx(V24PacketRequest* req); /** * @brief Internal helper to setup the local voice channel port. From 6c6d1e6300fa37cd8d9958ea40f2aecf7d308801 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 10:58:06 -0400 Subject: [PATCH 04/41] rename ThreadPoolCallback to ThreadPoolTask; add some checking around task validness; correct a valgrind issue with RawFrameQueue() write not deleting the buffer before return; --- src/common/ThreadPool.cpp | 17 ++++++++++------- src/common/ThreadPool.h | 16 ++++++++-------- src/common/network/RawFrameQueue.cpp | 1 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/common/ThreadPool.cpp b/src/common/ThreadPool.cpp index a30351e3..780526d1 100644 --- a/src/common/ThreadPool.cpp +++ b/src/common/ThreadPool.cpp @@ -49,7 +49,7 @@ ThreadPool::~ThreadPool() = default; /* Enqueue a thread pool task. */ -bool ThreadPool::enqueue(ThreadPoolCallback* task) +bool ThreadPool::enqueue(ThreadPoolTask* task) { // scope is intentional { @@ -91,7 +91,7 @@ bool ThreadPool::enqueue(ThreadPoolCallback* task) return false; } - m_tasks.emplace(std::unique_ptr(task)); + m_tasks.emplace(std::unique_ptr(task)); } m_cond.notify_one(); @@ -107,8 +107,7 @@ void ThreadPool::start() std::unique_lock lock(m_queueMutex); m_poolState = RUNNING; - uint32_t numWorkingThreads = m_maxWorkerCnt < m_tasks.size() ? m_maxWorkerCnt : m_tasks.size(); - for (uint32_t i = m_workers.size(); i < numWorkingThreads; i++) { + for (uint32_t i = m_workers.size(); i < m_maxWorkerCnt; i++) { thread_t* thread = new thread_t(); thread->obj = this; @@ -195,7 +194,7 @@ void* ThreadPool::worker(void* arg) ::pthread_setname_np(thread->thread, threadName.str().c_str()); #endif // _GNU_SOURCE - ThreadPoolCallback* callback; + ThreadPoolTask* task = nullptr; while (threadPool->m_poolState != STOP) { // scope is intentional { @@ -210,11 +209,15 @@ void* ThreadPool::worker(void* arg) #endif // defined(_WIN32) } - callback = (threadPool->m_tasks.front()).release(); + task = (threadPool->m_tasks.front()).release(); threadPool->m_tasks.pop(); } - callback->run(); + if (task == nullptr) + continue; + + task->run(); + delete task; } #if defined(_WIN32) diff --git a/src/common/ThreadPool.h b/src/common/ThreadPool.h index f9e91ed6..8b9253c0 100644 --- a/src/common/ThreadPool.h +++ b/src/common/ThreadPool.h @@ -33,21 +33,21 @@ * @brief Represents a task run by a thread pool worker thread. * @ingroup threading */ -class HOST_SW_API ThreadPoolCallback { +class HOST_SW_API ThreadPoolTask { public: /** - * @brief Initializes a new instance of the ThreadPoolCallback class. + * @brief Initializes a new instance of the ThreadPoolTask class. * @tparam F * @tparam Args * @param f * @param args */ template - ThreadPoolCallback(F&& f, Args&&... args) + ThreadPoolTask(F&& f, Args&&... args) { task = std::bind(std::forward(f), std::forward(args)...); } - virtual ~ThreadPoolCallback() = default; + virtual ~ThreadPoolTask() = default; /** * @brief Starts the task function. @@ -64,10 +64,10 @@ class HOST_SW_API ThreadPoolCallback { * @tparam Args * @param f * @param args - * @return ThreadPoolCallback* + * @return ThreadPoolTask* */ template -ThreadPoolCallback* new_pooltask(F&& f, Args&&... args) { return new ThreadPoolCallback(f, args...); } +ThreadPoolTask* new_pooltask(F&& f, Args&&... args) { return new ThreadPoolTask(f, args...); } // --------------------------------------------------------------------------- // Class Declaration @@ -95,7 +95,7 @@ class HOST_SW_API ThreadPool { * @param task Task to enqueue. * @returns bool True, if task enqueued otherwise false. */ - bool enqueue(ThreadPoolCallback* task); + bool enqueue(ThreadPoolTask* task); /** * @brief Starts the thread pool. @@ -135,7 +135,7 @@ class HOST_SW_API ThreadPool { PoolState m_poolState; std::vector m_workers; - std::queue> m_tasks; + std::queue> m_tasks; std::mutex m_workerMutex; std::mutex m_queueMutex; diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index 8cc9014b..a0125936 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.cpp @@ -106,6 +106,7 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor ret = false; } + delete[] buffer; return ret; } From a95868cb1404ad0eaf1d47c4486fcbfb91bdbf59 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 11:14:12 -0400 Subject: [PATCH 05/41] valgrind cleanups; --- src/fne/CryptoContainer.cpp | 4 ++++ src/fne/network/RESTAPI.cpp | 7 ++++++- src/host/network/RESTAPI.cpp | 7 ++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/fne/CryptoContainer.cpp b/src/fne/CryptoContainer.cpp index 92b8e282..d6ae6099 100644 --- a/src/fne/CryptoContainer.cpp +++ b/src/fne/CryptoContainer.cpp @@ -451,6 +451,7 @@ bool CryptoContainer::load() ::memcpy(key, keyIv, keyLength); ::memcpy(iv, keyIv + keyLength, EVP_MAX_IV_LENGTH); } + free(salt); // base64Decode allocates memory with malloc() // get inner container encrypted data // bryanb: annoying levels of XML encapsulation... @@ -476,6 +477,7 @@ bool CryptoContainer::load() // decrypt inner container AES aes = AES(AESKeyLength::AES_256); uint8_t* innerContainer = aes.decryptCBC(innerContainerCrypted, innerContainerLen, key, iv); + free(innerContainerCrypted); // base64Decode allocates memory with malloc() /* ** bryanb: this is probably slightly error prone... @@ -553,6 +555,8 @@ bool CryptoContainer::load() } } } + + delete[] innerContainer; } } catch(const std::exception& e) { ::LogError(LOG_HOST, "Error opening EKC: %s", e.what()); diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 7364cc67..4248c6ae 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -545,7 +545,12 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p /* Finalizes a instance of the RESTAPI class. */ -RESTAPI::~RESTAPI() = default; +RESTAPI::~RESTAPI() +{ + if (m_passwordHash != nullptr) { + delete[] m_passwordHash; + } +} /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index 1c922c9d..e68b2339 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -195,7 +195,12 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p /* Finalizes a instance of the RESTAPI class. */ -RESTAPI::~RESTAPI() = default; +RESTAPI::~RESTAPI() +{ + if (m_passwordHash != nullptr) { + delete[] m_passwordHash; + } +} /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ From 57e62b9d69bba512ba5a066d3af60fe9aa31dcb6 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 11:35:56 -0400 Subject: [PATCH 06/41] gate active TG from CC to VC updates at 5s (prevent API spam); --- src/host/p25/Control.cpp | 80 ++++++++++++++++++++++------------------ src/host/p25/Control.h | 1 + 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 6767d924..9692fbb9 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -107,6 +107,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_netTGHang(1000U, 2U), m_networkWatchdog(1000U, 0U, 1500U), m_adjSiteUpdate(1000U, 75U), + m_activeTGUpdate(1000U, 5U), m_ccPacketInterval(1000U, 0U, 10U), m_interval(), m_hangCount(3U * 8U), @@ -1053,44 +1054,53 @@ void Control::clockSiteData(uint32_t ms) m_control->m_adjSiteUpdateTimer.start(); } - // do we have any granted channels? - if (m_affiliations.getGrantedRFChCnt() > 0U) { - uint8_t activeCnt = m_affiliations.getGrantedRFChCnt(); - std::unordered_map grantTable = m_affiliations.grantTable(); + if (!m_activeTGUpdate.isRunning()) { + m_activeTGUpdate.start(); + } - // iterate dynamic channel grant table entries - json::array active = json::array(); - for (auto entry : grantTable) { - uint32_t dstId = entry.first; - active.push_back(json::value((double)dstId)); - } + m_activeTGUpdate.clock(ms); + if (m_activeTGUpdate.isRunning() && m_activeTGUpdate.hasExpired()) { + m_activeTGUpdate.start(); + + // do we have any granted channels? + if (m_affiliations.getGrantedRFChCnt() > 0U) { + uint8_t activeCnt = m_affiliations.getGrantedRFChCnt(); + std::unordered_map grantTable = m_affiliations.grantTable(); + + // iterate dynamic channel grant table entries + json::array active = json::array(); + for (auto entry : grantTable) { + uint32_t dstId = entry.first; + active.push_back(json::value((double)dstId)); + } - std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); - for (auto entry : voiceChs) { - ::lookups::VoiceChData voiceChData = entry.second; - - // callback RPC to transmit active TG list to the voice channels - if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { - json::object req = json::object(); - req["active"].set(active); - - g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { - if (!req["status"].is()) { - ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); - return; - } - - int status = req["status"].get(); - if (status != network::NetRPC::OK) { - ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); - if (req["message"].is()) { - std::string retMsg = req["message"].get(); - ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); + for (auto entry : voiceChs) { + ::lookups::VoiceChData voiceChData = entry.second; + + // callback RPC to transmit active TG list to the voice channels + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { + json::object req = json::object(); + req["active"].set(active); + + g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); + return; } - } - else - ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); - }, voiceChData.address(), voiceChData.port()); + + int status = req["status"].get(); + if (status != network::NetRPC::OK) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); + }, voiceChData.address(), voiceChData.port()); + } } } } diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index e42be987..c7d7cf32 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -348,6 +348,7 @@ namespace p25 Timer m_networkWatchdog; Timer m_adjSiteUpdate; + Timer m_activeTGUpdate; Timer m_ccPacketInterval; From b152747334fc593957a67439731db15e2d3f3123 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 12:53:26 -0400 Subject: [PATCH 07/41] update package version; --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a55f5332..1a15fa78 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,7 +60,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 "R04Axx") +set(CPACK_DEBIAN_PACKAGE_VERSION "R04Hxx") set(CPACK_DEBIAN_PACKAGE_RELEASE "0") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") From 253ca210cac34a4acc9d1ae08f459caea4909327 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 13:01:04 -0400 Subject: [PATCH 08/41] don't attempt to send active TG updates to 0.0.0.0; --- src/host/p25/Control.cpp | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 9692fbb9..57cd2787 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1080,26 +1080,28 @@ void Control::clockSiteData(uint32_t ms) // callback RPC to transmit active TG list to the voice channels if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { - json::object req = json::object(); - req["active"].set(active); - - g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { - if (!req["status"].is()) { - ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); - return; - } - - int status = req["status"].get(); - if (status != network::NetRPC::OK) { - ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); - if (req["message"].is()) { - std::string retMsg = req["message"].get(); - ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + if (voiceChData.address() != "0.0.0.0") { + json::object req = json::object(); + req["active"].set(active); + + g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); + return; } - } - else - ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); - }, voiceChData.address(), voiceChData.port()); + + int status = req["status"].get(); + if (status != network::NetRPC::OK) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); + }, voiceChData.address(), voiceChData.port()); + } } } } From a003bb101c64ea3acd12e69fa9bf0083145ee6ff Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 17:57:38 -0400 Subject: [PATCH 09/41] make notification of active TGs CC -> VC optional; --- configs/config.example.yml | 2 + src/host/network/RPCDefines.h | 1 + src/host/p25/Control.cpp | 136 +++++++++++++++++++++++----------- src/host/p25/Control.h | 7 ++ 4 files changed, 104 insertions(+), 42 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index 4f733792..96e7e68c 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -216,6 +216,8 @@ protocols: interval: 300 # Amount of time to transmit non-dedicated CC broadcasts. (seconds) duration: 1 + # Flag indicating this CC will notify VCs of active TGIDs. + notifyActiveTG: false # Flag to disable TSDU triple-block transmissions and instead transmit single-block TSDUs. disableTSDUMBF: false # Flag to enable optional TIME_DATE_ANNC TSBK during a CC broadcast. diff --git a/src/host/network/RPCDefines.h b/src/host/network/RPCDefines.h index bd8868d9..6d01d099 100644 --- a/src/host/network/RPCDefines.h +++ b/src/host/network/RPCDefines.h @@ -43,6 +43,7 @@ #define RPC_DMR_TSCC_PAYLOAD_ACT 0x0010U #define RPC_ACTIVE_P25_TG 0x0020U +#define RPC_CLEAR_ACTIVE_P25_TG 0x0021U /** @} */ diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 57cd2787..d7b00e34 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -129,6 +129,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_minRSSI(0U), m_aveRSSI(0U), m_rssiCount(0U), + m_ccNotifyActiveTG(false), m_notifyCC(true), m_ccDebug(debug), m_verbose(verbose), @@ -167,6 +168,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q // register RPC handlers g_RPC->registerHandler(RPC_PERMIT_P25_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); g_RPC->registerHandler(RPC_ACTIVE_P25_TG, RPC_FUNC_BIND(Control::RPC_activeTG, this)); + g_RPC->registerHandler(RPC_CLEAR_ACTIVE_P25_TG, RPC_FUNC_BIND(Control::RPC_clearActiveTG, this)); g_RPC->registerHandler(RPC_RELEASE_P25_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this)); g_RPC->registerHandler(RPC_TOUCH_P25_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this)); } @@ -317,6 +319,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_control->m_redundantGrant = control["redundantGrantTransmit"].as(false); m_ccDebug = control["debug"].as(false); + m_ccNotifyActiveTG = control["notifyActiveTG"].as(true); + m_allowExplicitSourceId = p25Protocol["allowExplicitSourceId"].as(true); m_convNetGrantDemand = p25Protocol["convNetGrantDemand"].as(false); @@ -515,6 +519,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Conventional Network Grant Demand: %s", m_convNetGrantDemand ? "yes" : "no"); LogInfo(" Demand Unit Registration for Refused Affiliation: %s", m_demandUnitRegForRefusedAff ? "yes" : "no"); + LogInfo(" Notify VCs of Active TGs: %s", m_ccNotifyActiveTG ? "yes" : "no"); + if (disableUnitRegTimeout) { LogInfo(" Disable Unit Registration Timeout: yes"); } @@ -1054,53 +1060,85 @@ void Control::clockSiteData(uint32_t ms) m_control->m_adjSiteUpdateTimer.start(); } - if (!m_activeTGUpdate.isRunning()) { - m_activeTGUpdate.start(); - } + if (m_ccNotifyActiveTG) { + if (!m_activeTGUpdate.isRunning()) { + m_activeTGUpdate.start(); + } - m_activeTGUpdate.clock(ms); - if (m_activeTGUpdate.isRunning() && m_activeTGUpdate.hasExpired()) { - m_activeTGUpdate.start(); + m_activeTGUpdate.clock(ms); + if (m_activeTGUpdate.isRunning() && m_activeTGUpdate.hasExpired()) { + m_activeTGUpdate.start(); - // do we have any granted channels? - if (m_affiliations.getGrantedRFChCnt() > 0U) { - uint8_t activeCnt = m_affiliations.getGrantedRFChCnt(); - std::unordered_map grantTable = m_affiliations.grantTable(); + // do we have any granted channels? + if (m_affiliations.getGrantedRFChCnt() > 0U) { + uint8_t activeCnt = m_affiliations.getGrantedRFChCnt(); + std::unordered_map grantTable = m_affiliations.grantTable(); - // iterate dynamic channel grant table entries - json::array active = json::array(); - for (auto entry : grantTable) { - uint32_t dstId = entry.first; - active.push_back(json::value((double)dstId)); - } + // iterate dynamic channel grant table entries + json::array active = json::array(); + for (auto entry : grantTable) { + uint32_t dstId = entry.first; + active.push_back(json::value((double)dstId)); + } - std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); - for (auto entry : voiceChs) { - ::lookups::VoiceChData voiceChData = entry.second; - - // callback RPC to transmit active TG list to the voice channels - if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { - if (voiceChData.address() != "0.0.0.0") { - json::object req = json::object(); - req["active"].set(active); - - g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { - if (!req["status"].is()) { - ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); - return; - } - - int status = req["status"].get(); - if (status != network::NetRPC::OK) { - ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); - if (req["message"].is()) { - std::string retMsg = req["message"].get(); - ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); + for (auto entry : voiceChs) { + ::lookups::VoiceChData voiceChData = entry.second; + + // callback RPC to transmit active TG list to the voice channels + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { + if (voiceChData.address() != "0.0.0.0") { + json::object req = json::object(); + req["active"].set(active); + + g_RPC->req(RPC_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); + return; } - } - else - ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); - }, voiceChData.address(), voiceChData.port()); + + int status = req["status"].get(); + if (status != network::NetRPC::OK) { + ::LogError(LOG_P25, "failed to send active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); + }, voiceChData.address(), voiceChData.port()); + } + } + } + } else { + std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); + for (auto entry : voiceChs) { + ::lookups::VoiceChData voiceChData = entry.second; + + // callback RPC to transmit active TG list to the voice channels + if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { + if (voiceChData.address() != "0.0.0.0") { + json::object req = json::object(); + + g_RPC->req(RPC_CLEAR_ACTIVE_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to send clear active TG list to VC %s:%u, invalid RPC response", voiceChData.address().c_str(), voiceChData.port()); + return; + } + + int status = req["status"].get(); + if (status != network::NetRPC::OK) { + ::LogError(LOG_P25, "failed to send clear active TG list to VC %s:%u", voiceChData.address().c_str(), voiceChData.port()); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "VC %s:%u, clear active TG update", voiceChData.address().c_str(), voiceChData.port()); + }, voiceChData.address(), voiceChData.port()); + } } } } @@ -1905,6 +1943,20 @@ void Control::RPC_activeTG(json::object& req, json::object& reply) m_activeTG.push_back(entry.get()); } } + + ::LogMessage(LOG_P25, "active TG update, activeCnt = %u", m_activeTG.size()); +} + +/* (RPC Handler) Clear active TGID list from the authoritative CC host. */ + +void Control::RPC_clearActiveTG(json::object& req, json::object& reply) +{ + g_RPC->defaultResponse(reply, "OK", network::NetRPC::OK); + + if (m_activeTG.size() > 0) { + std::lock_guard lock(m_activeTGLock); + m_activeTG.clear(); + } } /* (RPC Handler) Releases a granted TG. */ diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index c7d7cf32..79d6dd2b 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -382,6 +382,7 @@ namespace p25 uint32_t m_rssiCount; static std::mutex m_activeTGLock; + bool m_ccNotifyActiveTG; bool m_notifyCC; @@ -465,6 +466,12 @@ namespace p25 * @param reply JSON response. */ void RPC_activeTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Clear active TGID list from the authoritative CC host. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_clearActiveTG(json::object& req, json::object& reply); /** * @brief (RPC Handler) Releases a granted TG. * @param req JSON request. From fa4fd415d3df67304490a1763ea290f1384f7573 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 18 Apr 2025 21:49:56 -0400 Subject: [PATCH 10/41] correct ThreadPool issue on Win32; split UDP PCM audio processing into its own thread; implement user control of inter-audio frame delay and jitter buffer (if using inter-audio frame delay); --- configs/bridge-config.example.yml | 11 ++ src/bridge/HostBridge.cpp | 318 +++++++++++++++++++++--------- src/bridge/HostBridge.h | 30 +++ src/common/ThreadPool.cpp | 8 + 4 files changed, 273 insertions(+), 94 deletions(-) diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 1a918baa..36fc829a 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -80,6 +80,17 @@ network: # Flag indicating UDP audio should follow the USRP format. udpUsrp: false + # Delay in-between UDP audio frames (in ms). + # (Some applications will send RTP/PCM audio too fast, requiring a delay in-between packets to + # be added for appropriate IMBE audio pacing. For most cases a 20ms delay will properly pace + # audio frames. If set to 0, no frame pacing or jitter buffer will be applied and audio will be + # encoded as fast as it is received.) + udpInterFrameDelay: 0 + # Jitter Buffer Length (in ms). + # (This is only applied if utilizing inter frame delay, otherwise packet timing is assumed to be + # properly handled by the source.) + udpJitter: 200 + # Flag indicating the UDP audio will be padded with silence during hang time before end of call. udpHangSilence: true diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index b26c62bc..d8a66b97 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -298,7 +298,10 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpUseULaw(false), m_udpRTPFrames(false), m_udpUsrp(false), + m_udpInterFrameDelay(0U), + m_udpJitter(200U), m_udpSilenceDuringHang(true), + m_lastUdpFrameTime(0U), m_tekAlgoId(p25::defines::ALGO_UNENCRYPT), m_tekKeyId(0U), m_requestedTek(false), @@ -336,6 +339,7 @@ HostBridge::HostBridge(const std::string& confFile) : 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_udpPackets(), m_decoder(nullptr), m_encoder(nullptr), m_mdcDecoder(nullptr), @@ -646,6 +650,11 @@ int HostBridge::run() } } + if (m_udpAudio) { + if (!Thread::runAsThread(this, threadUDPAudioProcess)) + return EXIT_FAILURE; + } + ::LogInfoEx(LOG_HOST, "Bridge is up and running"); m_running = true; @@ -1068,6 +1077,8 @@ bool HostBridge::createNetwork() m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); m_udpUseULaw = networkConf["udpUseULaw"].as(false); m_udpUsrp = networkConf["udpUsrp"].as(false); + m_udpInterFrameDelay = (uint8_t)networkConf["udpInterFrameDelay"].as(0U); + m_udpJitter = (uint16_t)networkConf["udpJitter"].as(200U); m_udpSilenceDuringHang = networkConf["udpHangSilence"].as(true); if (m_udpUsrp) { @@ -1220,6 +1231,8 @@ bool HostBridge::createNetwork() LogInfo(" UDP Audio RTP Framed: %s", m_udpRTPFrames ? "yes" : "no"); } LogInfo(" UDP Audio USRP: %s", m_udpUsrp ? "yes" : "no"); + LogInfo(" UDP Inter Audio Frame Delay: %ums", m_udpInterFrameDelay); + LogInfo(" UDP Jitter: %ums", m_udpJitter); LogInfo(" UDP Silence During Hangtime: %s", m_udpSilenceDuringHang ? "yes" : "no"); } @@ -1354,110 +1367,46 @@ void HostBridge::processUDPAudio() // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); - m_udpDstId = m_dstId; - - if (m_udpMetadata) { - if (m_overrideSrcIdFromUDP) { - uint32_t udpSrcId = __GET_UINT32(buffer, pcmLength + 8U); - - if (udpSrcId != 0U) { - // if the UDP source ID now doesn't match the current call ID, reset call states - if (m_resetCallForSourceIdChange && (udpSrcId != m_udpSrcId)) { - callEnd(m_udpSrcId, m_dstId); - m_udpDstId = m_dstId; - } - - m_udpSrcId = udpSrcId; - } else { - if (m_udpSrcId == 0U) { - m_udpSrcId = m_srcId; - } - } - } else { - m_udpSrcId = m_srcId; - } - } else { - m_udpSrcId = m_srcId; - } + NetPacketRequest* req = new NetPacketRequest(); + req->pcm = new uint8_t[pcmLength]; + ::memset(req->pcm, 0x00U, pcmLength); + ::memcpy(req->pcm, pcm, pcmLength); - std::lock_guard lock(m_audioMutex); + req->pcmLength = pcmLength; - int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; - if (m_udpUseULaw) { - if (m_trace) - Utils::dump(1U, "HostBridge()::processUDPAudio() uLaw Audio", pcm, MBE_SAMPLES_LENGTH * 2U); + req->srcId = m_srcId; + req->dstId = m_dstId; - for (uint32_t pcmIdx = 0; pcmIdx < MBE_SAMPLES_LENGTH; pcmIdx++) { - samples[smpIdx] = decodeMuLaw(pcm[pcmIdx]); - smpIdx++; - } + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + if (m_udpInterFrameDelay > 0U) { + uint64_t pktTime = 0U; - int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); - pcmIdx += 2; + // if this is our first message, timestamp is just now + the jitter buffer offset in ms + if (m_lastUdpFrameTime == 0U) { + pktTime = now + m_udpJitter; } - } - else { - for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) { - samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); - smpIdx++; - } - } - - m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH); - - m_trafficFromUDP = true; - - // force start a call if one isn't already in progress - if (!m_audioDetect && !m_callInProgress) { - m_audioDetect = true; - if (m_txStreamId == 0U) { - m_txStreamId = 1U; // prevent further false starts -- this isn't the right way to handle this... - LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, m_udpSrcId, m_udpDstId); - if (m_grantDemand) { - switch (m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(m_udpDstId); - lc.setSrcId(m_udpSrcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = 0x80U; - m_network->writeP25TDU(lc, lsd, controlByte); - } - break; - } + else { + // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above + if ((int64_t)(now - m_lastUdpFrameTime) > m_udpJitter) { + pktTime = now + m_udpJitter; + } + // otherwise, we time out messages as required by the message type + else { + pktTime = m_lastUdpFrameTime + 20U; } } - m_udpCallClock.stop(); - m_udpDropTime.stop(); - - if (!m_udpDropTime.isRunning()) - m_udpDropTime.start(); - m_udpCallClock.start(); + req->pktRxTime = pktTime; + m_lastUdpFrameTime = pktTime; + } else { + req->pktRxTime = now; } - // If audio detection is active and no call is in progress, encode and transmit the audio - if (m_audioDetect && !m_callInProgress) { - m_udpDropTime.start(); - m_udpCallClock.start(); - - switch (m_txMode) { - case TX_MODE_DMR: - encodeDMRAudioFrame(pcm, m_udpSrcId); - break; - case TX_MODE_P25: - encodeP25AudioFrame(pcm, m_udpSrcId); - break; - } + if (m_udpMetadata) { + req->srcId = __GET_UINT32(buffer, pcmLength + 8U); } + + m_udpPackets.push_back(req); } } @@ -2845,6 +2794,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) m_udpSrcId = 0; m_udpDstId = 0; + m_lastUdpFrameTime = 0U; m_trafficFromUDP = false; m_dmrSeqNo = 0U; @@ -2894,7 +2844,7 @@ void* HostBridge::threadAudioProcess(void* arg) ::pthread_detach(th->thread); #endif // defined(_WIN32) - std::string threadName("bridge:audio-process"); + std::string threadName("bridge:local-audio"); HostBridge* bridge = static_cast(th->obj); if (bridge == nullptr) { g_killed = true; @@ -3034,6 +2984,186 @@ void* HostBridge::threadAudioProcess(void* arg) return nullptr; } +/* Entry point to UDP audio processing thread. */ + +void* HostBridge::threadUDPAudioProcess(void* arg) +{ + 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("bridge:udp-audio"); + HostBridge* bridge = static_cast(th->obj); + if (bridge == 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 (!bridge->m_running) { + Thread::sleep(1U); + continue; + } + + if (bridge->m_udpPackets.empty()) + Thread::sleep(1U); + else { + NetPacketRequest* req = bridge->m_udpPackets[0]; + + if (req != nullptr) { + if (bridge->m_udpInterFrameDelay > 0U) { + int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + if (req->pktRxTime > now) { + Thread::sleep(1U); + continue; + } + } + + bridge->m_udpPackets.pop_front(); + + bridge->m_udpDstId = bridge->m_dstId; + + if (bridge->m_udpMetadata) { + if (bridge->m_overrideSrcIdFromUDP) { + if (req->srcId != 0U) { + // if the UDP source ID now doesn't match the current call ID, reset call states + if (bridge->m_resetCallForSourceIdChange && (req->srcId != bridge->m_udpSrcId)) { + bridge->callEnd(bridge->m_udpSrcId, bridge->m_dstId); + bridge->m_udpDstId = bridge->m_dstId; + } + + bridge->m_udpSrcId = req->srcId; + } + else { + if (bridge->m_udpSrcId == 0U) { + bridge->m_udpSrcId = req->srcId; + } + } + } + else { + bridge->m_udpSrcId = bridge->m_srcId; + } + } + else { + bridge->m_udpSrcId = bridge->m_srcId; + } + + std::lock_guard lock(m_audioMutex); + + int smpIdx = 0; + short samples[MBE_SAMPLES_LENGTH]; + if (bridge->m_udpUseULaw) { + if (bridge->m_trace) + Utils::dump(1U, "HostBridge()::processUDPAudio() uLaw Audio", req->pcm, MBE_SAMPLES_LENGTH * 2U); + + for (uint32_t pcmIdx = 0; pcmIdx < MBE_SAMPLES_LENGTH; pcmIdx++) { + samples[smpIdx] = decodeMuLaw(req->pcm[pcmIdx]); + smpIdx++; + } + + int pcmIdx = 0; + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + req->pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + req->pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } + } + else { + for (uint32_t pcmIdx = 0; pcmIdx < req->pcmLength; pcmIdx += 2) { + samples[smpIdx] = (short)((req->pcm[pcmIdx + 1] << 8) + req->pcm[pcmIdx + 0]); + smpIdx++; + } + } + + bridge->m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH); + bridge->m_trafficFromUDP = true; + + // force start a call if one isn't already in progress + if (!bridge->m_audioDetect && !bridge->m_callInProgress) { + bridge->m_audioDetect = true; + if (bridge->m_txStreamId == 0U) { + bridge->m_txStreamId = 1U; // prevent further false starts -- this isn't the right way to handle this... + 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; + } + } + } + + bridge->m_udpCallClock.stop(); + bridge->m_udpDropTime.stop(); + + if (!bridge->m_udpDropTime.isRunning()) + bridge->m_udpDropTime.start(); + bridge->m_udpCallClock.start(); + } + + // If audio detection is active and no call is in progress, encode and transmit the audio + if (bridge->m_audioDetect && !bridge->m_callInProgress) { + bridge->m_udpDropTime.start(); + bridge->m_udpCallClock.start(); + + switch (bridge->m_txMode) { + case TX_MODE_DMR: + bridge->encodeDMRAudioFrame(req->pcm, bridge->m_udpSrcId); + break; + case TX_MODE_P25: + bridge->encodeP25AudioFrame(req->pcm, bridge->m_udpSrcId); + break; + } + } + + delete[] req->pcm; + delete req; + + if (bridge->m_udpInterFrameDelay > 0U) { + Thread::sleep(bridge->m_udpInterFrameDelay); + } + else { + Thread::sleep(1U); + } + } else { + bridge->m_udpPackets.pop_front(); + Thread::sleep(1U); + } + } + } + + LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; +} + /* Entry point to network processing thread. */ void* HostBridge::threadNetworkProcess(void* arg) diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index bb35a522..1583d27e 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -36,6 +36,7 @@ #include #include #include +#include #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN @@ -119,6 +120,24 @@ uint8_t encodeMuLaw(short pcm); */ short decodeMuLaw(uint8_t ulaw); +// --------------------------------------------------------------------------- +// Structure Declaration +// --------------------------------------------------------------------------- + +/** + * @brief Represents the data required for a network packet handler thread. + * @ingroup bridge + */ +struct NetPacketRequest { + uint32_t srcId; + uint32_t dstId; + + int pcmLength = 0U; //! Length of PCM data buffer + uint8_t* pcm = nullptr; //! Raw PCM buffer + + uint64_t pktRxTime; //! Packet receive time +}; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -166,7 +185,10 @@ class HOST_SW_API HostBridge { bool m_udpUseULaw; bool m_udpRTPFrames; bool m_udpUsrp; + uint8_t m_udpInterFrameDelay; + uint16_t m_udpJitter; bool m_udpSilenceDuringHang; + uint64_t m_lastUdpFrameTime; uint8_t m_tekAlgoId; uint16_t m_tekKeyId; @@ -219,6 +241,7 @@ class HOST_SW_API HostBridge { RingBuffer m_inputAudio; RingBuffer m_outputAudio; + std::deque m_udpPackets; vocoder::MBEDecoder* m_decoder; vocoder::MBEEncoder* m_encoder; @@ -503,6 +526,13 @@ class HOST_SW_API HostBridge { */ static void* threadAudioProcess(void* arg); + /** + * @brief Entry point to UDP audio processing thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadUDPAudioProcess(void* arg); + /** * @brief Entry point to network processing thread. * @param arg Instance of the thread_t structure. diff --git a/src/common/ThreadPool.cpp b/src/common/ThreadPool.cpp index 780526d1..92ea7473 100644 --- a/src/common/ThreadPool.cpp +++ b/src/common/ThreadPool.cpp @@ -178,14 +178,22 @@ void* ThreadPool::worker(void* arg) thread_t* thread = (thread_t*)arg; if (thread == nullptr) { LogError(LOG_HOST, "Fatal error starting thread pool worker! No thread!"); +#if defined(_WIN32) + return 0UL; +#else return nullptr; +#endif // defined(_WIN32) } ThreadPool* threadPool = (ThreadPool*)thread->obj; if (threadPool == nullptr) { LogError(LOG_HOST, "Fatal error starting thread pool worker! No thread pool owner!"); delete thread; +#if defined(_WIN32) + return 0UL; +#else return nullptr; +#endif // defined(_WIN32) } std::stringstream threadName; From e802b49f2b0aa2123d71814898043a5dfed8e073 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 08:33:56 -0400 Subject: [PATCH 11/41] lock queue; --- src/bridge/HostBridge.cpp | 17 +++++++++++++---- src/bridge/HostBridge.h | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index d8a66b97..795541ca 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -81,6 +81,7 @@ const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; std::mutex HostBridge::m_audioMutex; std::mutex HostBridge::m_networkMutex; +std::mutex HostBridge::m_udpAudioMutex; // --------------------------------------------------------------------------- // Global Functions @@ -1406,7 +1407,11 @@ void HostBridge::processUDPAudio() req->srcId = __GET_UINT32(buffer, pcmLength + 8U); } - m_udpPackets.push_back(req); + // scope is intentional + { + std::lock_guard lock(m_udpAudioMutex); + m_udpPackets.push_back(req); + } } } @@ -3033,8 +3038,12 @@ void* HostBridge::threadUDPAudioProcess(void* arg) continue; } } - - bridge->m_udpPackets.pop_front(); + + // scope is intentional + { + std::lock_guard lock(m_udpAudioMutex); + bridge->m_udpPackets.pop_front(); + } bridge->m_udpDstId = bridge->m_dstId; @@ -3069,7 +3078,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) short samples[MBE_SAMPLES_LENGTH]; if (bridge->m_udpUseULaw) { if (bridge->m_trace) - Utils::dump(1U, "HostBridge()::processUDPAudio() uLaw Audio", req->pcm, MBE_SAMPLES_LENGTH * 2U); + Utils::dump(1U, "HostBridge()::threadUDPAudioProcess() uLaw Audio", req->pcm, MBE_SAMPLES_LENGTH * 2U); for (uint32_t pcmIdx = 0; pcmIdx < MBE_SAMPLES_LENGTH; pcmIdx++) { samples[smpIdx] = decodeMuLaw(req->pcm[pcmIdx]); diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 1583d27e..65b758e6 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -287,6 +287,7 @@ class HOST_SW_API HostBridge { static std::mutex m_audioMutex; static std::mutex m_networkMutex; + static std::mutex m_udpAudioMutex; #if defined(_WIN32) void* m_decoderState; From fb77e64dc4630197e6c01b0dd5222872069e4114 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 14:49:02 -0400 Subject: [PATCH 12/41] add custom classes for STL containers that support mutex locking for thread safe operation; modify ChannelLookup and AffiliationLookup to use concurrent containers; modify FNE to use concurrent containers for internal lists; --- src/bridge/HostBridge.h | 6 +- src/common/CMakeLists.txt | 1 + src/common/concurrent/deque.h | 392 +++++++++++++++ src/common/concurrent/map.h | 409 +++++++++++++++ src/common/concurrent/unordered_map.h | 409 +++++++++++++++ src/common/concurrent/vector.h | 473 ++++++++++++++++++ src/common/lookups/AffiliationLookup.cpp | 25 +- src/common/lookups/AffiliationLookup.h | 32 +- src/common/lookups/ChannelLookup.cpp | 12 - src/common/lookups/ChannelLookup.h | 17 +- src/fne/network/FNENetwork.cpp | 6 - src/fne/network/FNENetwork.h | 10 +- src/host/Host.cpp | 18 +- src/host/dmr/lookups/DMRAffiliationLookup.cpp | 11 +- src/host/dmr/lookups/DMRAffiliationLookup.h | 5 +- src/host/network/RESTAPI.cpp | 52 +- src/host/nxdn/Control.cpp | 50 +- src/host/nxdn/Control.h | 4 +- src/host/nxdn/packet/ControlSignaling.cpp | 52 +- src/host/nxdn/packet/Voice.cpp | 2 +- src/host/p25/Control.cpp | 66 +-- src/host/p25/Control.h | 4 +- src/host/p25/lookups/P25AffiliationLookup.cpp | 4 +- src/host/p25/lookups/P25AffiliationLookup.h | 3 +- src/host/p25/packet/ControlSignaling.cpp | 114 ++--- src/host/p25/packet/Voice.cpp | 52 +- 26 files changed, 1936 insertions(+), 293 deletions(-) create mode 100644 src/common/concurrent/deque.h create mode 100644 src/common/concurrent/map.h create mode 100644 src/common/concurrent/unordered_map.h create mode 100644 src/common/concurrent/vector.h diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 65b758e6..d94e4c84 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -17,6 +17,7 @@ #define __HOST_BRIDGE_H__ #include "Defines.h" +#include "common/concurrent/deque.h" #include "common/dmr/data/EmbeddedData.h" #include "common/dmr/lc/LC.h" #include "common/dmr/lc/PrivacyLC.h" @@ -33,10 +34,7 @@ #include "network/PeerNetwork.h" #include -#include -#include #include -#include #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN @@ -241,7 +239,7 @@ class HOST_SW_API HostBridge { RingBuffer m_inputAudio; RingBuffer m_outputAudio; - std::deque m_udpPackets; + concurrent::deque m_udpPackets; vocoder::MBEDecoder* m_decoder; vocoder::MBEEncoder* m_encoder; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index de41287e..d4b39179 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -87,6 +87,7 @@ file(GLOB common_INCLUDE "src/common/nxdn/lc/rcch/*.h" # Core + "src/common/concurrent/*.h" "src/common/edac/*.h" "src/common/edac/rs/*.h" "src/common/lookups/*.h" diff --git a/src/common/concurrent/deque.h b/src/common/concurrent/deque.h new file mode 100644 index 00000000..f06d4757 --- /dev/null +++ b/src/common/concurrent/deque.h @@ -0,0 +1,392 @@ +// 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 deque.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_DEQUE_H__) +#define __CONCURRENCY_DEQUE_H__ + +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Thread-safe std::deque. + * @ingroup concurrency + */ + template + class deque + { + using __std = std::deque; + public: + using iterator = typename __std::iterator; + using const_iterator = typename __std::const_iterator; + + /** + * @brief Initializes a new instance of the deque class. + */ + deque() : + m_mutex(), + m_locked(false), + m_deque() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the deque class. + * @param size Initial size of the deque. + */ + deque(size_t size) : + m_mutex(), + m_locked(false), + m_deque(size) + { + /* stub */ + } + /** + * @brief Finalizes a instance of the deque class. + */ + virtual ~deque() + { + m_deque.clear(); + } + + /** + * @brief Deque assignment operator. + * @param other A deque of identical element and allocator types. + */ + deque& operator=(const deque& other) + { + __lock(); + m_deque = other.m_deque; + __unlock(); + return *this; + } + /** + * @brief Deque assignment operator. + * @param other A deque of identical element and allocator types. + */ + deque& operator=(const std::deque& other) + { + __lock(); + m_deque = other; + __unlock(); + return *this; + } + /** + * @brief Deque assignment operator. + * @param other A deque of identical element and allocator types. + */ + deque& operator=(deque& other) + { + __lock(); + m_deque = other.m_deque; + __unlock(); + return *this; + } + /** + * @brief Deque assignment operator. + * @param other A deque of identical element and allocator types. + */ + deque& operator=(std::deque& other) + { + __lock(); + m_deque = other; + __unlock(); + return *this; + } + + /** + * @brief Assigns a given value to a deque. + * @param size Number of elements to be assigned. + * @param value Value to be assigned. + */ + void assign(size_t size, const T& value) + { + __lock(); + m_deque.assign(size, value); + __unlock(); + } + + /** + * @brief Gets the total number of elements in the deque. + * @returns size_t Total number of elements in the deque. + */ + size_t size() const + { + __spinlock(); + return m_deque.size(); + } + /** + * @brief Resizes the deque to contain the specified number of elements. + * @param size Number of elements the deque should contain. + */ + void resize(size_t size) + { + __lock(); + m_deque.resize(size); + __unlock(); + } + + /** + * @brief Returns the total number of elements that the deque can + * hold before needing to allocate more memory. + * @returns size_t + */ + size_t capacity() const + { + __spinlock(); + return m_deque.capacity(); + } + + /** + * @brief Checks if the deque is empty. + * @returns bool True if the deque is empty, false otherwise. + */ + bool empty() const + { + __spinlock(); + return m_deque.empty(); + } + + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns T& Element at the specified index. + */ + T& operator[](size_t index) + { + __spinlock(); + return m_deque[index]; + } + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns const T& Element at the specified index. + */ + const T& operator[](size_t index) const + { + __spinlock(); + return m_deque[index]; + } + + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns T& Element at the specified index. + */ + T& at(size_t index) + { + __spinlock(); + return m_deque.at(index); + } + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns const T& Element at the specified index. + */ + const T& at(size_t index) const + { + __spinlock(); + return m_deque.at(index); + } + + /** + * @brief Adds an element to the end of the deque. + * @param value Value to be added. + */ + void push_back(const T& value) + { + __lock(); + m_deque.push_back(value); + __unlock(); + } + /** + * @brief Adds an element to the end of the deque. + * @param value Value to be added. + */ + void push_front(const T& value) + { + __lock(); + m_deque.push_front(value); + __unlock(); + } + /** + * @brief Removes the last element of the deque. + */ + void pop_back() + { + __lock(); + m_deque.pop_back(); + __unlock(); + } + /** + * @brief Removes the first element of the deque. + */ + void pop_front() + { + __lock(); + m_deque.pop_front(); + __unlock(); + } + + /** + * @brief Gets the first element of the deque. + * @returns T& First element of the deque. + */ + T& front() + { + __spinlock(); + return m_deque.front(); + } + /** + * @brief Gets the first element of the deque. + * @returns const T& First element of the deque. + */ + const T& front() const + { + __spinlock(); + return m_deque.front(); + } + /** + * @brief Gets the last element of the deque. + * @returns T& Last element of the deque. + */ + T& back() + { + __spinlock(); + return m_deque.back(); + } + /** + * @brief Gets the last element of the deque. + * @returns const T& Last element of the deque. + */ + const T& back() const + { + __spinlock(); + return m_deque.back(); + } + + /** + * @brief Removes the element at the specified index. + * @param index Index of the element to remove. + */ + void erase(size_t index) + { + __lock(); + m_deque.erase(m_deque.begin() + index); + __unlock(); + } + /** + * @brief Removes the element at the specified iterator. + * @param position Iterator of the element to remove. + */ + void erase(const_iterator position) + { + __lock(); + m_deque.erase(position); + __unlock(); + } + /** + * @brief Removes the elements in the specified range. + * @param first Iterator of the first element to remove. + * @param last Iterator of the last element to remove. + */ + void erase(const_iterator first, const_iterator last) + { + __lock(); + m_deque.erase(first, last); + __unlock(); + } + + /** + * @brief Swaps data with another deque. + * @param other A deque of the same element and allocator types. + */ + void swap(deque& other) + { + __lock(); + m_deque.swap(other.m_deque); + __unlock(); + } + + /** + * @brief Clears the deque. + */ + void clear() + { + __lock(); + m_deque.clear(); + __unlock(); + } + + /** + * @brief Gets the underlying deque. + * @returns std::deque& Underlying deque. + */ + std::deque& get() + { + __spinlock(); + return m_deque; + } + /** + * @brief Gets the underlying deque. + * @returns const std::deque& Underlying deque. + */ + const std::deque& get() const + { + __spinlock(); + return m_deque; + } + + private: + mutable std::mutex m_mutex; //! Mutex used for hard locking. + mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + std::deque m_deque; + + /** + * @brief Locks the deque. + */ + inline void __lock() const + { + m_mutex.lock(); + m_locked = true; + } + /** + * @brief Unlocks the deque. + */ + inline void __unlock() const + { + m_mutex.unlock(); + m_locked = false; + } + /** + * @brief Spins until the deque is unlocked. + */ + inline void __spinlock() const + { + if (m_locked) { + while (m_locked) + Thread::sleep(1U); + } + } + }; +} // namespace concurrent + +#endif // __CONCURRENCY_DEQUE_H__ diff --git a/src/common/concurrent/map.h b/src/common/concurrent/map.h new file mode 100644 index 00000000..5721f3e6 --- /dev/null +++ b/src/common/concurrent/map.h @@ -0,0 +1,409 @@ +// 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 map.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_MAP_H__) +#define __CONCURRENCY_MAP_H__ + +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Thread-safe std::map. + * @ingroup concurrency + */ + template + class map + { + using __std = std::map; + public: + using iterator = typename __std::iterator; + using const_iterator = typename __std::const_iterator; + + /** + * @brief Initializes a new instance of the map class. + */ + map() : + m_mutex(), + m_locked(false), + m_map() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the map class. + * @param size Initial size of the map. + */ + map(size_t size) : + m_mutex(), + m_locked(false), + m_map(size) + { + /* stub */ + } + /** + * @brief Finalizes a instance of the map class. + */ + virtual ~map() + { + m_map.clear(); + } + + /** + * @brief map assignment operator. + * @param other A map of identical element and allocator types. + */ + map& operator=(const map& other) + { + __lock(); + m_map = other.m_map; + __unlock(); + return *this; + } + /** + * @brief Map assignment operator. + * @param other A map of identical element and allocator types. + */ + map& operator=(const std::map& other) + { + __lock(); + m_map = other; + __unlock(); + return *this; + } + /** + * @brief Map assignment operator. + * @param other A map of identical element and allocator types. + */ + map& operator=(map& other) + { + __lock(); + m_map = other.m_map; + __unlock(); + return *this; + } + /** + * @brief Map assignment operator. + * @param other A map of identical element and allocator types. + */ + map& operator=(std::map& other) + { + __lock(); + m_map = other; + __unlock(); + return *this; + } + + /** + * @brief Assigns a given value to a map. + * @param size Number of elements to be assigned. + * @param value Value to be assigned. + */ + void assign(size_t size, const T& value) + { + __lock(); + m_map.assign(size, value); + __unlock(); + } + + /** + * @brief Returns a read/write iterator that points to the first + * element in the map. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator begin() + { + __spinlock(); + return m_map.begin(); + } + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator begin() const + { + __spinlock(); + return m_map.begin(); + } + /** + * @brief Returns a read/write iterator that points one past the last + * element in the map. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator end() + { + __spinlock(); + return m_map.end(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator end() const + { + __spinlock(); + return m_map.end(); + } + + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cbegin() const + { + __spinlock(); + return m_map.cbegin(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cend() const + { + __spinlock(); + return m_map.cend(); + } + + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns T& Element at the specified key. + */ + T& operator[](const Key& key) + { + __spinlock(); + return m_map[key]; + } + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns const T& Element at the specified key. + */ + const T& operator[](const Key& key) const + { + __spinlock(); + return m_map[key]; + } + + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns T& Element at the specified key. + */ + T& at(const Key& key) + { + __spinlock(); + return m_map.at(key); + } + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns const T& Element at the specified key. + */ + const T& at(const Key& key) const + { + __spinlock(); + return m_map.at(key); + } + + /** + * @brief Gets the total number of elements in the map. + * @returns size_t Total number of elements in the map. + */ + size_t size() const + { + __spinlock(); + return m_map.size(); + } + + /** + * @brief Checks if the map is empty. + * @returns bool True if the map is empty, false otherwise. + */ + bool empty() const + { + __spinlock(); + return m_map.empty(); + } + + /** + * @brief Checks if the map contains the specified key. + * @param key Key to check. + * @returns bool True if the map contains the specified key, false otherwise. + */ + bool contains(const Key& key) const + { + __spinlock(); + return m_map.contains(key); + } + + /** + * @brief Inserts a new element into the map. + * @param key Key of the element to insert. + * @param value Value of the element to insert. + */ + void insert(const Key& key, const T& value) + { + __lock(); + m_map.insert({key, value}); + __unlock(); + } + + /** + * @brief Removes the element at the specified key. + * @param key Key of the element to remove. + */ + void erase(const Key& key) + { + __lock(); + m_map.erase(key); + __unlock(); + } + /** + * @brief Removes the element at the specified iterator. + * @param position Iterator of the element to remove. + */ + void erase(const_iterator position) + { + __lock(); + m_map.erase(position); + __unlock(); + } + /** + * @brief Removes the elements in the specified range. + * @param first Iterator of the first element to remove. + * @param last Iterator of the last element to remove. + */ + void erase(const_iterator first, const_iterator last) + { + __lock(); + m_map.erase(first, last); + __unlock(); + } + + /** + * @brief Clears the map. + */ + void clear() + { + __lock(); + m_map.clear(); + __unlock(); + } + + /** + * @brief Tries to locate an element in an map. + * @param key Key to be located. + * @return iterator Iterator pointing to sought-after element, or end() if not + * found. + */ + iterator find(const Key& key) + { + __spinlock(); + return m_map.find(key); + } + /** + * @brief Tries to locate an element in an map. + * @param key Key to be located. + * @return const_iterator Iterator pointing to sought-after element, or end() if not + * found. + */ + const_iterator find(const Key& key) const + { + __spinlock(); + return m_map.find(key); + } + + /** + * @brief Finds the number of elements. + * @param key Key to count. + * @return size_t Number of elements with specified key. + */ + size_t count(const Key& key) const + { + __spinlock(); + return m_map.count(key); + } + + /** + * @brief Gets the underlying map. + * @returns std::map& Underlying map. + */ + std::map& get() + { + __spinlock(); + return m_map; + } + /** + * @brief Gets the underlying map. + * @returns const std::map& Underlying map. + */ + const std::map& get() const + { + __spinlock(); + return m_map; + } + + private: + mutable std::mutex m_mutex; //! Mutex used for hard locking. + mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + std::map m_map; + + /** + * @brief Locks the map. + */ + inline void __lock() const + { + m_mutex.lock(); + m_locked = true; + } + /** + * @brief Unlocks the map. + */ + inline void __unlock() const + { + m_mutex.unlock(); + m_locked = false; + } + /** + * @brief Spins until the map is unlocked. + */ + inline void __spinlock() const + { + if (m_locked) { + while (m_locked) + Thread::sleep(1U); + } + } + }; +} // namespace concurrent + +#endif // __CONCURRENCY_MAP_H__ diff --git a/src/common/concurrent/unordered_map.h b/src/common/concurrent/unordered_map.h new file mode 100644 index 00000000..a2ef7eff --- /dev/null +++ b/src/common/concurrent/unordered_map.h @@ -0,0 +1,409 @@ +// 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 unordered_map.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_UNORDERED_MAP_H__) +#define __CONCURRENCY_UNORDERED_MAP_H__ + +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Thread-safe std::unordered_map. + * @ingroup concurrency + */ + template + class unordered_map + { + using __std = std::unordered_map; + public: + using iterator = typename __std::iterator; + using const_iterator = typename __std::const_iterator; + + /** + * @brief Initializes a new instance of the unordered_map class. + */ + unordered_map() : + m_mutex(), + m_locked(false), + m_map() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the unordered_map class. + * @param size Initial size of the unordered_map. + */ + unordered_map(size_t size) : + m_mutex(), + m_locked(false), + m_map(size) + { + /* stub */ + } + /** + * @brief Finalizes a instance of the unordered_map class. + */ + virtual ~unordered_map() + { + m_map.clear(); + } + + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + unordered_map& operator=(const unordered_map& other) + { + __lock(); + m_map = other.m_map; + __unlock(); + return *this; + } + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + unordered_map& operator=(const std::unordered_map& other) + { + __lock(); + m_map = other; + __unlock(); + return *this; + } + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + unordered_map& operator=(unordered_map& other) + { + __lock(); + m_map = other.m_map; + __unlock(); + return *this; + } + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + unordered_map& operator=(std::unordered_map& other) + { + __lock(); + m_map = other; + __unlock(); + return *this; + } + + /** + * @brief Assigns a given value to a unordered_map. + * @param size Number of elements to be assigned. + * @param value Value to be assigned. + */ + void assign(size_t size, const T& value) + { + __lock(); + m_map.assign(size, value); + __unlock(); + } + + /** + * @brief Returns a read/write iterator that points to the first + * element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator begin() + { + __spinlock(); + return m_map.begin(); + } + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator begin() const + { + __spinlock(); + return m_map.begin(); + } + /** + * @brief Returns a read/write iterator that points one past the last + * element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator end() + { + __spinlock(); + return m_map.end(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator end() const + { + __spinlock(); + return m_map.end(); + } + + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cbegin() const + { + __spinlock(); + return m_map.cbegin(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cend() const + { + __spinlock(); + return m_map.cend(); + } + + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns T& Element at the specified key. + */ + T& operator[](const Key& key) + { + __spinlock(); + return m_map[key]; + } + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns const T& Element at the specified key. + */ + const T& operator[](const Key& key) const + { + __spinlock(); + return m_map[key]; + } + + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns T& Element at the specified key. + */ + T& at(const Key& key) + { + __spinlock(); + return m_map.at(key); + } + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns const T& Element at the specified key. + */ + const T& at(const Key& key) const + { + __spinlock(); + return m_map.at(key); + } + + /** + * @brief Gets the total number of elements in the unordered_map. + * @returns size_t Total number of elements in the unordered_map. + */ + size_t size() const + { + __spinlock(); + return m_map.size(); + } + + /** + * @brief Checks if the unordered_map is empty. + * @returns bool True if the unordered_map is empty, false otherwise. + */ + bool empty() const + { + __spinlock(); + return m_map.empty(); + } + + /** + * @brief Checks if the unordered_map contains the specified key. + * @param key Key to check. + * @returns bool True if the unordered_map contains the specified key, false otherwise. + */ + bool contains(const Key& key) const + { + __spinlock(); + return m_map.contains(key); + } + + /** + * @brief Inserts a new element into the unordered_map. + * @param key Key of the element to insert. + * @param value Value of the element to insert. + */ + void insert(const Key& key, const T& value) + { + __lock(); + m_map.insert({key, value}); + __unlock(); + } + + /** + * @brief Removes the element at the specified key. + * @param key Key of the element to remove. + */ + void erase(const Key& key) + { + __lock(); + m_map.erase(key); + __unlock(); + } + /** + * @brief Removes the element at the specified iterator. + * @param position Iterator of the element to remove. + */ + void erase(const_iterator position) + { + __lock(); + m_map.erase(position); + __unlock(); + } + /** + * @brief Removes the elements in the specified range. + * @param first Iterator of the first element to remove. + * @param last Iterator of the last element to remove. + */ + void erase(const_iterator first, const_iterator last) + { + __lock(); + m_map.erase(first, last); + __unlock(); + } + + /** + * @brief Clears the unordered_map. + */ + void clear() + { + __lock(); + m_map.clear(); + __unlock(); + } + + /** + * @brief Tries to locate an element in an unordered_map. + * @param key Key to be located. + * @return iterator Iterator pointing to sought-after element, or end() if not + * found. + */ + iterator find(const Key& key) + { + __spinlock(); + return m_map.find(key); + } + /** + * @brief Tries to locate an element in an unordered_map. + * @param key Key to be located. + * @return const_iterator Iterator pointing to sought-after element, or end() if not + * found. + */ + const_iterator find(const Key& key) const + { + __spinlock(); + return m_map.find(key); + } + + /** + * @brief Finds the number of elements. + * @param key Key to count. + * @return size_t Number of elements with specified key. + */ + size_t count(const Key& key) const + { + __spinlock(); + return m_map.count(key); + } + + /** + * @brief Gets the underlying unordered_map. + * @returns std::unordered_map& Underlying unordered_map. + */ + std::unordered_map& get() + { + __spinlock(); + return m_map; + } + /** + * @brief Gets the underlying unordered_map. + * @returns const std::unordered_map& Underlying unordered_map. + */ + const std::unordered_map& get() const + { + __spinlock(); + return m_map; + } + + private: + mutable std::mutex m_mutex; //! Mutex used for hard locking. + mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + std::unordered_map m_map; + + /** + * @brief Locks the unordered_map. + */ + inline void __lock() const + { + m_mutex.lock(); + m_locked = true; + } + /** + * @brief Unlocks the unordered_map. + */ + inline void __unlock() const + { + m_mutex.unlock(); + m_locked = false; + } + /** + * @brief Spins until the unordered_map is unlocked. + */ + inline void __spinlock() const + { + if (m_locked) { + while (m_locked) + Thread::sleep(1U); + } + } + }; +} // namespace concurrent + +#endif // __CONCURRENCY_UNORDERED_MAP_H__ diff --git a/src/common/concurrent/vector.h b/src/common/concurrent/vector.h new file mode 100644 index 00000000..c477b254 --- /dev/null +++ b/src/common/concurrent/vector.h @@ -0,0 +1,473 @@ +// 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 concurrency Concurrent STL. + * @brief Defines and implements concurrency support using standard STL containers. + * @ingroup common + * + * @file vector.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_VECTOR_H__) +#define __CONCURRENCY_VECTOR_H__ + +#include + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Thread-safe std::vector. + * @ingroup concurrency + */ + template + class vector + { + using __std = std::vector; + public: + using iterator = typename __std::iterator; + using const_iterator = typename __std::const_iterator; + + /** + * @brief Initializes a new instance of the vector class. + */ + vector() : + m_mutex(), + m_locked(false), + m_vector() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the vector class. + * @param size Initial size of the vector. + */ + vector(size_t size) : + m_mutex(), + m_locked(false), + m_vector(size) + { + /* stub */ + } + /** + * @brief Finalizes a instance of the vector class. + */ + virtual ~vector() + { + m_vector.clear(); + } + + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + vector& operator=(const vector& other) + { + __lock(); + m_vector = other.m_vector; + __unlock(); + return *this; + } + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + vector& operator=(const std::vector& other) + { + __lock(); + m_vector = other; + __unlock(); + return *this; + } + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + vector& operator=(vector& other) + { + __lock(); + m_vector = other.m_vector; + __unlock(); + return *this; + } + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + vector& operator=(std::vector& other) + { + __lock(); + m_vector = other; + __unlock(); + return *this; + } + + /** + * @brief Assigns a given value to a %vector. + * @param size Number of elements to be assigned. + * @param value Value to be assigned. + */ + void assign(size_t size, const T& value) + { + __lock(); + m_vector.assign(size, value); + __unlock(); + } + + /** + * @brief Returns a read/write iterator that points to the first + * element in the vector. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator begin() + { + __spinlock(); + return m_vector.begin(); + } + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator begin() const + { + __spinlock(); + return m_vector.begin(); + } + /** + * @brief Returns a read/write iterator that points one past the last + * element in the vector. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator end() + { + __spinlock(); + return m_vector.end(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator end() const + { + __spinlock(); + return m_vector.end(); + } + + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cbegin() const + { + __spinlock(); + return m_vector.cbegin(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cend() const + { + __spinlock(); + return m_vector.cend(); + } + + /** + * @brief Gets the number of elements in the vector. + * @returns size_t Number of elements in the vector. + */ + size_t size() const + { + __spinlock(); + return m_vector.size(); + } + /** + * @brief Resizes the %vector to the specified number of elements. + * @param size Number of elements the %vector should contain. + */ + void resize(size_t size) + { + __lock(); + m_vector.resize(size); + __unlock(); + } + + /** + * @brief Returns the total number of elements that the %vector can + * hold before needing to allocate more memory. + * @returns size_t + */ + size_t capacity() const + { + __spinlock(); + return m_vector.capacity(); + } + + /** + * @brief Checks if the vector is empty. + * @returns bool True if the vector is empty, false otherwise. + */ + bool empty() const + { + __spinlock(); + return m_vector.empty(); + } + + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns T& Element at the specified index. + */ + T& operator[](size_t index) + { + __spinlock(); + return m_vector[index]; + } + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns const T& Element at the specified index. + */ + const T& operator[](size_t index) const + { + __spinlock(); + return m_vector[index]; + } + + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns T& Element at the specified index. + */ + T& at(size_t index) + { + __spinlock(); + return m_vector.at(index); + } + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns const T& Element at the specified index. + */ + const T& at(size_t index) const + { + __spinlock(); + return m_vector.at(index); + } + + /** + * @brief Gets the first element of the vector. + * @returns T& First element of the vector. + */ + T& front() + { + __spinlock(); + return m_vector.front(); + } + /** + * @brief Gets the first element of the vector. + * @returns const T& First element of the vector. + */ + const T& front() const + { + __spinlock(); + return m_vector.front(); + } + + /** + * @brief Gets the last element of the vector. + * @returns T& Last element of the vector. + */ + T& back() + { + __spinlock(); + return m_vector.back(); + } + /** + * @brief Gets the last element of the vector. + * @returns const T& Last element of the vector. + */ + const T& back() const + { + __spinlock(); + return m_vector.back(); + } + + /** + * @brief Adds an element to the end of the vector. + * @param value Value to add. + */ + void push_back(const T& value) + { + __lock(); + m_vector.push_back(value); + __unlock(); + } + /** + * @brief Adds an element to the end of the vector. + * @param value Value to add. + */ + void push_back(T&& value) + { + __lock(); + m_vector.push_back(std::move(value)); + __unlock(); + } + + /** + * @brief Removes last element. + */ + void pop_back() + { + __lock(); + m_vector.pop_back(); + __unlock(); + } + + /** + * @brief Inserts given value into vector before specified iterator. + * @param position A const_iterator into the vector. + * @param value Data to be inserted. + * @return iterator An iterator that points to the inserted data. + */ + iterator insert(iterator position, const T& value) + { + __lock(); + auto it = m_vector.insert(position, value); + __unlock(); + return it; + } + + /** + * @brief Removes the element at the specified index. + * @param index Index of the element to remove. + */ + void erase(size_t index) + { + __lock(); + m_vector.erase(m_vector.begin() + index); + __unlock(); + } + /** + * @brief Removes the element at the specified iterator. + * @param position Iterator of the element to remove. + */ + void erase(const_iterator position) + { + __lock(); + m_vector.erase(position); + __unlock(); + } + /** + * @brief Removes the elements in the specified range. + * @param first Iterator of the first element to remove. + * @param last Iterator of the last element to remove. + */ + void erase(const_iterator first, const_iterator last) + { + __lock(); + m_vector.erase(first, last); + __unlock(); + } + + /** + * @brief Swaps data with another vector. + * @param other A vector of the same element and allocator types. + */ + void swap(vector& other) + { + __lock(); + m_vector.swap(other.m_vector); + __unlock(); + } + + /** + * @brief Clears the vector. + */ + void clear() + { + __lock(); + m_vector.clear(); + __unlock(); + } + + /** + * @brief Gets the underlying vector. + * @returns std::vector& Underlying vector. + */ + std::vector& get() + { + __spinlock(); + return m_vector; + } + /** + * @brief Gets the underlying vector. + * @returns const std::vector& Underlying vector. + */ + const std::vector& get() const + { + __spinlock(); + return m_vector; + } + + private: + mutable std::mutex m_mutex; //! Mutex used for hard locking. + mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + std::vector m_vector; + + /** + * @brief Lock the vector. + */ + inline void __lock() const + { + m_mutex.lock(); + m_locked = true; + } + + /** + * @brief Unlock the vector. + */ + inline void __unlock() const + { + m_mutex.unlock(); + m_locked = false; + } + + /** + * @brief Spins until the vector is unlocked. + */ + inline void __spinlock() const + { + if (m_locked) { + while (m_locked) + Thread::sleep(1U); + } + } + }; +} // namespace concurrent + +#endif // __CONCURRENCY_VECTOR_H__ diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 099c8365..579bcb6e 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -20,12 +20,6 @@ using namespace lookups; const uint32_t UNIT_REG_TIMEOUT = 43200U; // 12 hours -// --------------------------------------------------------------------------- -// Static Class Members -// --------------------------------------------------------------------------- - -std::mutex AffiliationLookup::m_mutex; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -299,8 +293,6 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi return false; } - std::lock_guard lock(m_mutex); - if (!m_chLookup->isRFChAvailable()) { return false; } @@ -336,8 +328,6 @@ void AffiliationLookup::touchGrant(uint32_t dstId) return; } - std::lock_guard lock(m_mutex); - if (isGranted(dstId)) { m_grantTimers[dstId].start(); } @@ -345,15 +335,12 @@ void AffiliationLookup::touchGrant(uint32_t dstId) /* Helper to release the channel grant for the destination ID. */ -bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock) +bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) { if (dstId == 0U && !releaseAll) { return false; } - if (!noLock) - m_mutex.lock(); - // are we trying to release all grants? if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); @@ -369,8 +356,6 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLoc releaseGrant(dstId, false); } - if (!noLock) - m_mutex.unlock(); return true; } @@ -401,13 +386,9 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLoc m_grantTimers[dstId].stop(); - if (!noLock) - m_mutex.unlock(); return true; } - if (!noLock) - m_mutex.unlock(); return false; } @@ -549,8 +530,6 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId) void AffiliationLookup::clock(uint32_t ms) { - std::lock_guard lock(m_mutex); - // clock all the grant timers std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { @@ -564,7 +543,7 @@ void AffiliationLookup::clock(uint32_t ms) // release grants that have timed out for (uint32_t dstId : gntsToRel) { - releaseGrant(dstId, false, true); + releaseGrant(dstId, false); } if (!m_disableUnitRegTimeout) { diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index 2c7ac586..23893914 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -21,15 +21,14 @@ #define __AFFILIATION_LOOKUP_H__ #include "common/Defines.h" +#include "common/concurrent/vector.h" +#include "common/concurrent/unordered_map.h" #include "common/lookups/ChannelLookup.h" #include "common/Timer.h" #include -#include #include -#include #include -#include namespace lookups { @@ -66,7 +65,7 @@ namespace lookups * @brief Gets the unit registration table. * @returns std::vector Unit Registration Table. */ - std::vector unitRegTable() const { return m_unitRegTable; } + std::vector unitRegTable() const { return m_unitRegTable.get(); } /** * @brief Helper to register a source ID. * @param srcId Source Radio ID. @@ -118,7 +117,7 @@ namespace lookups * @brief Gets the group affiliation table. * @returns std::unordered_map Group Affiliation Table. */ - std::unordered_map grpAffTable() const { return m_grpAffTable; } + std::unordered_map grpAffTable() const { return m_grpAffTable.get(); } /** * @brief Helper to group affiliate a source ID. * @param srcId Source Radio ID. @@ -163,7 +162,7 @@ namespace lookups * @brief Gets the grant table. * @returns std::unordered_map Channel Grant Table. */ - std::unordered_map grantTable() const { return m_grantChTable; } + std::unordered_map grantTable() const { return m_grantChTable.get(); } /** * @brief Helper to grant a channel. * @param dstId Destination Address. @@ -183,10 +182,9 @@ namespace lookups * @brief Helper to release the channel grant for the destination ID. * @param dstId Destination Address. * @param releaseAll Flag indicating all channel grants should be released. - * @param noLock Flag indicating no mutex lock operation should be performed while releasing. * @returns bool True, if channel grant was released, otherwise false. */ - virtual bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false); + virtual bool releaseGrant(uint32_t dstId, bool releaseAll); /** * @brief Helper to determine if the channel number is busy. * @param chNo Channel Number. @@ -279,15 +277,15 @@ namespace lookups protected: uint8_t m_rfGrantChCnt; - std::vector m_unitRegTable; - std::unordered_map m_unitRegTimers; - std::unordered_map m_grpAffTable; + concurrent::vector m_unitRegTable; + concurrent::unordered_map m_unitRegTimers; + concurrent::unordered_map m_grpAffTable; - std::unordered_map m_grantChTable; - std::unordered_map m_grantSrcIdTable; - std::unordered_map m_uuGrantedTable; - std::unordered_map m_netGrantedTable; - std::unordered_map m_grantTimers; + concurrent::unordered_map m_grantChTable; + concurrent::unordered_map m_grantSrcIdTable; + concurrent::unordered_map m_uuGrantedTable; + concurrent::unordered_map m_netGrantedTable; + concurrent::unordered_map m_grantTimers; // chNo dstId slot std::function m_releaseGrant; @@ -300,8 +298,6 @@ namespace lookups bool m_disableUnitRegTimeout; bool m_verbose; - - static std::mutex m_mutex; }; } // namespace lookups diff --git a/src/common/lookups/ChannelLookup.cpp b/src/common/lookups/ChannelLookup.cpp index fed733be..5f081e23 100644 --- a/src/common/lookups/ChannelLookup.cpp +++ b/src/common/lookups/ChannelLookup.cpp @@ -12,12 +12,6 @@ using namespace lookups; -// --------------------------------------------------------------------------- -// Static Class Members -// --------------------------------------------------------------------------- - -std::mutex ChannelLookup::m_mutex; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -39,8 +33,6 @@ ChannelLookup::~ChannelLookup() = default; VoiceChData ChannelLookup::getRFChData(uint32_t chNo) const { - std::lock_guard lock(m_mutex); - if (chNo == 0U) { return VoiceChData(); } @@ -59,8 +51,6 @@ VoiceChData ChannelLookup::getRFChData(uint32_t chNo) const bool ChannelLookup::addRFCh(uint32_t chNo, bool force) { - std::lock_guard lock(m_mutex); - if (chNo == 0U) { return false; } @@ -83,8 +73,6 @@ bool ChannelLookup::addRFCh(uint32_t chNo, bool force) bool ChannelLookup::removeRFCh(uint32_t chNo) { - std::lock_guard lock(m_mutex); - if (chNo == 0U) { return false; } diff --git a/src/common/lookups/ChannelLookup.h b/src/common/lookups/ChannelLookup.h index b9207061..c5ce28b5 100644 --- a/src/common/lookups/ChannelLookup.h +++ b/src/common/lookups/ChannelLookup.h @@ -24,14 +24,11 @@ #define __CHANNEL_LOOKUP_H__ #include "common/Defines.h" +#include "common/concurrent/vector.h" +#include "common/concurrent/unordered_map.h" #include "common/Timer.h" #include -#include -#include -#include -#include -#include namespace lookups { @@ -163,7 +160,7 @@ namespace lookups * @brief Gets the RF channel data table. * @returns std::unordered_map RF channel data table. */ - std::unordered_map rfChDataTable() const { return m_rfChDataTable; } + std::unordered_map rfChDataTable() const { return m_rfChDataTable.get(); } /** * @brief Helper to set RF channel data. * @param chData RF Channel data table. @@ -196,7 +193,7 @@ namespace lookups * @brief Gets the RF channels table. * @returns std::vector RF channel table. */ - std::vector rfChTable() const { return m_rfChTable; } + std::vector rfChTable() const { return m_rfChTable.get(); } /** * @brief Helper to add a RF channel. * @param chNo Channel Number. @@ -218,10 +215,8 @@ namespace lookups /** @} */ private: - std::vector m_rfChTable; - std::unordered_map m_rfChDataTable; - - static std::mutex m_mutex; + concurrent::vector m_rfChTable; + concurrent::unordered_map m_rfChDataTable; }; } // namespace lookups diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index e1d704d4..e956e43c 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -44,7 +44,6 @@ const uint64_t PACKET_LATE_TIME = 200U; // 200ms // Static Class Members // --------------------------------------------------------------------------- -std::mutex FNENetwork::m_peerMutex; std::timed_mutex FNENetwork::m_keyQueueMutex; // --------------------------------------------------------------------------- @@ -1652,7 +1651,6 @@ void FNENetwork::eraseStreamPktSeq(uint32_t peerId, uint32_t streamId) if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { - std::lock_guard lock(m_peerMutex); connection->eraseStreamPktSeq(streamId); } } @@ -1664,7 +1662,6 @@ void FNENetwork::createPeerAffiliations(uint32_t peerId, std::string peerName) { erasePeerAffiliations(peerId); - std::lock_guard lock(m_peerMutex); lookups::ChannelLookup* chLookup = new lookups::ChannelLookup(); m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, chLookup, m_verbose); m_peerAffiliations[peerId]->setDisableUnitRegTimeout(true); // FNE doesn't allow unit registration timeouts (notification must come from the peers) @@ -1674,7 +1671,6 @@ void FNENetwork::createPeerAffiliations(uint32_t peerId, std::string peerName) bool FNENetwork::erasePeerAffiliations(uint32_t peerId) { - std::lock_guard lock(m_peerMutex); auto it = std::find_if(m_peerAffiliations.begin(), m_peerAffiliations.end(), [&](PeerAffiliationMapPair x) { return x.first == peerId; }); if (it != m_peerAffiliations.end()) { lookups::AffiliationLookup* aff = m_peerAffiliations[peerId]; @@ -1696,7 +1692,6 @@ bool FNENetwork::erasePeerAffiliations(uint32_t peerId) bool FNENetwork::erasePeer(uint32_t peerId) { - std::lock_guard lock(m_peerMutex); { auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { @@ -1793,7 +1788,6 @@ bool FNENetwork::resetPeer(uint32_t peerId) std::string FNENetwork::resolvePeerIdentity(uint32_t peerId) { - std::lock_guard lock(m_peerMutex); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { if (it->second != nullptr) { diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 1b68c69e..2e57857b 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -25,6 +25,7 @@ #define __FNE_NETWORK_H__ #include "fne/Defines.h" +#include "common/concurrent/unordered_map.h" #include "common/network/BaseNetwork.h" #include "common/network/json/json.h" #include "common/lookups/AffiliationLookup.h" @@ -552,13 +553,12 @@ namespace network NET_CONN_STATUS m_status; - static std::mutex m_peerMutex; typedef std::pair PeerMapPair; - std::unordered_map m_peers; - std::unordered_map m_peerLinkPeers; + concurrent::unordered_map m_peers; + concurrent::unordered_map m_peerLinkPeers; typedef std::pair PeerAffiliationMapPair; - std::unordered_map m_peerAffiliations; - std::unordered_map> m_ccPeerMap; + concurrent::unordered_map m_peerAffiliations; + concurrent::unordered_map> m_ccPeerMap; static std::timed_mutex m_keyQueueMutex; std::unordered_map m_peerLinkKeyQueue; diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 81b80d73..c067d834 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -1355,7 +1355,7 @@ json::object Host::getStatus() uint32_t dstId = 0U, srcId = 0U; // fetch affiliations from DMR if we're a DMR CC - if (m_dmrTSCCData) { + if (m_dmrTSCCData && m_dmr->affiliations() != nullptr) { if (m_dmr->affiliations()->isChBusy(chNo)) { chData["tx"].set(_true); } else { @@ -1368,29 +1368,29 @@ json::object Host::getStatus() } // fetch affiliations from P25 if we're a P25 CC - if (m_p25CCData) { - if (m_p25->affiliations().isChBusy(chNo)) { + if (m_p25CCData && m_p25->affiliations() != nullptr) { + if (m_p25->affiliations()->isChBusy(chNo)) { chData["tx"].set(_true); } else { chData["tx"].set(_false); } - dstId = m_p25->affiliations().getGrantedDstByCh(chNo); + dstId = m_p25->affiliations()->getGrantedDstByCh(chNo); if (dstId > 0U) - srcId = m_p25->affiliations().getGrantedSrcId(dstId); + srcId = m_p25->affiliations()->getGrantedSrcId(dstId); } // fetch affiliations from NXDN if we're a NXDN CC - if (m_nxdnCCData) { - if (m_nxdn->affiliations().isChBusy(chNo)) { + if (m_nxdnCCData && m_nxdn->affiliations() != nullptr) { + if (m_nxdn->affiliations()->isChBusy(chNo)) { chData["tx"].set(_true); } else { chData["tx"].set(_false); } - dstId = m_nxdn->affiliations().getGrantedDstByCh(chNo); + dstId = m_nxdn->affiliations()->getGrantedDstByCh(chNo); if (dstId > 0U) - srcId = m_nxdn->affiliations().getGrantedSrcId(dstId); + srcId = m_nxdn->affiliations()->getGrantedSrcId(dstId); } chData["lastDstId"].set(dstId); diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.cpp b/src/host/dmr/lookups/DMRAffiliationLookup.cpp index 8a486922..6a03b862 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.cpp +++ b/src/host/dmr/lookups/DMRAffiliationLookup.cpp @@ -86,15 +86,12 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s /* Helper to release the channel grant for the destination ID. */ -bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock) +bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) { if (dstId == 0U && !releaseAll) { return false; } - if (!noLock) - m_mutex.lock(); - // are we trying to release all grants? if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); @@ -110,8 +107,6 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool no releaseGrant(dstId, false); } - if (!noLock) - m_mutex.unlock(); return true; } @@ -145,13 +140,9 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool no m_grantTimers[dstId].stop(); - if (!noLock) - m_mutex.unlock(); return true; } - if (!noLock) - m_mutex.unlock(); return false; } diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.h b/src/host/dmr/lookups/DMRAffiliationLookup.h index cf5587cc..3a8e475f 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.h +++ b/src/host/dmr/lookups/DMRAffiliationLookup.h @@ -73,10 +73,9 @@ namespace dmr * @brief Helper to release the channel grant for the destination ID. * @param dstId Destination Address. * @param releaseAll Flag indicating all channel grants should be released. - * @param noLock Flag indicating no mutex lock operation should be performed while releasing. * @returns bool True, if channel grant was released, otherwise false. */ - bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false) override; + bool releaseGrant(uint32_t dstId, bool releaseAll) override; /** * @brief Helper to determine if the channel number is busy. * @param chNo Channel Number. @@ -111,7 +110,7 @@ namespace dmr uint8_t getAvailableSlotForChannel(uint32_t chNo) const; protected: - std::unordered_map> m_grantChSlotTable; + concurrent::unordered_map> m_grantChSlotTable; uint32_t m_tsccChNo; uint8_t m_tsccSlot; diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index e68b2339..acccc157 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -939,11 +939,13 @@ void RESTAPI::restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload& } if (m_p25 != nullptr) { - m_p25->affiliations().releaseGrant(0, true); + if (m_p25->affiliations() != nullptr) + m_p25->affiliations()->releaseGrant(0, true); } if (m_nxdn != nullptr) { - m_nxdn->affiliations().releaseGrant(0, true); + if (m_nxdn->affiliations() != nullptr) + m_nxdn->affiliations()->releaseGrant(0, true); } } @@ -962,11 +964,13 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& re } if (m_p25 != nullptr) { - m_p25->affiliations().clearGroupAff(0, true); + if (m_p25->affiliations() != nullptr) + m_p25->affiliations()->clearGroupAff(0, true); } if (m_nxdn != nullptr) { - m_nxdn->affiliations().clearGroupAff(0, true); + if (m_nxdn->affiliations() != nullptr) + m_nxdn->affiliations()->clearGroupAff(0, true); } } @@ -1625,17 +1629,19 @@ void RESTAPI::restAPI_GetP25AffList(const HTTPPayload& request, HTTPPayload& rep setResponseDefaultStatus(response); json::array affs = json::array(); - std::unordered_map affTable = m_p25->affiliations().grpAffTable(); - if (affTable.size() > 0) { - for (auto entry : affTable) { - uint32_t srcId = entry.first; - uint32_t grpId = entry.second; + if (m_p25->affiliations() != nullptr) { + std::unordered_map affTable = m_p25->affiliations()->grpAffTable(); + if (affTable.size() > 0) { + for (auto entry : affTable) { + uint32_t srcId = entry.first; + uint32_t grpId = entry.second; - json::object aff = json::object(); - aff["srcId"].set(srcId); - aff["grpId"].set(grpId); + json::object aff = json::object(); + aff["srcId"].set(srcId); + aff["grpId"].set(grpId); - affs.push_back(json::value(aff)); + affs.push_back(json::value(aff)); + } } } @@ -1792,17 +1798,19 @@ void RESTAPI::restAPI_GetNXDNAffList(const HTTPPayload& request, HTTPPayload& re setResponseDefaultStatus(response); json::array affs = json::array(); - std::unordered_map affTable = m_nxdn->affiliations().grpAffTable(); - if (affTable.size() > 0) { - for (auto entry : affTable) { - uint32_t srcId = entry.first; - uint32_t grpId = entry.second; + if (m_nxdn->affiliations() != nullptr) { + std::unordered_map affTable = m_nxdn->affiliations()->grpAffTable(); + if (affTable.size() > 0) { + for (auto entry : affTable) { + uint32_t srcId = entry.first; + uint32_t grpId = entry.second; - json::object aff = json::object(); - aff["srcId"].set(srcId); - aff["grpId"].set(grpId); + json::object aff = json::object(); + aff["srcId"].set(srcId); + aff["grpId"].set(grpId); - affs.push_back(json::value(aff)); + affs.push_back(json::value(aff)); + } } } diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index d49e025a..62f78d62 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -80,7 +80,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), - m_affiliations("NXDN Affiliations", chLookup, verbose), + m_affiliations(nullptr), m_controlChData(), m_idenEntry(), m_txImmQueue(queueSize, "NXDN Imm Frame"), @@ -125,6 +125,8 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q assert(idenTable != nullptr); assert(rssiMapper != nullptr); + m_affiliations = new lookups::AffiliationLookup("NXDN Affiliations", chLookup, verbose); + m_interval.start(); acl::AccessControl::init(m_ridLookup, m_tidLookup); @@ -146,6 +148,10 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q Control::~Control() { + if (m_affiliations != nullptr) { + delete m_affiliations; + } + if (m_voice != nullptr) { delete m_voice; } @@ -262,13 +268,13 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_controlChData = controlChData; bool disableUnitRegTimeout = nxdnProtocol["disableUnitRegTimeout"].as(false); - m_affiliations.setDisableUnitRegTimeout(disableUnitRegTimeout); + m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); // set the grant release callback - m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { + m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel if (m_authoritative && m_supervisor) { - ::lookups::VoiceChData voiceChData = m_affiliations.rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_siteData.channelNo()) { json::object req = json::object(); @@ -284,7 +290,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw }); // set the unit deregistration callback - m_affiliations.setUnitDeregCallback([=](uint32_t srcId, bool automatic) { + m_affiliations->setUnitDeregCallback([=](uint32_t srcId, bool automatic) { if (m_network != nullptr) m_network->announceUnitDeregistration(srcId); }); @@ -609,8 +615,8 @@ void Control::clock() if (m_adjSiteUpdate.isRunning() && m_adjSiteUpdate.hasExpired()) { if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { if (m_network != nullptr) { - if (m_affiliations.grpAffSize() > 0) { - auto affs = m_affiliations.grpAffTable(); + if (m_affiliations->grpAffSize() > 0) { + auto affs = m_affiliations->grpAffTable(); m_network->announceAffiliationUpdate(affs); } } @@ -692,7 +698,7 @@ void Control::clock() m_networkWatchdog.stop(); if (m_enableControl) { - m_affiliations.releaseGrant(m_netLC.getDstId(), false); + m_affiliations->releaseGrant(m_netLC.getDstId(), false); } if (m_dedicatedControl) { @@ -726,7 +732,7 @@ void Control::clockSiteData(uint32_t ms) { if (m_enableControl) { // clock all the grant timers - m_affiliations.clock(ms); + m_affiliations->clock(ms); } } @@ -1000,7 +1006,7 @@ void Control::processFrameLoss() LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); - m_affiliations.releaseGrant(m_rfLC.getDstId(), false); + m_affiliations->releaseGrant(m_rfLC.getDstId(), false); if (m_notifyCC) { notifyCC_ReleaseGrant(m_rfLC.getDstId()); } @@ -1027,8 +1033,8 @@ void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId) { if (m_rfState == RS_RF_AUDIO && m_rfLC.getDstId() == dstId) { LogWarning(LOG_P25, "network requested in-call traffic reject, dstId = %u", dstId); - if (m_affiliations.isGranted(dstId)) { - m_affiliations.releaseGrant(dstId, false); + if (m_affiliations->isGranted(dstId)) { + m_affiliations->releaseGrant(dstId, false); if (!m_enableControl) { notifyCC_ReleaseGrant(dstId); } @@ -1185,16 +1191,16 @@ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); } - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + if (m_affiliations->isGranted(dstId)) { + uint32_t chNo = m_affiliations->getGrantedCh(dstId); + uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } - m_affiliations.releaseGrant(dstId, false); + m_affiliations->releaseGrant(dstId, false); } } @@ -1224,16 +1230,16 @@ void Control::RPC_touchGrantTG(json::object& req, json::object& reply) // LogDebugEx(LOG_NXDN, "Control::RPC_touchGrantTG()", "callback, dstId = %u", dstId); - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + if (m_affiliations->isGranted(dstId)) { + uint32_t chNo = m_affiliations->getGrantedCh(dstId); + uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } - m_affiliations.touchGrant(dstId); + m_affiliations->touchGrant(dstId); } } diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index 8b655307..883ed2c0 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -201,7 +201,7 @@ namespace nxdn * @brief Gets instance of the AffiliationLookup class. * @returns AffiliationLookup Instance of the AffiliationLookup class. */ - lookups::AffiliationLookup affiliations() { return m_affiliations; } + lookups::AffiliationLookup* affiliations() { return m_affiliations; } /** * @brief Returns the current operating RF state of the NXDN controller. @@ -291,7 +291,7 @@ namespace nxdn lookups::IdenTableLookup* m_idenTable; lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; - lookups::AffiliationLookup m_affiliations; + lookups::AffiliationLookup* m_affiliations; ::lookups::VoiceChData m_controlChData; lookups::IdenTable m_idenEntry; diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 9a831a2e..d7c274a3 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -37,13 +37,13 @@ using namespace nxdn::packet; #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ if (!m_nxdn->m_enableControl) { \ LogWarning(LOG_RF, "NXDN, %s denial, unsupported service, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ - writeRF_Message_Deny(0U, _SRCID, CauseResponse::SVC_UNAVAILABLE, _PCKT); \ + writeRF_Message_Deny(0U, _SRCID, CauseResponse::SVC_UNAVAILABLE, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ } // Validate the source RID. -#define VALID_SRCID(_PCKT_STR, _PCKT, _SRCID, _RSN) \ +#define VALID_SRCID(_PCKT_STR, _PCKT, _SRCID, _RSN) \ if (!acl::AccessControl::validateSrcId(_SRCID)) { \ LogWarning(LOG_RF, "NXDN, %s denial, RID rejection, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ @@ -71,7 +71,7 @@ using namespace nxdn::packet; // Verify the source RID is registered. #define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID, _RSN) \ - if (!m_nxdn->m_affiliations.isUnitReg(_SRCID) && m_verifyReg) { \ + if (!m_nxdn->m_affiliations->isUnitReg(_SRCID) && m_verifyReg) { \ LogWarning(LOG_RF, "NXDN, %s denial, RID not registered, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ @@ -80,7 +80,7 @@ using namespace nxdn::packet; // Verify the source RID is affiliated. #define VERIFY_SRCID_AFF(_PCKT_STR, _PCKT, _SRCID, _DSTID, _RSN) \ - if (!m_nxdn->m_affiliations.isGroupAff(_SRCID, _DSTID) && m_verifyAff) { \ + if (!m_nxdn->m_affiliations->isGroupAff(_SRCID, _DSTID) && m_verifyAff) { \ LogWarning(LOG_RF, "NXDN, %s denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ @@ -96,7 +96,7 @@ using namespace nxdn::packet; // Macro helper to verbose log a generic message. #define VERBOSE_LOG_MSG_DST(_PCKT_STR, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, "NXDN, %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ + LogMessage(LOG_RF, "NXDN, %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ } // Macro helper to verbose log a generic network message. @@ -108,7 +108,7 @@ using namespace nxdn::packet; // Macro helper to verbose log a generic network message. #define DEBUG_LOG_MSG(_PCKT_STR) \ if (m_debug) { \ - LogMessage(LOG_RF, "NXDN, %s", _PCKT_STR.c_str()); \ + LogMessage(LOG_RF, "NXDN, %s", _PCKT_STR.c_str()); \ } // --------------------------------------------------------------------------- @@ -156,7 +156,7 @@ bool ControlSignaling::process(FuncChannelType::E fct, ChOption::E option, uint8 uint16_t srcId = rcch->getSrcId(); uint16_t dstId = rcch->getDstId(); - m_nxdn->m_affiliations.touchUnitReg(srcId); + m_nxdn->m_affiliations->touchUnitReg(srcId); switch (rcch->getMessageType()) { case MessageType::RTCH_VCALL: @@ -250,7 +250,7 @@ bool ControlSignaling::processNetwork(FuncChannelType::E fct, ChOption::E option case MessageType::RTCH_VCALL: { if (m_nxdn->m_dedicatedControl) { - if (!m_nxdn->m_affiliations.isGranted(dstId)) { + if (!m_nxdn->m_affiliations->isGranted(dstId)) { if (m_verbose) { LogMessage(LOG_NET, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", rcch->toString().c_str(), rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), srcId, dstId); @@ -473,12 +473,12 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } - if (!m_nxdn->m_affiliations.isGranted(dstId)) { + if (!m_nxdn->m_affiliations->isGranted(dstId)) { if (grp && !m_nxdn->m_ignoreAffiliationCheck) { // is this an affiliation required group? ::lookups::TalkgroupRuleGroupVoice tid = m_nxdn->m_tidLookup->find(dstId); if (tid.config().affiliated()) { - if (!m_nxdn->m_affiliations.hasGroupAff(dstId)) { + if (!m_nxdn->m_affiliations->hasGroupAff(dstId)) { LogWarning(LOG_RF, "NXDN, %s ignored, no group affiliations, dstId = %u", rcch->toString().c_str(), dstId); return false; } @@ -487,13 +487,13 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin if (!grp && !m_nxdn->m_ignoreAffiliationCheck) { // is this the target registered? - if (!m_nxdn->m_affiliations.isUnitReg(dstId)) { + if (!m_nxdn->m_affiliations->isUnitReg(dstId)) { LogWarning(LOG_RF, "NXDN, %s ignored, no unit registration, dstId = %u", rcch->toString().c_str(), dstId); return false; } } - if (!m_nxdn->m_affiliations.rfCh()->isRFChAvailable()) { + if (!m_nxdn->m_affiliations->rfCh()->isRFChAvailable()) { if (grp) { if (!net) { LogWarning(LOG_RF, "NXDN, %s queued, no channels available, dstId = %u", rcch->toString().c_str(), dstId); @@ -518,8 +518,8 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } else { - if (m_nxdn->m_affiliations.grantCh(dstId, srcId, GRANT_TIMER_TIMEOUT, grp, net)) { - chNo = m_nxdn->m_affiliations.getGrantedCh(dstId); + if (m_nxdn->m_affiliations->grantCh(dstId, srcId, GRANT_TIMER_TIMEOUT, grp, net)) { + chNo = m_nxdn->m_affiliations->getGrantedCh(dstId); } } } @@ -527,7 +527,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin if (!m_disableGrantSrcIdCheck && !net) { // do collision check between grants to see if a SU is attempting a "grant retry" or if this is a // different source from the original grant - uint32_t grantedSrcId = m_nxdn->m_affiliations.getGrantedSrcId(dstId); + uint32_t grantedSrcId = m_nxdn->m_affiliations->getGrantedSrcId(dstId); if (srcId != grantedSrcId) { if (!net) { LogWarning(LOG_RF, "NXDN, %s denied, traffic in progress, dstId = %u", rcch->toString().c_str(), dstId); @@ -541,14 +541,14 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } - chNo = m_nxdn->m_affiliations.getGrantedCh(dstId); - m_nxdn->m_affiliations.touchGrant(dstId); + chNo = m_nxdn->m_affiliations->getGrantedCh(dstId); + m_nxdn->m_affiliations->touchGrant(dstId); } } else { - if (m_nxdn->m_affiliations.isGranted(dstId)) { - chNo = m_nxdn->m_affiliations.getGrantedCh(dstId); - m_nxdn->m_affiliations.touchGrant(dstId); + if (m_nxdn->m_affiliations->isGranted(dstId)) { + chNo = m_nxdn->m_affiliations->getGrantedCh(dstId); + m_nxdn->m_affiliations->touchGrant(dstId); } else { return false; @@ -568,7 +568,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin // callback RPC to permit the granted TG on the specified voice channel if (m_nxdn->m_authoritative && m_nxdn->m_supervisor) { - ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_nxdn->m_siteData.channelNo()) { json::object req = json::object(); @@ -596,7 +596,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin if (requestFailed) { ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo); - m_nxdn->m_affiliations.releaseGrant(dstId, false); + m_nxdn->m_affiliations->releaseGrant(dstId, false); if (!net) { writeRF_Message_Deny(0U, srcId, CauseResponse::VD_QUE_GRP_BUSY, MessageType::RTCH_VCALL); m_nxdn->m_rfState = RS_RF_REJECTED; @@ -680,7 +680,7 @@ bool ControlSignaling::writeRF_Message_Grp_Reg_Rsp(uint32_t srcId, uint32_t dstI } // validate the source RID is registered - if (!m_nxdn->m_affiliations.isUnitReg(srcId) && m_verifyReg) { + if (!m_nxdn->m_affiliations->isUnitReg(srcId) && m_verifyReg) { LogWarning(LOG_RF, "NXDN, %s denial, RID not registered, srcId = %u", rcch->toString().c_str(), srcId); ::ActivityLog("NXDN", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); rcch->setCauseResponse(CauseResponse::MM_REG_REFUSED); @@ -705,7 +705,7 @@ bool ControlSignaling::writeRF_Message_Grp_Reg_Rsp(uint32_t srcId, uint32_t dstI ret = true; // update dynamic affiliation table - m_nxdn->m_affiliations.groupAff(srcId, dstId); + m_nxdn->m_affiliations->groupAff(srcId, dstId); if (m_nxdn->m_network != nullptr) m_nxdn->m_network->announceGroupAffiliation(srcId, dstId); @@ -757,8 +757,8 @@ void ControlSignaling::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t dstId, ::ActivityLog("NXDN", true, "unit registration request from %u", srcId); // update dynamic unit registration table - if (!m_nxdn->m_affiliations.isUnitReg(srcId)) { - m_nxdn->m_affiliations.unitReg(srcId); + if (!m_nxdn->m_affiliations->isUnitReg(srcId)) { + m_nxdn->m_affiliations->unitReg(srcId); } if (m_nxdn->m_network != nullptr) diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 837ac4fa..7ae86824 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -59,7 +59,7 @@ using namespace nxdn::packet; } \ \ if (m_nxdn->m_enableControl && _DST_ID == m_nxdn->m_netLastDstId) { \ - if (m_nxdn->m_affiliations.isNetGranted(_DST_ID)) { \ + 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(); \ diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index d7b00e34..ba80e271 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -83,7 +83,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), - m_affiliations(this, chLookup, verbose), + m_affiliations(nullptr), m_controlChData(), m_idenEntry(), m_activeTG(), @@ -141,6 +141,8 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q assert(idenTable != nullptr); assert(rssiMapper != nullptr); + m_affiliations = new lookups::P25AffiliationLookup(this, chLookup, verbose); + // bryanb: this is a hacky check to see if the modem is a ModemV24 or not... modem::ModemV24* modemV24 = dynamic_cast(modem); if (modemV24 != nullptr) @@ -177,6 +179,10 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q Control::~Control() { + if (m_affiliations != nullptr) { + delete m_affiliations; + } + if (m_voice != nullptr) { delete m_voice; } @@ -418,18 +424,18 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw } } - m_siteData.setChCnt((uint8_t)m_affiliations.rfCh()->rfChSize()); + m_siteData.setChCnt((uint8_t)m_affiliations->rfCh()->rfChSize()); m_controlChData = controlChData; bool disableUnitRegTimeout = p25Protocol["disableUnitRegTimeout"].as(false); - m_affiliations.setDisableUnitRegTimeout(disableUnitRegTimeout); + m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); // set the grant release callback - m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { + m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel if (m_authoritative && m_supervisor) { - ::lookups::VoiceChData voiceChData = m_affiliations.rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_siteData.channelNo()) { json::object req = json::object(); @@ -445,7 +451,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw }); // set the unit deregistration callback - m_affiliations.setUnitDeregCallback([=](uint32_t srcId, bool automatic) { + m_affiliations->setUnitDeregCallback([=](uint32_t srcId, bool automatic) { if (m_network != nullptr) m_network->announceUnitDeregistration(srcId); @@ -687,7 +693,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) if (!m_dedicatedControl || m_control->m_convFallback) ret = m_voice->process(data, len); else { - if (m_voiceOnControl && m_affiliations.isChBusy(m_siteData.channelNo())) + if (m_voiceOnControl && m_affiliations->isChBusy(m_siteData.channelNo())) ret = m_voice->process(data, len); } break; @@ -951,7 +957,7 @@ void Control::clock() } m_networkWatchdog.stop(); - m_affiliations.releaseGrant(m_voice->m_netLC.getDstId(), false); + m_affiliations->releaseGrant(m_voice->m_netLC.getDstId(), false); if (m_dedicatedControl) { if (m_network != nullptr) @@ -990,7 +996,7 @@ void Control::clockSiteData(uint32_t ms) { if (m_enableControl) { // clock all the grant timers - m_affiliations.clock(ms); + m_affiliations->clock(ms); } if (m_control != nullptr) { @@ -1005,8 +1011,8 @@ void Control::clockSiteData(uint32_t ms) if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { m_control->writeAdjSSNetwork(); if (m_network != nullptr) { - if (m_affiliations.grpAffSize() > 0) { - auto affs = m_affiliations.grpAffTable(); + if (m_affiliations->grpAffSize() > 0) { + auto affs = m_affiliations->grpAffTable(); m_network->announceAffiliationUpdate(affs); } } @@ -1070,9 +1076,9 @@ void Control::clockSiteData(uint32_t ms) m_activeTGUpdate.start(); // do we have any granted channels? - if (m_affiliations.getGrantedRFChCnt() > 0U) { - uint8_t activeCnt = m_affiliations.getGrantedRFChCnt(); - std::unordered_map grantTable = m_affiliations.grantTable(); + if (m_affiliations->getGrantedRFChCnt() > 0U) { + uint8_t activeCnt = m_affiliations->getGrantedRFChCnt(); + std::unordered_map grantTable = m_affiliations->grantTable(); // iterate dynamic channel grant table entries json::array active = json::array(); @@ -1081,7 +1087,7 @@ void Control::clockSiteData(uint32_t ms) active.push_back(json::value((double)dstId)); } - std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); + std::unordered_map voiceChs = m_affiliations->rfCh()->rfChDataTable(); for (auto entry : voiceChs) { ::lookups::VoiceChData voiceChData = entry.second; @@ -1112,7 +1118,7 @@ void Control::clockSiteData(uint32_t ms) } } } else { - std::unordered_map voiceChs = m_affiliations.rfCh()->rfChDataTable(); + std::unordered_map voiceChs = m_affiliations->rfCh()->rfChDataTable(); for (auto entry : voiceChs) { ::lookups::VoiceChData voiceChData = entry.second; @@ -1581,7 +1587,7 @@ void Control::processFrameLoss() LogMessage(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); - m_affiliations.releaseGrant(m_voice->m_rfLC.getDstId(), false); + m_affiliations->releaseGrant(m_voice->m_rfLC.getDstId(), false); if (!m_enableControl) { notifyCC_ReleaseGrant(m_voice->m_rfLC.getDstId()); } @@ -1638,10 +1644,10 @@ void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId) { if (m_rfState == RS_RF_AUDIO && m_voice->m_rfLC.getDstId() == dstId) { LogWarning(LOG_P25, "network requested in-call traffic reject, dstId = %u", dstId); - if (m_affiliations.isGranted(dstId)) { - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); + if (m_affiliations->isGranted(dstId)) { + uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - m_affiliations.releaseGrant(dstId, false); + m_affiliations->releaseGrant(dstId, false); if (!m_enableControl) { notifyCC_ReleaseGrant(dstId); } @@ -1989,16 +1995,16 @@ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); } - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + if (m_affiliations->isGranted(dstId)) { + uint32_t chNo = m_affiliations->getGrantedCh(dstId); + uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } - m_affiliations.releaseGrant(dstId, false); + m_affiliations->releaseGrant(dstId, false); } } @@ -2028,16 +2034,16 @@ void Control::RPC_touchGrantTG(json::object& req, json::object& reply) // LogDebugEx(LOG_P25, "Control::RPC_touchGrantTG()", "callback, dstId = %u", dstId); - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + if (m_affiliations->isGranted(dstId)) { + uint32_t chNo = m_affiliations->getGrantedCh(dstId); + uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } - m_affiliations.touchGrant(dstId); + m_affiliations->touchGrant(dstId); } } diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 79d6dd2b..07e17dbc 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -228,7 +228,7 @@ namespace p25 * @brief Gets instance of the P25AffiliationLookup class. * @returns P25AffiliationLookup Instance of the P25AffiliationLookup class. */ - lookups::P25AffiliationLookup affiliations() { return m_affiliations; } + lookups::P25AffiliationLookup* affiliations() { return m_affiliations; } /** * @brief Returns the current operating RF state of the P25 controller. @@ -315,7 +315,7 @@ namespace p25 ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; ::lookups::TalkgroupRulesLookup* m_tidLookup; - lookups::P25AffiliationLookup m_affiliations; + lookups::P25AffiliationLookup* m_affiliations; ::lookups::VoiceChData m_controlChData; ::lookups::IdenTable m_idenEntry; diff --git a/src/host/p25/lookups/P25AffiliationLookup.cpp b/src/host/p25/lookups/P25AffiliationLookup.cpp index 10bdbedf..72dda5e5 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.cpp +++ b/src/host/p25/lookups/P25AffiliationLookup.cpp @@ -46,9 +46,9 @@ std::vector P25AffiliationLookup::clearGroupAff(uint32_t dstId, bool r /* Helper to release the channel grant for the destination ID. */ -bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock) +bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) { - bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll, noLock); + bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll); if (ret) { if (m_rfGrantChCnt > 0U) { m_p25->m_siteData.setChCnt(m_chLookup->rfChSize() + m_rfGrantChCnt); diff --git a/src/host/p25/lookups/P25AffiliationLookup.h b/src/host/p25/lookups/P25AffiliationLookup.h index 875f524f..56540b6a 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.h +++ b/src/host/p25/lookups/P25AffiliationLookup.h @@ -72,10 +72,9 @@ namespace p25 * @brief Helper to release the channel grant for the destination ID. * @param dstId Destination Address. * @param releaseAll Flag indicating all channel grants should be released. - * @param noLock Flag indicating no mutex lock operation should be performed while releasing. * @returns bool True, if channel grant was released, otherwise false. */ - bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false) override; + bool releaseGrant(uint32_t dstId, bool releaseAll) override; /** @} */ protected: diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 91680b53..045912c1 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -63,7 +63,7 @@ using namespace p25::packet; #define VALID_DSTID(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!acl::AccessControl::validateSrcId(_DSTID)) { \ LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ - writeRF_TSDU_Deny(_SRCID, _DSTID, ReasonCode::DENY_TGT_UNIT_NOT_VALID, _PCKT); \ + writeRF_TSDU_Deny(_SRCID, _DSTID, ReasonCode::DENY_TGT_UNIT_NOT_VALID, _PCKT); \ m_p25->m_rfState = RS_RF_REJECTED; \ return false; \ } @@ -72,14 +72,14 @@ using namespace p25::packet; #define VALID_TGID(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!acl::AccessControl::validateTGId(_DSTID)) { \ LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, TGID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ - writeRF_TSDU_Deny(_SRCID, _DSTID, ReasonCode::DENY_TGT_GROUP_NOT_VALID, _PCKT); \ + writeRF_TSDU_Deny(_SRCID, _DSTID, ReasonCode::DENY_TGT_GROUP_NOT_VALID, _PCKT); \ m_p25->m_rfState = RS_RF_REJECTED; \ return false; \ } // Verify the source RID is registered. #define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID) \ - if (!m_p25->m_affiliations.isUnitReg(_SRCID) && m_verifyReg) { \ + if (!m_p25->m_affiliations->isUnitReg(_SRCID) && m_verifyReg) { \ LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not registered, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_TSDU_Deny(_SRCID, WUID_FNE, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, _PCKT); \ writeRF_TSDU_U_Reg_Cmd(_SRCID); \ @@ -89,9 +89,9 @@ using namespace p25::packet; // Verify the source RID is affiliated. #define VERIFY_SRCID_AFF(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ - if (!m_p25->m_affiliations.isGroupAff(_SRCID, _DSTID) && m_verifyAff) { \ + if (!m_p25->m_affiliations->isGroupAff(_SRCID, _DSTID) && m_verifyAff) { \ LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ - writeRF_TSDU_Deny(_SRCID, _DSTID, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, _PCKT); \ + writeRF_TSDU_Deny(_SRCID, _DSTID, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, _PCKT); \ writeRF_TSDU_U_Reg_Cmd(_SRCID); \ m_p25->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -209,7 +209,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetSrcId(); uint32_t dstId = tsbk->getDstId(); - m_p25->m_affiliations.touchUnitReg(srcId); + m_p25->m_affiliations->touchUnitReg(srcId); m_lastMFID = tsbk->getMFId(); // handle standard P25 reference opcodes @@ -570,9 +570,9 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_affiliations.isGroupAff(srcId, dstId)) { + if (!m_p25->m_affiliations->isGroupAff(srcId, dstId)) { // update dynamic affiliation table - m_p25->m_affiliations.groupAff(srcId, dstId); + m_p25->m_affiliations->groupAff(srcId, dstId); if (m_p25->m_network != nullptr) m_p25->m_network->announceGroupAffiliation(srcId, dstId); @@ -792,7 +792,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr { if (m_p25->m_dedicatedControl) { // is the specified channel granted? - if (/*m_p25->m_affiliations.isChBusy(chNo) &&*/ m_p25->m_affiliations.isGranted(dstId)) { + if (/*m_p25->m_affiliations->isChBusy(chNo) &&*/ m_p25->m_affiliations->isGranted(dstId)) { uint32_t chNo = tsbk->getGrpVchNo(); if (m_verbose) { @@ -800,7 +800,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr tsbk->toString().c_str(), chNo, srcId, dstId); } - m_p25->m_affiliations.releaseGrant(dstId, false); + m_p25->m_affiliations->releaseGrant(dstId, false); } } @@ -824,7 +824,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr case TSBKO::IOSP_UU_VCH: { if (m_p25->m_enableControl && m_p25->m_dedicatedControl) { - if (!m_p25->m_affiliations.isGranted(dstId)) { + if (!m_p25->m_affiliations->isGranted(dstId)) { if (m_verbose) { LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); @@ -1829,7 +1829,7 @@ void ControlSignaling::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adj break; /** update data */ case 5: - if (m_p25->m_affiliations.grantSize() > 0) { + if (m_p25->m_affiliations->grantSize() > 0) { writeRF_TSDU_Grant_Update(); } break; @@ -2198,12 +2198,12 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } - if (!m_p25->m_affiliations.isGranted(dstId)) { + if (!m_p25->m_affiliations->isGranted(dstId)) { if (grp && !m_p25->m_ignoreAffiliationCheck) { // is this an affiliation required group? ::lookups::TalkgroupRuleGroupVoice tid = m_p25->m_tidLookup->find(dstId); if (tid.config().affiliated()) { - if (!m_p25->m_affiliations.hasGroupAff(dstId)) { + if (!m_p25->m_affiliations->hasGroupAff(dstId)) { LogWarning(LOG_NET, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request) ignored, no group affiliations, dstId = %u", dstId); return false; } @@ -2212,13 +2212,13 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (!grp && !m_p25->m_ignoreAffiliationCheck) { // is this the target registered? - if (!m_p25->m_affiliations.isUnitReg(dstId)) { + if (!m_p25->m_affiliations->isUnitReg(dstId)) { LogWarning(LOG_NET, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request) ignored, no unit registration, dstId = %u", dstId); return false; } } - if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) { + if (!m_p25->m_affiliations->rfCh()->isRFChAvailable()) { if (grp) { if (!net) { LogWarning(LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request) denied, no channels available, dstId = %u", dstId); @@ -2249,9 +2249,9 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } else { - if (m_p25->m_affiliations.grantCh(dstId, srcId, GRANT_TIMER_TIMEOUT, grp, net)) { - chNo = m_p25->m_affiliations.getGrantedCh(dstId); - m_p25->m_siteData.setChCnt(m_p25->m_affiliations.rfCh()->rfChSize() + m_p25->m_affiliations.getGrantedRFChCnt()); + if (m_p25->m_affiliations->grantCh(dstId, srcId, GRANT_TIMER_TIMEOUT, grp, net)) { + chNo = m_p25->m_affiliations->getGrantedCh(dstId); + m_p25->m_siteData.setChCnt(m_p25->m_affiliations->rfCh()->rfChSize() + m_p25->m_affiliations->getGrantedRFChCnt()); } } } @@ -2259,7 +2259,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (!m_disableGrantSrcIdCheck && !net) { // do collision check between grants to see if a SU is attempting a "grant retry" or if this is a // different source from the original grant - uint32_t grantedSrcId = m_p25->m_affiliations.getGrantedSrcId(dstId); + uint32_t grantedSrcId = m_p25->m_affiliations->getGrantedSrcId(dstId); if (srcId != grantedSrcId) { if (!net) { LogWarning(LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request) denied, traffic collision, dstId = %u", dstId); @@ -2276,14 +2276,14 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } - chNo = m_p25->m_affiliations.getGrantedCh(dstId); - m_p25->m_affiliations.touchGrant(dstId); + chNo = m_p25->m_affiliations->getGrantedCh(dstId); + m_p25->m_affiliations->touchGrant(dstId); } } else { - if (m_p25->m_affiliations.isGranted(dstId)) { - chNo = m_p25->m_affiliations.getGrantedCh(dstId); - m_p25->m_affiliations.touchGrant(dstId); + if (m_p25->m_affiliations->isGranted(dstId)) { + chNo = m_p25->m_affiliations->getGrantedCh(dstId); + m_p25->m_affiliations->touchGrant(dstId); } else { return false; @@ -2291,7 +2291,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } if (chNo > 0U) { - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); if (grp) { // callback RPC to permit the granted TG on the specified voice channel @@ -2323,7 +2323,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (requestFailed) { ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); - m_p25->m_affiliations.releaseGrant(dstId, false); + m_p25->m_affiliations->releaseGrant(dstId, false); if (!net) { writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true); m_p25->m_rfState = RS_RF_REJECTED; @@ -2393,7 +2393,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (requestFailed) { ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); - m_p25->m_affiliations.releaseGrant(dstId, false); + m_p25->m_affiliations->releaseGrant(dstId, false); if (!net) { writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true); m_p25->m_rfState = RS_RF_REJECTED; @@ -2446,15 +2446,15 @@ void ControlSignaling::writeRF_TSDU_Grant_Update() return; // write group voice grant update - if (m_p25->m_affiliations.grantSize() > 0) { - if (m_mbfGrpGrntCnt >= m_p25->m_affiliations.grantSize()) + if (m_p25->m_affiliations->grantSize() > 0) { + if (m_mbfGrpGrntCnt >= m_p25->m_affiliations->grantSize()) m_mbfGrpGrntCnt = 0U; std::unique_ptr osp; bool noData = false; uint8_t i = 0U; - std::unordered_map grantTable = m_p25->m_affiliations.grantTable(); + std::unordered_map grantTable = m_p25->m_affiliations->grantTable(); for (auto entry : grantTable) { // no good very bad way of skipping entries... if (i != m_mbfGrpGrntCnt) { @@ -2464,9 +2464,9 @@ void ControlSignaling::writeRF_TSDU_Grant_Update() else { uint32_t dstId = entry.first; uint32_t chNo = entry.second; - bool grp = m_p25->m_affiliations.isGroup(dstId); + bool grp = m_p25->m_affiliations->isGroup(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); if (chNo == 0U) { noData = true; @@ -2487,7 +2487,7 @@ void ControlSignaling::writeRF_TSDU_Grant_Update() m_mbfGrpGrntCnt++; break; } else { - uint32_t srcId = m_p25->m_affiliations.getGrantedSrcId(dstId); + uint32_t srcId = m_p25->m_affiliations->getGrantedSrcId(dstId); osp = std::make_unique(); DEBUG_LOG_TSBK(osp->toString()); @@ -2542,8 +2542,8 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 return false; } - if (!m_p25->m_affiliations.isGranted(srcId)) { - if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) { + if (!m_p25->m_affiliations->isGranted(srcId)) { + if (!m_p25->m_affiliations->rfCh()->isRFChAvailable()) { LogWarning(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request) denied, no channels available, srcId = %u", srcId); writeRF_TSDU_Deny(WUID_FNE, srcId, ReasonCode::DENY_NO_RF_RSRC_AVAIL, TSBKO::ISP_SNDCP_CH_REQ, false, true); @@ -2552,31 +2552,31 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 return false; } else { - if (m_p25->m_affiliations.grantCh(srcId, srcId, GRANT_TIMER_TIMEOUT, false, false)) { - chNo = m_p25->m_affiliations.getGrantedCh(srcId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); + if (m_p25->m_affiliations->grantCh(srcId, srcId, GRANT_TIMER_TIMEOUT, false, false)) { + chNo = m_p25->m_affiliations->getGrantedCh(srcId); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); osp->setGrpVchId(voiceChData.chId()); osp->setGrpVchNo(chNo); osp->setDataChnNo(chNo); - m_p25->m_siteData.setChCnt(m_p25->m_affiliations.rfCh()->rfChSize() + m_p25->m_affiliations.getGrantedRFChCnt()); + m_p25->m_siteData.setChCnt(m_p25->m_affiliations->rfCh()->rfChSize() + m_p25->m_affiliations->getGrantedRFChCnt()); } } } else { - chNo = m_p25->m_affiliations.getGrantedCh(srcId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); + chNo = m_p25->m_affiliations->getGrantedCh(srcId); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); osp->setGrpVchId(voiceChData.chId()); osp->setGrpVchNo(chNo); osp->setDataChnNo(chNo); - m_p25->m_affiliations.touchGrant(srcId); + m_p25->m_affiliations->touchGrant(srcId); } } if (chNo > 0U) { - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); // callback RPC to permit the granted TG on the specified voice channel if (m_p25->m_authoritative && m_p25->m_supervisor) { @@ -2611,7 +2611,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 if (requestFailed) { ::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo); - m_p25->m_affiliations.releaseGrant(srcId, false); + m_p25->m_affiliations->releaseGrant(srcId, false); writeRF_TSDU_Deny(srcId, srcId, ReasonCode::DENY_PTT_BONK, TSBKO::ISP_SNDCP_CH_REQ, false, true); m_p25->m_rfState = RS_RF_REJECTED; @@ -2717,7 +2717,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI } // register the RID if the MFID is $90 (this is typically DVRS, and DVRS won't unit register so we'll do it for them) - if (!m_p25->m_affiliations.isUnitReg(srcId) && m_lastMFID == MFG_MOT) { + if (!m_p25->m_affiliations->isUnitReg(srcId) && m_lastMFID == MFG_MOT) { // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, srcId = %u", iosp->toString().c_str(), srcId); @@ -2727,8 +2727,8 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI } else { // update dynamic unit registration table - if (!m_p25->m_affiliations.isUnitReg(srcId)) { - m_p25->m_affiliations.unitReg(srcId); + if (!m_p25->m_affiliations->isUnitReg(srcId)) { + m_p25->m_affiliations->unitReg(srcId); } if (m_p25->m_network != nullptr) @@ -2737,7 +2737,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI } // validate the source RID is registered - if (!m_p25->m_affiliations.isUnitReg(srcId) && m_verifyReg) { + if (!m_p25->m_affiliations->isUnitReg(srcId) && m_verifyReg) { LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not registered, srcId = %u", iosp->toString().c_str(), srcId); ::ActivityLog("P25", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); iosp->setResponse(ResponseCode::REFUSED); @@ -2774,7 +2774,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI ::ActivityLog("P25", true, "group affiliation request from %u to %s %u", srcId, "TG ", dstId); // update dynamic affiliation table - m_p25->m_affiliations.groupAff(srcId, dstId); + m_p25->m_affiliations->groupAff(srcId, dstId); if (m_p25->m_network != nullptr) m_p25->m_network->announceGroupAffiliation(srcId, dstId); @@ -2816,8 +2816,8 @@ void ControlSignaling::writeRF_TSDU_U_Reg_Rsp(uint32_t srcId, uint32_t sysId) ::ActivityLog("P25", true, "unit registration request from %u", srcId); // update dynamic unit registration table - if (!m_p25->m_affiliations.isUnitReg(srcId)) { - m_p25->m_affiliations.unitReg(srcId); + if (!m_p25->m_affiliations->isUnitReg(srcId)) { + m_p25->m_affiliations->unitReg(srcId); } if (m_p25->m_network != nullptr) @@ -2839,7 +2839,7 @@ void ControlSignaling::writeRF_TSDU_U_Dereg_Ack(uint32_t srcId) bool dereged = false; // remove dynamic unit registration table entry - dereged = m_p25->m_affiliations.unitDereg(srcId); + dereged = m_p25->m_affiliations->unitDereg(srcId); if (dereged) { std::unique_ptr osp = std::make_unique(); @@ -2903,7 +2903,7 @@ bool ControlSignaling::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, } // validate the source RID is registered - if (!m_p25->m_affiliations.isUnitReg(srcId)) { + if (!m_p25->m_affiliations->isUnitReg(srcId)) { LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not registered, srcId = %u", osp->toString().c_str(), srcId); ::ActivityLog("P25", true, "location registration request from %u denied", srcId); writeRF_TSDU_U_Reg_Cmd(srcId); @@ -2941,7 +2941,7 @@ bool ControlSignaling::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, ::ActivityLog("P25", true, "location registration request from %u", srcId); // update dynamic affiliation table - m_p25->m_affiliations.groupAff(srcId, dstId); + m_p25->m_affiliations->groupAff(srcId, dstId); if (m_p25->m_network != nullptr) m_p25->m_network->announceGroupAffiliation(srcId, dstId); @@ -2990,8 +2990,8 @@ void ControlSignaling::writeRF_TSDU_Auth_Dmd(uint32_t srcId) bool ControlSignaling::writeNet_TSDU_Call_Term(uint32_t srcId, uint32_t dstId) { // is the specified channel granted? - if (m_p25->m_affiliations.isGranted(dstId)) { - m_p25->m_affiliations.releaseGrant(dstId, false); + if (m_p25->m_affiliations->isGranted(dstId)) { + m_p25->m_affiliations->releaseGrant(dstId, false); } std::unique_ptr osp = std::make_unique(); diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 365893d3..408d2e61 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -162,7 +162,7 @@ bool Voice::process(uint8_t* data, uint32_t len) LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", lc.getDstId(), m_p25->m_netLastDstId); if (!m_p25->m_dedicatedControl) { - m_p25->m_affiliations.releaseGrant(m_p25->m_netLastDstId, false); + m_p25->m_affiliations->releaseGrant(m_p25->m_netLastDstId, false); } resetNet(); @@ -258,7 +258,7 @@ bool Voice::process(uint8_t* data, uint32_t len) 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); + m_p25->m_affiliations->releaseGrant(m_p25->m_netLastDstId, false); } resetNet(); @@ -274,7 +274,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // 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)) { + 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(); @@ -365,7 +365,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // verify the source RID is affiliated to the group TGID; only if control data // is supported if (group && m_p25->m_enableControl) { - if (!m_p25->m_affiliations.isGroupAff(srcId, dstId) && m_p25->m_control->m_verifyAff) { + if (!m_p25->m_affiliations->isGroupAff(srcId, dstId) && m_p25->m_control->m_verifyAff) { if (m_lastRejectId == 0 || m_lastRejectId != srcId) { LogWarning(LOG_RF, P25_HDU_STR " denial, RID not affiliated to TGID, srcId = %u, dstId = %u", srcId, dstId); m_p25->m_control->writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, TSBKO::IOSP_GRP_VCH, true, true); @@ -416,11 +416,11 @@ bool Voice::process(uint8_t* data, uint32_t len) if (m_p25->m_enableControl) { // if the group wasn't granted out -- explicitly grant the group - if (!m_p25->m_affiliations.isGranted(dstId)) { + if (!m_p25->m_affiliations->isGranted(dstId)) { if (m_p25->m_legacyGroupGrnt) { // are we auto-registering legacy radios to groups? if (m_p25->m_legacyGroupReg && group) { - if (!m_p25->m_affiliations.isGroupAff(srcId, dstId)) { + if (!m_p25->m_affiliations->isGroupAff(srcId, dstId)) { if (m_p25->m_control->writeRF_TSDU_Grp_Aff_Rsp(srcId, dstId) != ResponseCode::ACCEPT) { LogWarning(LOG_RF, P25_HDU_STR " denial, conventional affiliation required, not affiliated to TGID, srcId = %u, dstId = %u", srcId, dstId); m_p25->m_rfLastDstId = 0U; @@ -449,15 +449,15 @@ bool Voice::process(uint8_t* data, uint32_t len) // conventional registration or DVRS support? if ((m_p25->m_enableControl && !m_p25->m_dedicatedControl) || m_p25->m_voiceOnControl) { - if (!m_p25->m_affiliations.isGranted(dstId)) { + if (!m_p25->m_affiliations->isGranted(dstId)) { m_p25->m_control->writeRF_TSDU_Grant(srcId, dstId, serviceOptions, group, false, true); } // if voice on control; insert grant updates before voice traffic if (m_p25->m_voiceOnControl) { - uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); - bool grp = m_p25->m_affiliations.isGroup(dstId); + uint32_t chNo = m_p25->m_affiliations->getGrantedCh(dstId); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); + bool grp = m_p25->m_affiliations->isGroup(dstId); std::unique_ptr osp; @@ -471,7 +471,7 @@ bool Voice::process(uint8_t* data, uint32_t len) osp->setGrpVchNo(chNo); } else { - uint32_t srcId = m_p25->m_affiliations.getGrantedSrcId(dstId); + uint32_t srcId = m_p25->m_affiliations->getGrantedSrcId(dstId); osp = std::make_unique(); @@ -560,9 +560,9 @@ bool Voice::process(uint8_t* data, uint32_t len) // if voice on control; insert group voice channel updates directly after HDU but before LDUs if (m_p25->m_voiceOnControl) { - uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); - bool grp = m_p25->m_affiliations.isGroup(dstId); + uint32_t chNo = m_p25->m_affiliations->getGrantedCh(dstId); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); + bool grp = m_p25->m_affiliations->isGroup(dstId); std::unique_ptr osp; @@ -576,7 +576,7 @@ bool Voice::process(uint8_t* data, uint32_t len) osp->setGrpVchNo(chNo); } else { - uint32_t srcId = m_p25->m_affiliations.getGrantedSrcId(dstId); + uint32_t srcId = m_p25->m_affiliations->getGrantedSrcId(dstId); osp = std::make_unique(); @@ -632,7 +632,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // is control is enabled, and the group was granted by network already ignore RF traffic if (m_p25->m_enableControl && m_rfLC.getDstId() == m_p25->m_netLastDstId) { - if (m_p25->m_affiliations.isNetGranted(m_rfLC.getDstId())) { + if (m_p25->m_affiliations->isNetGranted(m_rfLC.getDstId())) { 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", m_rfLC.getSrcId(), m_rfLC.getDstId(), m_netLC.getSrcId(), m_p25->m_netLastDstId); resetRF(); @@ -689,7 +689,7 @@ bool Voice::process(uint8_t* data, uint32_t len) alreadyDecoded = false; if (m_p25->m_enableControl) { - m_p25->m_affiliations.touchGrant(m_rfLC.getDstId()); + m_p25->m_affiliations->touchGrant(m_rfLC.getDstId()); } if (m_p25->m_notifyCC) { @@ -1100,7 +1100,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } else if (duid == DUID::TDU || duid == DUID::TDULC) { if (!m_p25->m_enableControl) { - m_p25->m_affiliations.releaseGrant(m_rfLC.getDstId(), false); + m_p25->m_affiliations->releaseGrant(m_rfLC.getDstId(), false); } if (m_p25->m_notifyCC) { @@ -1305,7 +1305,7 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L if (m_p25->m_enableControl) { lc::LC control = lc::LC(*m_dfsiLC.control()); - m_p25->m_affiliations.touchGrant(control.getDstId()); + m_p25->m_affiliations->touchGrant(control.getDstId()); } if (m_p25->m_notifyCC) { @@ -1379,7 +1379,7 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L if (m_p25->m_enableControl) { lc::LC control = lc::LC(*m_dfsiLC.control()); - m_p25->m_affiliations.touchGrant(control.getDstId()); + m_p25->m_affiliations->touchGrant(control.getDstId()); } if (m_p25->m_notifyCC) { @@ -1449,7 +1449,7 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L m_netLastDUID = duid; if (!m_p25->m_enableControl) { - m_p25->m_affiliations.releaseGrant(m_netLC.getDstId(), false); + m_p25->m_affiliations->releaseGrant(m_netLC.getDstId(), false); } if (m_p25->m_notifyCC) { @@ -1780,7 +1780,7 @@ void Voice::writeNet_LDU1() (m_netLC.getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag (m_netLC.getPriority() & 0x07U); // Priority - if (!m_p25->m_affiliations.isGranted(dstId)) { + if (!m_p25->m_affiliations->isGranted(dstId)) { if (!m_p25->m_control->writeRF_TSDU_Grant(srcId, dstId, serviceOptions, group, true)) { LogError(LOG_NET, P25_HDU_STR " call rejected, network call not granted, dstId = %u", dstId); @@ -1816,9 +1816,9 @@ void Voice::writeNet_LDU1() // if voice on control; insert grant updates before voice traffic if (m_p25->m_voiceOnControl) { - uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId); - ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); - bool grp = m_p25->m_affiliations.isGroup(dstId); + uint32_t chNo = m_p25->m_affiliations->getGrantedCh(dstId); + ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); + bool grp = m_p25->m_affiliations->isGroup(dstId); std::unique_ptr osp; @@ -1832,7 +1832,7 @@ void Voice::writeNet_LDU1() osp->setGrpVchNo(chNo); } else { - uint32_t srcId = m_p25->m_affiliations.getGrantedSrcId(dstId); + uint32_t srcId = m_p25->m_affiliations->getGrantedSrcId(dstId); osp = std::make_unique(); From 245ba254c4dba740a88ef376e8dbf262705e7e49 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 14:50:41 -0400 Subject: [PATCH 13/41] remove mutex used for protecting udp packet deque; --- src/bridge/HostBridge.cpp | 13 ++----------- src/bridge/HostBridge.h | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 795541ca..bd495850 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -81,7 +81,6 @@ const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; std::mutex HostBridge::m_audioMutex; std::mutex HostBridge::m_networkMutex; -std::mutex HostBridge::m_udpAudioMutex; // --------------------------------------------------------------------------- // Global Functions @@ -1407,11 +1406,7 @@ void HostBridge::processUDPAudio() req->srcId = __GET_UINT32(buffer, pcmLength + 8U); } - // scope is intentional - { - std::lock_guard lock(m_udpAudioMutex); - m_udpPackets.push_back(req); - } + m_udpPackets.push_back(req); } } @@ -3039,11 +3034,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) } } - // scope is intentional - { - std::lock_guard lock(m_udpAudioMutex); - bridge->m_udpPackets.pop_front(); - } + bridge->m_udpPackets.pop_front(); bridge->m_udpDstId = bridge->m_dstId; diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index d94e4c84..398935e8 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -285,7 +285,6 @@ class HOST_SW_API HostBridge { static std::mutex m_audioMutex; static std::mutex m_networkMutex; - static std::mutex m_udpAudioMutex; #if defined(_WIN32) void* m_decoderState; From bc7dea017ae732469e604e6df21aee675e0a82cb Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 17:11:37 -0400 Subject: [PATCH 14/41] more concurrency solidification; --- src/common/concurrent/deque.h | 14 +++++---- src/common/concurrent/map.h | 33 +++++++++++++++++---- src/common/concurrent/unordered_map.h | 33 +++++++++++++++++---- src/common/concurrent/vector.h | 31 +++++++++++++++---- src/common/lookups/AffiliationLookup.cpp | 10 +++++++ src/common/lookups/PeerListLookup.cpp | 2 +- src/common/lookups/PeerListLookup.h | 4 +-- src/common/lookups/RadioIdLookup.cpp | 2 +- src/common/lookups/RadioIdLookup.h | 4 +-- src/common/lookups/TalkgroupRulesLookup.cpp | 2 +- src/common/lookups/TalkgroupRulesLookup.h | 4 +-- 11 files changed, 107 insertions(+), 32 deletions(-) diff --git a/src/common/concurrent/deque.h b/src/common/concurrent/deque.h index f06d4757..5470ba66 100644 --- a/src/common/concurrent/deque.h +++ b/src/common/concurrent/deque.h @@ -355,18 +355,20 @@ namespace concurrent } private: - mutable std::mutex m_mutex; //! Mutex used for hard locking. - mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + mutable std::mutex m_mutex; //! Mutex used for change locking. + mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. std::deque m_deque; /** - * @brief Locks the deque. + * @brief Lock the vector. + * @param readLock Flag indicating whether or not to use read locking. */ - inline void __lock() const + inline void __lock(bool readLock = false) const { m_mutex.lock(); - m_locked = true; + if (!readLock) + m_locked = true; } /** * @brief Unlocks the deque. @@ -377,7 +379,7 @@ namespace concurrent m_locked = false; } /** - * @brief Spins until the deque is unlocked. + * @brief Spins until the deque is read unlocked. */ inline void __spinlock() const { diff --git a/src/common/concurrent/map.h b/src/common/concurrent/map.h index 5721f3e6..5e0b07ec 100644 --- a/src/common/concurrent/map.h +++ b/src/common/concurrent/map.h @@ -371,19 +371,40 @@ namespace concurrent return m_map; } + /** + * @brief Locks the map. + * @param readLock Flag indicating whether or not to use read locking. + */ + void lock(bool readLock = false) const { __lock(readLock); } + /** + * @brief Unlocks the map. + */ + void unlock() const { __unlock(); } + /** + * @brief Flag indicating whether or not the map is read locked. + * @return bool True if the map is read locked, false otherwise. + */ + bool isReadLocked() const { return m_locked; } + /** + * @brief Spins until the map is unlocked. + */ + void spinlock() const { __spinlock(); } + private: - mutable std::mutex m_mutex; //! Mutex used for hard locking. - mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + mutable std::mutex m_mutex; //! Mutex used for change locking. + mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. std::map m_map; /** - * @brief Locks the map. + * @brief Lock the vector. + * @param readLock Flag indicating whether or not to use read locking. */ - inline void __lock() const + inline void __lock(bool readLock = false) const { m_mutex.lock(); - m_locked = true; + if (readLock) + m_locked = true; } /** * @brief Unlocks the map. @@ -394,7 +415,7 @@ namespace concurrent m_locked = false; } /** - * @brief Spins until the map is unlocked. + * @brief Spins until the map is read unlocked. */ inline void __spinlock() const { diff --git a/src/common/concurrent/unordered_map.h b/src/common/concurrent/unordered_map.h index a2ef7eff..dba791e7 100644 --- a/src/common/concurrent/unordered_map.h +++ b/src/common/concurrent/unordered_map.h @@ -371,19 +371,40 @@ namespace concurrent return m_map; } + /** + * @brief Locks the unordered_map. + * @param readLock Flag indicating whether or not to use read locking. + */ + void lock(bool readLock = true) const { __lock(readLock); } + /** + * @brief Unlocks the unordered_map. + */ + void unlock() const { __unlock(); } + /** + * @brief Flag indicating whether or not the unordered_map is read locked. + * @return bool True if the unordered_map is read locked, false otherwise. + */ + bool isReadLocked() const { return m_locked; } + /** + * @brief Spins until the unordered_map is unlocked. + */ + void spinlock() const { __spinlock(); } + private: - mutable std::mutex m_mutex; //! Mutex used for hard locking. - mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + mutable std::mutex m_mutex; //! Mutex used for change locking. + mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. std::unordered_map m_map; /** - * @brief Locks the unordered_map. + * @brief Lock the vector. + * @param readLock Flag indicating whether or not to use read locking. */ - inline void __lock() const + inline void __lock(bool readLock = true) const { m_mutex.lock(); - m_locked = true; + if (readLock) + m_locked = true; } /** * @brief Unlocks the unordered_map. @@ -394,7 +415,7 @@ namespace concurrent m_locked = false; } /** - * @brief Spins until the unordered_map is unlocked. + * @brief Spins until the unordered_map is read unlocked. */ inline void __spinlock() const { diff --git a/src/common/concurrent/vector.h b/src/common/concurrent/vector.h index c477b254..aa377a2d 100644 --- a/src/common/concurrent/vector.h +++ b/src/common/concurrent/vector.h @@ -433,19 +433,40 @@ namespace concurrent return m_vector; } + /** + * @brief Locks the vector. + * @param readLock Flag indicating whether or not to use read locking. + */ + void lock(bool readLock = true) const { __lock(readLock); } + /** + * @brief Unlocks the vector. + */ + void unlock() const { __unlock(); } + /** + * @brief Flag indicating whether or not the vector is read locked. + * @return bool True if the vector is read locked, false otherwise. + */ + bool isReadLocked() const { return m_locked; } + /** + * @brief Spins until the vector is unlocked. + */ + void spinlock() const { __spinlock(); } + private: - mutable std::mutex m_mutex; //! Mutex used for hard locking. - mutable bool m_locked = false; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + mutable std::mutex m_mutex; //! Mutex used for change locking. + mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. std::vector m_vector; /** * @brief Lock the vector. + * @param readLock Flag indicating whether or not to use read locking. */ - inline void __lock() const + inline void __lock(bool readLock = true) const { m_mutex.lock(); - m_locked = true; + if (readLock) + m_locked = true; } /** @@ -458,7 +479,7 @@ namespace concurrent } /** - * @brief Spins until the vector is unlocked. + * @brief Spins until the vector is read unlocked. */ inline void __spinlock() const { diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 579bcb6e..0c9764fc 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -345,11 +345,13 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); + m_grantChTable.lock(); std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { uint32_t dstId = entry.first; gntsToRel.push_back(dstId); } + m_grantChTable.unlock(); // release grants for (uint32_t dstId : gntsToRel) { @@ -530,7 +532,10 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId) void AffiliationLookup::clock(uint32_t ms) { + m_grantChTable.spinlock(); + // clock all the grant timers + m_grantChTable.lock(false); std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { uint32_t dstId = entry.first; @@ -540,6 +545,7 @@ void AffiliationLookup::clock(uint32_t ms) gntsToRel.push_back(dstId); } } + m_grantChTable.unlock(); // release grants that have timed out for (uint32_t dstId : gntsToRel) { @@ -547,7 +553,10 @@ void AffiliationLookup::clock(uint32_t ms) } if (!m_disableUnitRegTimeout) { + m_unitRegTable.spinlock(); + // clock all the unit registration timers + m_unitRegTable.lock(false); std::vector unitsToDereg = std::vector(); for (uint32_t srcId : m_unitRegTable) { m_unitRegTimers[srcId].clock(ms); @@ -555,6 +564,7 @@ void AffiliationLookup::clock(uint32_t ms) unitsToDereg.push_back(srcId); } } + m_unitRegTable.unlock(); // release units registrations that have timed out for (uint32_t srcId : unitsToDereg) { diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index 60044ef0..0554c015 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -37,7 +37,7 @@ bool PeerListLookup::m_locked = false; // Unlock the table. #define __UNLOCK_TABLE() m_locked = false; -// Spinlock wait for table to be released. +// Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ if (m_locked) { \ while (m_locked) \ diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index 527102a1..8738a1e1 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -263,8 +263,8 @@ namespace lookups private: Mode m_mode; - static std::mutex m_mutex; //! Mutex used for hard locking. - static bool m_locked; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + 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. }; } // namespace lookups diff --git a/src/common/lookups/RadioIdLookup.cpp b/src/common/lookups/RadioIdLookup.cpp index b0c0f84c..9aba2377 100644 --- a/src/common/lookups/RadioIdLookup.cpp +++ b/src/common/lookups/RadioIdLookup.cpp @@ -39,7 +39,7 @@ bool RadioIdLookup::m_locked = false; // Unlock the table. #define __UNLOCK_TABLE() m_locked = false; -// Spinlock wait for table to be released. +// Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ if (m_locked) { \ while (m_locked) \ diff --git a/src/common/lookups/RadioIdLookup.h b/src/common/lookups/RadioIdLookup.h index 2c859f2d..e26d800f 100644 --- a/src/common/lookups/RadioIdLookup.h +++ b/src/common/lookups/RadioIdLookup.h @@ -208,8 +208,8 @@ namespace lookups bool save() override; private: - static std::mutex m_mutex; //! Mutex used for hard locking. - static bool m_locked; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + 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. }; } // namespace lookups diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index f85c6df0..b1d5aadc 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -37,7 +37,7 @@ bool TalkgroupRulesLookup::m_locked = false; // Unlock the table. #define __UNLOCK_TABLE() m_locked = false; -// Spinlock wait for table to be released. +// Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ if (m_locked) { \ while (m_locked) \ diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index 66f3c455..68e22cb3 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -643,8 +643,8 @@ namespace lookups bool m_acl; bool m_stop; - static std::mutex m_mutex; //! Mutex used for hard locking. - static bool m_locked; //! Flag used for soft locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + 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. From b46f100928034717a3a9d95f1793d65c7f31fe48 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 19:34:27 -0400 Subject: [PATCH 15/41] further concurrency class usage; bump version number to R04H30; --- src/common/Defines.h | 2 +- src/common/concurrent/deque.h | 68 +++++++++++++++++++ src/common/concurrent/map.h | 4 +- src/common/concurrent/unordered_map.h | 4 +- src/fne/network/callhandler/TagDMRData.h | 8 +-- src/fne/network/callhandler/TagNXDNData.h | 8 +-- src/fne/network/callhandler/TagP25Data.h | 8 +-- .../callhandler/packetdata/DMRPacketData.h | 4 +- .../callhandler/packetdata/P25PacketData.h | 6 +- 9 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/common/Defines.h b/src/common/Defines.h index 8a7c5157..6dce8186 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -108,7 +108,7 @@ typedef unsigned long long ulong64_t; #define __EXE_NAME__ "" #define VERSION_MAJOR "04" -#define VERSION_MINOR "22" +#define VERSION_MINOR "30" #define VERSION_REV "H" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR diff --git a/src/common/concurrent/deque.h b/src/common/concurrent/deque.h index 5470ba66..431f7535 100644 --- a/src/common/concurrent/deque.h +++ b/src/common/concurrent/deque.h @@ -123,6 +123,74 @@ namespace concurrent __unlock(); } + /** + * @brief Returns a read/write iterator that points to the first + * element in the deque. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator begin() + { + __spinlock(); + return m_deque.begin(); + } + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the deque. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator begin() const + { + __spinlock(); + return m_deque.begin(); + } + /** + * @brief Returns a read/write iterator that points one past the last + * element in the deque. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator end() + { + __spinlock(); + return m_deque.end(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the deque. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator end() const + { + __spinlock(); + return m_deque.end(); + } + + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the deque. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cbegin() const + { + __spinlock(); + return m_deque.cbegin(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the deque. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cend() const + { + __spinlock(); + return m_deque.cend(); + } + /** * @brief Gets the total number of elements in the deque. * @returns size_t Total number of elements in the deque. diff --git a/src/common/concurrent/map.h b/src/common/concurrent/map.h index 5e0b07ec..2f6c08a2 100644 --- a/src/common/concurrent/map.h +++ b/src/common/concurrent/map.h @@ -170,7 +170,7 @@ namespace concurrent /** * @brief Returns a read-only (constant) iterator that points to the - * first element in the vector. Iteration is done in ordinary + * first element in the map. Iteration is done in ordinary * element order. * @returns const_iterator */ @@ -181,7 +181,7 @@ namespace concurrent } /** * @brief Returns a read-only (constant) iterator that points one past - * the last element in the vector. Iteration is done in ordinary + * the last element in the map. Iteration is done in ordinary * element order. * @returns const_iterator */ diff --git a/src/common/concurrent/unordered_map.h b/src/common/concurrent/unordered_map.h index dba791e7..c9e7afbf 100644 --- a/src/common/concurrent/unordered_map.h +++ b/src/common/concurrent/unordered_map.h @@ -170,7 +170,7 @@ namespace concurrent /** * @brief Returns a read-only (constant) iterator that points to the - * first element in the vector. Iteration is done in ordinary + * first element in the unordered_map. Iteration is done in ordinary * element order. * @returns const_iterator */ @@ -181,7 +181,7 @@ namespace concurrent } /** * @brief Returns a read-only (constant) iterator that points one past - * the last element in the vector. Iteration is done in ordinary + * the last element in the unordered_map. Iteration is done in ordinary * element order. * @returns const_iterator */ diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index 23e7873b..9a20d8cb 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -17,6 +17,8 @@ #define __CALLHANDLER__TAG_DMR_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" @@ -24,8 +26,6 @@ #include "network/FNENetwork.h" #include "network/callhandler/packetdata/DMRPacketData.h" -#include - namespace network { namespace callhandler @@ -147,7 +147,7 @@ namespace network */ uint32_t dstId; }; - std::deque m_parrotFrames; + concurrent::deque m_parrotFrames; bool m_parrotFramesReady; /** @@ -196,7 +196,7 @@ namespace network } }; typedef std::pair StatusMapPair; - std::unordered_map m_status; + concurrent::unordered_map m_status; friend class packetdata::DMRPacketData; packetdata::DMRPacketData* m_packetData; diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 195d2497..35ec58cd 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -18,13 +18,13 @@ #include "fne/Defines.h" #include "common/Clock.h" +#include "common/concurrent/deque.h" +#include "common/concurrent/unordered_map.h" #include "common/nxdn/NXDNDefines.h" #include "common/nxdn/lc/RTCH.h" #include "common/nxdn/lc/RCCH.h" #include "network/FNENetwork.h" -#include - namespace network { namespace callhandler @@ -116,7 +116,7 @@ namespace network */ uint32_t dstId; }; - std::deque m_parrotFrames; + concurrent::deque m_parrotFrames; bool m_parrotFramesReady; /** @@ -160,7 +160,7 @@ namespace network } }; typedef std::pair StatusMapPair; - std::unordered_map m_status; + concurrent::unordered_map m_status; bool m_debug; diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index 4ab19f33..32433840 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -18,6 +18,8 @@ #include "fne/Defines.h" #include "common/Clock.h" +#include "common/concurrent/deque.h" +#include "common/concurrent/unordered_map.h" #include "common/p25/P25Defines.h" #include "common/p25/data/DataHeader.h" #include "common/p25/data/LowSpeedData.h" @@ -29,8 +31,6 @@ #include "network/FNENetwork.h" #include "network/callhandler/packetdata/P25PacketData.h" -#include - namespace network { namespace callhandler @@ -164,7 +164,7 @@ namespace network */ uint32_t dstId; }; - std::deque m_parrotFrames; + concurrent::deque m_parrotFrames; bool m_parrotFramesReady; bool m_parrotFirstFrame; @@ -209,7 +209,7 @@ namespace network } }; typedef std::pair StatusMapPair; - std::unordered_map m_status; + concurrent::unordered_map m_status; friend class packetdata::P25PacketData; packetdata::P25PacketData* m_packetData; diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.h b/src/fne/network/callhandler/packetdata/DMRPacketData.h index 743d7a6e..d1481029 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.h +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.h @@ -18,6 +18,8 @@ #include "fne/Defines.h" #include "common/Clock.h" +#include "common/concurrent/deque.h" +#include "common/concurrent/unordered_map.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/data/DataHeader.h" #include "network/FNENetwork.h" @@ -115,7 +117,7 @@ namespace network } }; typedef std::pair StatusMapPair; - std::unordered_map m_status; + concurrent::unordered_map m_status; bool m_debug; diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index d57b2ca2..a1d3848f 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -18,6 +18,8 @@ #include "fne/Defines.h" #include "common/Clock.h" +#include "common/concurrent/deque.h" +#include "common/concurrent/unordered_map.h" #include "common/p25/P25Defines.h" #include "common/p25/data/DataBlock.h" #include "common/p25/data/DataHeader.h" @@ -103,7 +105,7 @@ namespace network uint64_t timestamp; //! Timestamp in milliseconds }; - std::deque m_dataFrames; + concurrent::deque m_dataFrames; /** * @brief Represents the receive status of a call. @@ -162,7 +164,7 @@ namespace network } }; typedef std::pair StatusMapPair; - std::unordered_map m_status; + concurrent::unordered_map m_status; typedef std::pair ArpTablePair; std::unordered_map m_arpTable; From 4d00807feaf207381899c18b81366ee65d75fdd2 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 21:18:08 -0400 Subject: [PATCH 16/41] add --boot commandline argument to reboot modem into bootloader without any interactive interface; --- src/host/HostMain.cpp | 25 ++++++++++++++++--------- src/host/HostMain.h | 3 +++ src/host/calibrate/HostCal.cpp | 9 +++++++++ src/host/setup/HostSetup.cpp | 2 ++ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/host/HostMain.cpp b/src/host/HostMain.cpp index 608d5bf0..d6f3fa7b 100644 --- a/src/host/HostMain.cpp +++ b/src/host/HostMain.cpp @@ -55,6 +55,8 @@ std::string g_remoteAddress = std::string("127.0.0.1"); uint16_t g_remotePort = REMOTE_MODEM_PORT; uint16_t g_remoteLocalPort = 0U; +bool g_bootloader = false; + bool g_fireDMRBeacon = false; bool g_fireP25Control = false; bool g_fireNXDNControl = false; @@ -113,15 +115,15 @@ void usage(const char* message, const char* arg) } ::fprintf(stdout, - "usage: %s [-vhdf]" - "[--syslog]" + "usage: %s [-vhdf] " + " [--syslog] " #if defined(ENABLE_SETUP_TUI) "[--setup]" -#else - "[--cal]" #endif - "[-c ]" - "[--remote [-a
] [-p ]]" + "[--cal]" + "[--boot]" + " [-c ]" + " [--remote [-a
] [-p ]]" "\n\n" " -v show version information\n" " -h show this screen\n" @@ -131,10 +133,10 @@ void usage(const char* message, const char* arg) " --syslog force logging to syslog\n" "\n" #if defined(ENABLE_SETUP_TUI) - " --setup setup and calibration mode\n" -#else - " --cal old calibration mode\n" + " --setup TUI setup and calibration mode\n" #endif + " --cal simple calibration mode\n" + " --boot connects to modem and reboots into bootloader mode\n" "\n" " -c specifies the configuration file to use\n" "\n" @@ -187,6 +189,11 @@ int checkArgs(int argc, char* argv[]) g_calibrate = true; #endif // defined(ENABLE_SETUP_TUI) } + else if (IS("--boot")) { + g_bootloader = true; + g_calibrate = true; + g_setup = false; + } else if (IS("-c")) { if (argc-- <= 0) usage("error: %s", "must specify the configuration file to use"); diff --git a/src/host/HostMain.h b/src/host/HostMain.h index ca9b9790..00b1bbe2 100644 --- a/src/host/HostMain.h +++ b/src/host/HostMain.h @@ -52,6 +52,9 @@ extern uint16_t g_remotePort; /** @brief (Global) Local Remote Modem Port (Listening Port). */ extern uint16_t g_remoteLocalPort; +/** @brief (Global) Set Modem into Bootloader Mode. */ +extern bool g_bootloader; + /** @brief (Global) Fire DMR beacon flag. */ extern bool g_fireDMRBeacon; /** @brief (Global) Fire P25 control flag. */ diff --git a/src/host/calibrate/HostCal.cpp b/src/host/calibrate/HostCal.cpp index 7a19fcf9..2b2e2cb0 100644 --- a/src/host/calibrate/HostCal.cpp +++ b/src/host/calibrate/HostCal.cpp @@ -121,6 +121,15 @@ int HostCal::run(int argc, char **argv) return 1; } + if (g_bootloader) { + writeBootload(); + + m_isConnected = false; + m_modem->close(); + m_console.close(); + return 0; + } + readFlash(); writeFifoLength(); diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index a623103a..08726c36 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -2051,6 +2051,8 @@ bool HostSetup::writeFlash() void HostSetup::writeBootload() { m_reqBootload = true; + LogMessage(LOG_CAL, "Rebooting modem into ST bootloader mode..."); + if (writeFlash()) { uint8_t buffer[4U]; ::memset(buffer, 0x00U, 4U); From b9ad86c4f6301da02cf4fe167e7230d2bcdbae96 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 21:23:13 -0400 Subject: [PATCH 17/41] cleanup program -h usage display; --- src/host/HostMain.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/host/HostMain.cpp b/src/host/HostMain.cpp index d6f3fa7b..694d0f60 100644 --- a/src/host/HostMain.cpp +++ b/src/host/HostMain.cpp @@ -115,12 +115,12 @@ void usage(const char* message, const char* arg) } ::fprintf(stdout, - "usage: %s [-vhdf] " - " [--syslog] " + "usage: %s [-vhdf]" + " [--syslog]" #if defined(ENABLE_SETUP_TUI) - "[--setup]" + " [--setup]" #endif - "[--cal]" + " [--cal]" "[--boot]" " [-c ]" " [--remote [-a
] [-p ]]" @@ -134,6 +134,7 @@ void usage(const char* message, const char* arg) "\n" #if defined(ENABLE_SETUP_TUI) " --setup TUI setup and calibration mode\n" + "\n" #endif " --cal simple calibration mode\n" " --boot connects to modem and reboots into bootloader mode\n" From 07aff2df475bf2d03d8dc287e1a1ba4d430ed65a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 21:28:32 -0400 Subject: [PATCH 18/41] incorrect opcode define; --- src/common/p25/P25Defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index f5a1a9dc..b88e56a1 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -739,6 +739,7 @@ namespace p25 OSP_SNDCP_CH_GNT = 0x14U, //! SNDCP CH GNT - SNDCP Data Channel Grant OSP_SNDCP_CH_ANN = 0x16U, //! SNDCP CH ANN - SNDCP Data Channel Announcement OSP_STS_Q = 0x1AU, //! STS Q - Status Query + OSP_QUE_RSP = 0x21U, //! QUE RSP - Queued Response OSP_DENY_RSP = 0x27U, //! DENY RSP - Deny Response OSP_SCCB_EXP = 0x29U, //! SCCB - Secondary Control Channel Broadcast - Explicit OSP_GRP_AFF_Q = 0x2AU, //! GRP AFF Q - Group Affiliation Query @@ -748,7 +749,6 @@ namespace p25 OSP_SYNC_BCAST = 0x30U, //! SYNC BCAST - Synchronization Broadcast OSP_AUTH_DMD = 0x31U, //! AUTH DMD - Authentication Demand OSP_AUTH_FNE_RESP = 0x32U, //! AUTH FNE RESP - Authentication FNE Response - OSP_QUE_RSP = 0x33U, //! QUE RSP - Queued Response OSP_IDEN_UP_VU = 0x34U, //! IDEN UP VU - Channel Identifier Update for VHF/UHF Bands OSP_TIME_DATE_ANN = 0x35U, //! TIME DATE ANN - Time and Date Announcement OSP_SYS_SRV_BCAST = 0x38U, //! SYS SRV BCAST - System Service Broadcast From ffdb406678a0734b311701437ac2ab67fe82c4ef Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 21:31:30 -0400 Subject: [PATCH 19/41] update README.md; --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02f86eb3..4537816f 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ There is no other real configuration for a `dvmbridge` instance other then setti ### dvmhost Command Line Parameters ``` -usage: ./dvmhost [-vhdf][--syslog][--setup][-c ][--remote [-a
] [-p ]] +usage: ./dvmhost/dvmhost [-vhdf] [--syslog] [--setup] [--cal][--boot] [-c ] [--remote [-a
] [-p ]] -v show version information -h show this screen @@ -229,13 +229,17 @@ usage: ./dvmhost [-vhdf][--syslog][--setup][-c ][--remote [- --syslog force logging to syslog - --setup setup and calibration mode + --setup TUI setup and calibration mode + + --cal simple calibration mode + --boot connects to modem and reboots into bootloader mode -c specifies the configuration file to use --remote remote modem mode -a remote modem command address -p remote modem command port + -P remote modem command port (local listening port) -- stop handling options ``` From e483189cf9cd30b8d1ef9a56a25da2b79ed8356d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 19 Apr 2025 22:43:26 -0400 Subject: [PATCH 20/41] correct incorrect string format for non-useAlternatePortForDiagnostics; --- 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 e956e43c..7293918a 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1325,7 +1325,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) bool currState = g_disableTimeDisplay; g_disableTimeDisplay = true; - ::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); + ::Log(9999U, nullptr, nullptr, 0U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); g_disableTimeDisplay = currState; // report diagnostic log to InfluxDB From fcffdb36fd0b7ee176cd785ab034fd370a472f46 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 23 Apr 2025 08:36:43 -0400 Subject: [PATCH 21/41] update README.md; --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4537816f..2bd49a5c 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ There is no other real configuration for a `dvmbridge` instance other then setti ### dvmhost Command Line Parameters ``` -usage: ./dvmhost/dvmhost [-vhdf] [--syslog] [--setup] [--cal][--boot] [-c ] [--remote [-a
] [-p ]] +usage: ./dvmhost [-vhdf] [--syslog] [--setup] [--cal][--boot] [-c ] [--remote [-a
] [-p ]] -v show version information -h show this screen From 29dde34fbbde7c3163092d65ebbbb2e5e0d435df Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 23 Apr 2025 13:05:12 -0400 Subject: [PATCH 22/41] simplify influxdb worker task function; --- src/fne/network/influxdb/InfluxDB.h | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/fne/network/influxdb/InfluxDB.h b/src/fne/network/influxdb/InfluxDB.h index d424aa80..5c974830 100644 --- a/src/fne/network/influxdb/InfluxDB.h +++ b/src/fne/network/influxdb/InfluxDB.h @@ -462,28 +462,25 @@ namespace network /** * @brief */ - static void taskFluxRequest(void* arg) + static void taskFluxRequest(TSCallerRequest* req) { - TSCallerRequest* req = (TSCallerRequest*)arg; - if (req != nullptr) { - if (req == nullptr) { - return; - } - - TSCaller* caller = static_cast(req->obj); - if (caller == nullptr) { - if (req != nullptr) { - delete req; - } + if (req == nullptr) { + return; + } - return; + TSCaller* caller = static_cast(req->obj); + if (caller == nullptr) { + if (req != nullptr) { + delete req; } - const ServerInfo& si = req->si; - detail::inner::request("POST", "write", "", req->lines, si, nullptr); - - delete req; + return; } + + const ServerInfo& si = req->si; + detail::inner::request("POST", "write", "", req->lines, si, nullptr); + + delete req; } }; From 41bca4cceea88b1f0ba4c9f797741645b6d81cbe Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 23 Apr 2025 20:47:54 -0400 Subject: [PATCH 23/41] refactor PL_ACT_PEER_LIST opcode entirely, use zlib and compress list sent and properly block data sent; --- src/common/network/FrameQueue.cpp | 5 + src/fne/network/DiagNetwork.cpp | 196 +++++++++++++++++++++++++++--- src/fne/network/FNENetwork.cpp | 1 + src/fne/network/FNENetwork.h | 32 +++++ src/fne/network/PeerNetwork.cpp | 85 ++++++++++++- 5 files changed, 300 insertions(+), 19 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 2502a154..c0a51afe 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -108,6 +108,11 @@ UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint3 return nullptr; } + if (_fneHeader.getMessageLength() == 0U) { + LogError(LOG_NET, "FrameQueue::read(), invalid FNE packet length received from network"); + return nullptr; + } + if (fneHeader != nullptr) { *fneHeader = _fneHeader; } diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index f085c3a7..aa29ff42 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -174,6 +174,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) if (req->length > 0) { uint32_t peerId = req->fneHeader.getPeerId(); + uint32_t streamId = req->fneHeader.getStreamId(); // process incoming message function opcodes switch (req->fneHeader.getFunction()) { @@ -378,27 +379,190 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) // validate peer (simple validation really) if (connection->connected() && connection->address() == ip && connection->isExternalPeer() && connection->isPeerLink()) { - UInt8Array __rawPayload = std::make_unique(req->length - 8U); + UInt8Array __rawPayload = std::make_unique(req->length); uint8_t* rawPayload = __rawPayload.get(); - ::memset(rawPayload, 0x00U, req->length - 8U); - ::memcpy(rawPayload, req->buffer + 8U, req->length - 8U); - std::string payload(rawPayload, rawPayload + (req->length - 8U)); - - // parse JSON body - json::value v; - std::string err = json::parse(v, payload); - if (!err.empty()) { - break; + ::memset(rawPayload, 0x00U, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + // Utils::dump(1U, "Raw Payload", rawPayload, req->length); + + uint8_t curBlock = rawPayload[8U]; + uint8_t blockCnt = rawPayload[9U]; + + if (network->m_peerLinkActPkt.find(peerId) == network->m_peerLinkActPkt.end()) { + network->m_peerLinkActPkt.insert(peerId, FNENetwork::PLActPeerPkt()); + + FNENetwork::PLActPeerPkt& pkt = network->m_peerLinkActPkt[peerId]; + pkt.fragments = std::unordered_map(); + pkt.streamId = streamId; + + pkt.locked = false; + } else { + FNENetwork::PLActPeerPkt& pkt = network->m_peerLinkActPkt[peerId]; + if (!pkt.locked && pkt.streamId != streamId) { + LogError(LOG_NET, "PEER %u Peer-Link, Active Peer List, stream ID mismatch, expected %u, got %u", peerId, pkt.streamId, streamId); + + for (auto& frag : pkt.fragments) { + if (frag.second != nullptr) + delete[] frag.second; + } + + pkt.fragments = std::unordered_map(); + pkt.streamId = streamId; + } + + if (pkt.streamId != streamId) { + // otherwise drop the packet + break; + } + } + + FNENetwork::PLActPeerPkt& pkt = network->m_peerLinkActPkt[peerId]; + if (pkt.locked) { + while (pkt.locked) + Thread::sleep(1U); + } + + pkt.locked = true; + + // if this is the first block store sizes and initialize temp buffer + if (curBlock == 0U) { + uint32_t size = __GET_UINT32(rawPayload, 0U); + uint32_t compressedSize = __GET_UINT32(rawPayload, 4U); + + FNENetwork::PLActPeerPkt& pkt = network->m_peerLinkActPkt[peerId]; + pkt.size = size; + pkt.compressedSize = compressedSize; + } + + // scope is intentional + { + pkt.lastBlock = curBlock; + uint8_t* buffer = nullptr; + if (pkt.size < PEER_LINK_BLOCK_SIZE) + buffer = new uint8_t[PEER_LINK_BLOCK_SIZE + 1U]; + else + buffer = new uint8_t[pkt.size + 1U]; + + ::memcpy(buffer, rawPayload + 10U, PEER_LINK_BLOCK_SIZE); + // Utils::dump(1U, "Block Payload", buffer, PEER_LINK_BLOCK_SIZE); + pkt.fragments.insert({curBlock, buffer}); } - else { - // ensure parsed JSON is an array - if (!v.is()) { + + LogInfoEx(LOG_NET, "PEER %u Peer-Link, Active Peer List, block %u of %u, rxFragments = %u, streamId = %u", peerId, curBlock, blockCnt, pkt.fragments.size(), streamId); + + // do we have all the blocks? + if (pkt.fragments.size() == blockCnt + 1U) { + uint8_t* buffer = new uint8_t[pkt.size + 1U]; + ::memset(buffer, 0x00U, pkt.size + 1U); + for (uint8_t i = 0U; i < pkt.fragments.size(); i++) { + uint32_t offs = i * PEER_LINK_BLOCK_SIZE; + ::memcpy(buffer + offs, pkt.fragments[i], PEER_LINK_BLOCK_SIZE); + } + + // Utils::dump(1U, "Compressed Payload", buffer, pkt.compressedSize); + + // handle last block + // compression structures + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + // set input data + strm.avail_in = pkt.compressedSize; + strm.next_in = buffer; + + // initialize decompression + int ret = inflateInit(&strm); + if (ret != Z_OK) { + LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId); + + pkt.size = 0U; + pkt.compressedSize = 0U; + + delete[] buffer; + for (auto& frag : pkt.fragments) { + if (frag.second != nullptr) + delete[] frag.second; + } + + network->m_peerLinkActPkt.erase(peerId); break; } - else { - json::array arr = v.get(); - network->m_peerLinkPeers[peerId] = arr; + + // decompress data + std::vector decompressedData; + uint8_t outbuffer[1024]; + do { + strm.avail_out = sizeof(outbuffer); + strm.next_out = outbuffer; + + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) { + LogError(LOG_NET, "PEER %u error decompressing peer active list", peerId); + inflateEnd(&strm); + goto pal_lookup_cleanup; // yes - I hate myself; but this is quick + } + + decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out); + } while (ret != Z_STREAM_END); + + // cleanup + inflateEnd(&strm); + + // scope is intentional + { + uint32_t decompressedLen = strm.total_out; + uint8_t* decompressed = decompressedData.data(); + + // Utils::dump(1U, "Raw Peer Link Active Peer Data", decompressed + 8U, decompressedLen - 8U); + + // check that we got the appropriate data + if (decompressedLen == pkt.size) { + std::string payload(decompressed + 8U, decompressed + decompressedLen); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + LogError(LOG_NET, "PEER %u error parsing active peer list, %s", peerId, err.c_str()); + break; + } + else { + // ensure parsed JSON is an array + if (!v.is()) { + LogError(LOG_NET, "PEER %u error parsing active peer list, data was not valid", peerId); + break; + } + else { + json::array arr = v.get(); + LogInfoEx(LOG_NET, "PEER %u Peer-Link, Active Peer List, updating %u peer entries", peerId, arr.size()); + network->m_peerLinkPeers[peerId] = arr; + } + } + } + else { + LogError(LOG_NET, "PEER %u Peer-Link, error decompressed list, was not of expected size! %u != %u", peerId, decompressedLen, pkt.size); + } + } + + pal_lookup_cleanup: + pkt.size = 0U; + pkt.compressedSize = 0U; + + delete[] buffer; + for (auto& frag : pkt.fragments) { + if (frag.second != nullptr) + delete[] frag.second; } + + pkt.fragments = std::unordered_map(); + pkt.streamId = 0U; + + network->m_peerLinkActPkt.erase(peerId); + } else { + pkt.locked = false; } } else { diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 7293918a..705a8089 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -79,6 +79,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_peerAffiliations(), m_ccPeerMap(), m_peerLinkKeyQueue(), + m_peerLinkActPkt(), m_maintainenceTimer(1000U, pingTime), m_updateLookupTime(updateLookupTime * 60U), m_softConnLimit(0U), diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 2e57857b..b783f205 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -562,6 +562,38 @@ namespace network static std::timed_mutex m_keyQueueMutex; std::unordered_map m_peerLinkKeyQueue; + /** + * @brief Represents a Peer-Link Active Peer List fragment packet. + */ + class PLActPeerPkt { + public: + /** + * @brief Compressed size of the packet. + */ + uint32_t compressedSize; + /** + * @brief Uncompressed size of the packet. + */ + uint32_t size; + + /** + * @brief Last block of the packet. + */ + uint8_t lastBlock; + /** + * @brief Stream ID of the packet. + */ + uint32_t streamId; + + /** + * @brief Packet fragments. + */ + std::unordered_map fragments; + + bool locked; + }; + concurrent::unordered_map m_peerLinkActPkt; + Timer m_maintainenceTimer; uint32_t m_updateLookupTime; diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 4a01740f..0b06e75a 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -113,14 +113,93 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) json::value v = json::value(*peerList); std::string json = std::string(v.serialize()); - CharArray __buffer = std::make_unique(json.length() + 9U); + size_t len = json.length() + 9U; + CharArray __buffer = std::make_unique(len); char* buffer = __buffer.get(); ::memcpy(buffer + 0U, TAG_PEER_LINK, 4U); ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); - return writeMaster({ NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_ACT_PEER_LIST }, - (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, createStreamId(), false, true); + // compression structures + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + // initialize compression + if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) { + LogError(LOG_NET, "PEER %u error initializing ZLIB", m_peerId); + return false; + } + + // set input data + strm.avail_in = len; + strm.next_in = (uint8_t*)buffer; + + // compress data + std::vector compressedData; + int ret; + do { + // resize the output buffer as needed + compressedData.resize(compressedData.size() + 16384); + strm.avail_out = 16384; + strm.next_out = compressedData.data() + compressedData.size() - 16384; + + ret = deflate(&strm, Z_FINISH); + if (ret == Z_STREAM_ERROR) { + LogError(LOG_NET, "PEER %u error compressing active peer list", m_peerId); + deflateEnd(&strm); + return false; + } + } while (ret != Z_STREAM_END); + + // resize the output buffer to the actual compressed data size + compressedData.resize(strm.total_out); + + // cleanup + deflateEnd(&strm); + + uint32_t compressedLen = strm.total_out; + uint8_t* compressed = compressedData.data(); + + // Utils::dump(1U, "Compressed Payload", compressed, compressedLen); + + // transmit peer link active peer list + uint32_t streamId = createStreamId(); + uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); + uint32_t offs = 0U; + for (uint8_t i = 0U; i < blockCnt; i++) { + // build dataset + uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); + UInt8Array __payload = std::make_unique(bufSize); + uint8_t* payload = __payload.get(); + ::memset(payload, 0x00U, bufSize); + + if (i == 0U) { + __SET_UINT32(len, payload, 0U); + __SET_UINT32(compressedLen, payload, 4U); + } + + payload[8U] = i; + payload[9U] = blockCnt - 1U; + + uint32_t blockSize = PEER_LINK_BLOCK_SIZE; + if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) + blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); + + ::memcpy(payload + 10U, compressed + offs, blockSize); + + if (m_debug) + Utils::dump(1U, "Peer-Link Active Peer Payload", payload, bufSize); + + offs += PEER_LINK_BLOCK_SIZE; + + writeMaster({ NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_ACT_PEER_LIST }, + payload, bufSize, RTP_END_OF_CALL_SEQ, streamId, false, true); + Thread::sleep(60U); // pace block transmission + } + + return true; } return false; From e9a872dfd66c81d026bde87d07e9f591c61d9c9a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 24 Apr 2025 09:07:38 -0400 Subject: [PATCH 24/41] don't waste cycles on building the peer list repeatedly, build it once for the cycle; --- src/fne/network/FNENetwork.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 705a8089..4d2f79cb 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -354,6 +354,7 @@ void FNENetwork::clock(uint32_t ms) // send active peer list to Peer-Link masters if (m_host->m_peerNetworks.size() > 0) { + json::array peers = json::array(); for (auto peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isPeerLink()) { @@ -364,8 +365,7 @@ void FNENetwork::clock(uint32_t ms) }); } - if (m_peers.size() > 0) { - json::array peers = json::array(); + if (m_peers.size() > 0 && peers.size() == 0) { for (auto entry : m_peers) { uint32_t peerId = entry.first; network::FNEPeerConnection* peerConn = entry.second; @@ -376,7 +376,9 @@ void FNENetwork::clock(uint32_t ms) peers.push_back(json::value(peerObj)); } } + } + if (peers.size() > 0) { peer.second->writePeerLinkPeers(&peers); } } From 0f1ca3160f3c4372e6f6ff7dec2f84a9ed81bbdd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 24 Apr 2025 10:05:58 -0400 Subject: [PATCH 25/41] deduplicate compress/decompress code into a static C++ class; --- src/CompilerOptions.cmake | 5 + src/common/CMakeLists.txt | 1 + src/common/zlib/Compression.cpp | 169 ++++++++++++++++++ src/common/zlib/Compression.h | 57 ++++++ src/fne/network/DiagNetwork.cpp | 62 +------ src/fne/network/FNENetwork.cpp | 304 ++++++++++---------------------- src/fne/network/PeerNetwork.cpp | 268 +++++----------------------- 7 files changed, 380 insertions(+), 486 deletions(-) create mode 100644 src/common/zlib/Compression.cpp create mode 100644 src/common/zlib/Compression.h diff --git a/src/CompilerOptions.cmake b/src/CompilerOptions.cmake index 0fad339c..6c667071 100644 --- a/src/CompilerOptions.cmake +++ b/src/CompilerOptions.cmake @@ -46,6 +46,7 @@ option(DEBUG_P25_DFSI "" off) option(DEBUG_RINGBUFFER "" off) option(DEBUG_HTTP_PAYLOAD "" off) option(DEBUG_TRELLIS "" off) +option(DEBUG_COMPRESS "" off) if (DEBUG_DMR_PDU_DATA) message(CHECK_START "DMR PDU Data Debug") @@ -139,6 +140,10 @@ if (DEBUG_TRELLIS) message(CHECK_START "Trellis Encoding Debug") add_definitions(-DDEBUG_TRELLIS) endif (DEBUG_TRELLIS) +if (DEBUG_COMPRESS) + message(CHECK_START "zlib Compression Debug") + add_definitions(-DDEBUG_COMPRESS) +endif (DEBUG_COMPRESS) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") find_package(Threads REQUIRED) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d4b39179..a7d394f4 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -50,6 +50,7 @@ file(GLOB common_SRC "src/common/network/udp/*.cpp" "src/common/network/viface/*.cpp" "src/common/yaml/*.cpp" + "src/common/zlib/*.cpp" "src/common/zlib/*.c" "src/common/*.cpp" ) diff --git a/src/common/zlib/Compression.cpp b/src/common/zlib/Compression.cpp new file mode 100644 index 00000000..0d8dd5d2 --- /dev/null +++ b/src/common/zlib/Compression.cpp @@ -0,0 +1,169 @@ +// 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 "zlib/Compression.h" +#include "zlib/zlib.h" +#include "Log.h" +#include "Utils.h" + +#include +#include + +using namespace compress; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Compress the given input buffer. */ + +uint8_t* Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen) +{ + assert(buffer != nullptr); + assert(len > 0U); + + if (compressedLen != nullptr) { + *compressedLen = 0U; + } + + uint8_t* data = new uint8_t[len]; + ::memset(data, 0x00U, len); + ::memcpy(data, buffer, len); + + // compression structures + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + // initialize compression + if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) { + LogError(LOG_HOST, "Error initializing ZLIB"); + delete[] data; + return nullptr; + } + + // set input data + strm.avail_in = len; + strm.next_in = data; + + // compress data + std::vector compressedData; + int ret; + do { + // resize the output buffer as needed + compressedData.resize(compressedData.size() + 16384); + strm.avail_out = 16384; + strm.next_out = compressedData.data() + compressedData.size() - 16384; + + ret = deflate(&strm, Z_FINISH); + if (ret == Z_STREAM_ERROR) { + LogError(LOG_HOST, "ZLIB error compressing data; stream error"); + deflateEnd(&strm); + delete[] data; + return nullptr; + } + } while (ret != Z_STREAM_END); + + // resize the output buffer to the actual compressed data size + compressedData.resize(strm.total_out); + + // cleanup + deflateEnd(&strm); + + if (compressedLen != nullptr) { + *compressedLen = strm.total_out; + } + + uint8_t* compressed = compressedData.data(); +#if DEBUG_COMPRESS + 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; +} + +/* Decompress the given input buffer. */ + +uint8_t* Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen) +{ + assert(buffer != nullptr); + assert(len > 0U); + + if (decompressedLen != nullptr) { + *decompressedLen = 0U; + } + + uint8_t* data = new uint8_t[len]; + ::memset(data, 0x00U, len); + ::memcpy(data, buffer, len); + + // compression structures + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + // set input data + strm.avail_in = len; + strm.next_in = data; + + // initialize decompression + int ret = inflateInit(&strm); + if (ret != Z_OK) { + LogError(LOG_HOST, "Error initializing ZLIB"); + delete[] data; + return nullptr; + } + + // decompress data + std::vector decompressedData; + uint8_t outbuffer[1024]; + do { + strm.avail_out = sizeof(outbuffer); + strm.next_out = outbuffer; + + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) { + LogError(LOG_HOST, "ZLIB error decompressing compressed data; stream error"); + inflateEnd(&strm); + delete[] data; + return nullptr; + } + + decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out); + } while (ret != Z_STREAM_END); + + // cleanup + inflateEnd(&strm); + + if (decompressedLen != nullptr) { + *decompressedLen = strm.total_out; + } + + uint8_t* decompressed = decompressedData.data(); +#if DEBUG_COMPRESS + 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; +} diff --git a/src/common/zlib/Compression.h b/src/common/zlib/Compression.h new file mode 100644 index 00000000..f740aea1 --- /dev/null +++ b/src/common/zlib/Compression.h @@ -0,0 +1,57 @@ +// 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 compression Compression Routines + * @brief Defines and implements common compression routines. + * @ingroup common + * + * @file Compression.h + * @ingroup compression + * @file Compression.cpp + * @ingroup compression + */ +#if !defined(__COMPRESSION_H__) +#define __COMPRESSION_H__ + +#include "common/Defines.h" + +namespace compress +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief zlib Compression Helper. + * @ingroup compression + */ + class HOST_SW_API Compression { + public: + /** + * @brief Compress the given input buffer using zlib compression. + * @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. + */ + static uint8_t* 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. + */ + static uint8_t* decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen); + }; +} // namespace compress + +#endif // __COMPRESSION_H__ diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index aa29ff42..44e24f81 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -8,7 +8,7 @@ * */ #include "fne/Defines.h" -#include "common/zlib/zlib.h" +#include "common/zlib/Compression.h" #include "common/Log.h" #include "common/Utils.h" #include "network/DiagNetwork.h" @@ -17,6 +17,7 @@ using namespace network; using namespace network::callhandler; +using namespace compress; #include @@ -460,66 +461,13 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) ::memcpy(buffer + offs, pkt.fragments[i], PEER_LINK_BLOCK_SIZE); } - // Utils::dump(1U, "Compressed Payload", buffer, pkt.compressedSize); - - // handle last block - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // set input data - strm.avail_in = pkt.compressedSize; - strm.next_in = buffer; - - // initialize decompression - int ret = inflateInit(&strm); - if (ret != Z_OK) { - LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId); - - pkt.size = 0U; - pkt.compressedSize = 0U; - - delete[] buffer; - for (auto& frag : pkt.fragments) { - if (frag.second != nullptr) - delete[] frag.second; - } - - network->m_peerLinkActPkt.erase(peerId); - break; - } - - // decompress data - std::vector decompressedData; - uint8_t outbuffer[1024]; - do { - strm.avail_out = sizeof(outbuffer); - strm.next_out = outbuffer; - - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u error decompressing peer active list", peerId); - inflateEnd(&strm); - goto pal_lookup_cleanup; // yes - I hate myself; but this is quick - } - - decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out); - } while (ret != Z_STREAM_END); - - // cleanup - inflateEnd(&strm); - // scope is intentional { - uint32_t decompressedLen = strm.total_out; - uint8_t* decompressed = decompressedData.data(); - - // Utils::dump(1U, "Raw Peer Link Active Peer Data", decompressed + 8U, decompressedLen - 8U); + uint32_t decompressedLen = 0U; + uint8_t* decompressed = Compression::decompress(buffer, pkt.compressedSize, &decompressedLen); // check that we got the appropriate data - if (decompressedLen == pkt.size) { + if (decompressedLen == pkt.size && decompressed != nullptr) { std::string payload(decompressed + 8U, decompressed + decompressedLen); // parse JSON body diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 4d2f79cb..7125c24e 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -11,7 +11,7 @@ #include "common/edac/SHA256.h" #include "common/network/json/json.h" #include "common/p25/kmm/KMMFactory.h" -#include "common/zlib/zlib.h" +#include "common/zlib/Compression.h" #include "common/Log.h" #include "common/Utils.h" #include "network/FNENetwork.h" @@ -23,6 +23,7 @@ using namespace network; using namespace network::callhandler; +using namespace compress; #include #include @@ -1916,84 +1917,47 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE ::memset(buffer, 0x00U, len); ::memcpy(buffer, b.str().data(), len); - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // initialize compression - if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) { - LogError(LOG_NET, "PEER %u (%s) error initializing ZLIB", peerId, connection->identity().c_str()); - return; - } - - // set input data - strm.avail_in = len; - strm.next_in = buffer; - - // compress data - std::vector compressedData; - int ret; - do { - // resize the output buffer as needed - compressedData.resize(compressedData.size() + 16384); - strm.avail_out = 16384; - strm.next_out = compressedData.data() + compressedData.size() - 16384; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u (%s) error compressing TGID list", peerId, connection->identity().c_str()); - deflateEnd(&strm); - return; - } - } while (ret != Z_STREAM_END); + uint32_t compressedLen = 0U; + uint8_t* compressed = Compression::compress((uint8_t*)buffer, len, &compressedLen); + + if (compressed != nullptr) { + // transmit TGIDs + uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); + uint32_t offs = 0U; + for (uint8_t i = 0U; i < blockCnt; i++) { + // build dataset + uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); + UInt8Array __payload = std::make_unique(bufSize); + uint8_t* payload = __payload.get(); + ::memset(payload, 0x00U, bufSize); + + if (i == 0U) { + __SET_UINT32(len, payload, 0U); + __SET_UINT32(compressedLen, payload, 4U); + } - // resize the output buffer to the actual compressed data size - compressedData.resize(strm.total_out); + payload[8U] = i; + payload[9U] = blockCnt - 1U; - // cleanup - deflateEnd(&strm); + uint32_t blockSize = PEER_LINK_BLOCK_SIZE; + if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) + blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - uint32_t compressedLen = strm.total_out; - uint8_t* compressed = compressedData.data(); + ::memcpy(payload + 10U, compressed + offs, blockSize); - // Utils::dump(1U, "Compressed Payload", compressed, compressedLen); + if (m_debug) + Utils::dump(1U, "Peer-Link RID Block Payload", payload, bufSize); - // transmit TGIDs - uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); - uint32_t offs = 0U; - for (uint8_t i = 0U; i < blockCnt; i++) { - // build dataset - uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); - UInt8Array __payload = std::make_unique(bufSize); - uint8_t* payload = __payload.get(); - ::memset(payload, 0x00U, bufSize); + offs += PEER_LINK_BLOCK_SIZE; - if (i == 0U) { - __SET_UINT32(len, payload, 0U); - __SET_UINT32(compressedLen, payload, 4U); + writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, + payload, bufSize, 0U, streamId, false, true, true); } - payload[8U] = i; - payload[9U] = blockCnt - 1U; - - uint32_t blockSize = PEER_LINK_BLOCK_SIZE; - if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) - blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - - ::memcpy(payload + 10U, compressed + offs, blockSize); - - if (m_debug) - Utils::dump(1U, "Peer-Link RID Block Payload", payload, bufSize); - - offs += PEER_LINK_BLOCK_SIZE; - - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, - payload, bufSize, 0U, streamId, false, true, true); + connection->lastPing(now); + } else { + LogError(LOG_NET, "PEER %u error compressing RID list", peerId); } - - connection->lastPing(now); } return; @@ -2178,84 +2142,47 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP ::memset(buffer, 0x00U, len); ::memcpy(buffer, b.str().data(), len); - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // initialize compression - if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) { - LogError(LOG_NET, "PEER %u (%s) error initializing ZLIB", peerId, connection->identity().c_str()); - return; - } - - // set input data - strm.avail_in = len; - strm.next_in = buffer; - - // compress data - std::vector compressedData; - int ret; - do { - // resize the output buffer as needed - compressedData.resize(compressedData.size() + 16384); - strm.avail_out = 16384; - strm.next_out = compressedData.data() + compressedData.size() - 16384; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u (%s) error compressing TGID list", peerId, connection->identity().c_str()); - deflateEnd(&strm); - return; - } - } while (ret != Z_STREAM_END); + uint32_t compressedLen = 0U; + uint8_t* compressed = Compression::compress((uint8_t*)buffer, len, &compressedLen); + + if (compressed != nullptr) { + // transmit TGIDs + uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); + uint32_t offs = 0U; + for (uint8_t i = 0U; i < blockCnt; i++) { + // build dataset + uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); + UInt8Array __payload = std::make_unique(bufSize); + uint8_t* payload = __payload.get(); + ::memset(payload, 0x00U, bufSize); + + if (i == 0U) { + __SET_UINT32(len, payload, 0U); + __SET_UINT32(compressedLen, payload, 4U); + } - // resize the output buffer to the actual compressed data size - compressedData.resize(strm.total_out); + payload[8U] = i; + payload[9U] = blockCnt - 1U; - // cleanup - deflateEnd(&strm); + uint32_t blockSize = PEER_LINK_BLOCK_SIZE; + if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) + blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - uint32_t compressedLen = strm.total_out; - uint8_t* compressed = compressedData.data(); + ::memcpy(payload + 10U, compressed + offs, blockSize); - // Utils::dump(1U, "Compressed Payload", compressed, compressedLen); + if (m_debug) + Utils::dump(1U, "Peer-Link TGID Block Payload", payload, bufSize); - // transmit TGIDs - uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); - uint32_t offs = 0U; - for (uint8_t i = 0U; i < blockCnt; i++) { - // build dataset - uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); - UInt8Array __payload = std::make_unique(bufSize); - uint8_t* payload = __payload.get(); - ::memset(payload, 0x00U, bufSize); + offs += PEER_LINK_BLOCK_SIZE; - if (i == 0U) { - __SET_UINT32(len, payload, 0U); - __SET_UINT32(compressedLen, payload, 4U); + writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, + payload, bufSize, 0U, streamId, false, true, true); } - payload[8U] = i; - payload[9U] = blockCnt - 1U; - - uint32_t blockSize = PEER_LINK_BLOCK_SIZE; - if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) - blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - - ::memcpy(payload + 10U, compressed + offs, blockSize); - - if (m_debug) - Utils::dump(1U, "Peer-Link TGID Block Payload", payload, bufSize); - - offs += PEER_LINK_BLOCK_SIZE; - - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, - payload, bufSize, 0U, streamId, false, true, true); + connection->lastPing(now); + } else { + LogError(LOG_NET, "PEER %u error compressing TGID list", peerId); } - - connection->lastPing(now); } return; @@ -2429,85 +2356,48 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) ::memset(buffer, 0x00U, len); ::memcpy(buffer, b.str().data(), len); - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; + uint32_t compressedLen = 0U; + uint8_t* compressed = Compression::compress((uint8_t*)buffer, len, &compressedLen); - // initialize compression - if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) { - LogError(LOG_NET, "PEER %u (%s) error initializing ZLIB", peerId, connection->identity().c_str()); - return; - } + if (compressed != nullptr) { + // transmit PIDs + uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); + uint32_t offs = 0U; + for (uint8_t i = 0U; i < blockCnt; i++) { + // build dataset + uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); + UInt8Array __payload = std::make_unique(bufSize); + uint8_t* payload = __payload.get(); + ::memset(payload, 0x00U, bufSize); - // set input data - strm.avail_in = len; - strm.next_in = buffer; - - // compress data - std::vector compressedData; - int ret; - do { - // resize the output buffer as needed - compressedData.resize(compressedData.size() + 16384); - strm.avail_out = 16384; - strm.next_out = compressedData.data() + compressedData.size() - 16384; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u (%s) error compressing TGID list", peerId, connection->identity().c_str()); - deflateEnd(&strm); - return; - } - } while (ret != Z_STREAM_END); + if (i == 0U) { + __SET_UINT32(len, payload, 0U); + __SET_UINT32(compressedLen, payload, 4U); + } - // resize the output buffer to the actual compressed data size - compressedData.resize(strm.total_out); + payload[8U] = i; + payload[9U] = blockCnt - 1U; - // cleanup - deflateEnd(&strm); + uint32_t blockSize = PEER_LINK_BLOCK_SIZE; + if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) + blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - uint32_t compressedLen = strm.total_out; - uint8_t* compressed = compressedData.data(); + ::memcpy(payload + 10U, compressed + offs, blockSize); - // Utils::dump(1U, "Compressed Payload", compressed, compressedLen); + if (m_debug) + Utils::dump(1U, "Peer-Link Peer List Block Payload", payload, bufSize); - // transmit TGIDs - uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); - uint32_t offs = 0U; - for (uint8_t i = 0U; i < blockCnt; i++) { - // build dataset - uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); - UInt8Array __payload = std::make_unique(bufSize); - uint8_t* payload = __payload.get(); - ::memset(payload, 0x00U, bufSize); + offs += PEER_LINK_BLOCK_SIZE; - if (i == 0U) { - __SET_UINT32(len, payload, 0U); - __SET_UINT32(compressedLen, payload, 4U); + writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, + payload, bufSize, 0U, streamId, false, true, true); } - payload[8U] = i; - payload[9U] = blockCnt - 1U; - - uint32_t blockSize = PEER_LINK_BLOCK_SIZE; - if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) - blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - - ::memcpy(payload + 10U, compressed + offs, blockSize); - - if (m_debug) - Utils::dump(1U, "Peer-Link Peer List Block Payload", payload, bufSize); - - offs += PEER_LINK_BLOCK_SIZE; - - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, - payload, bufSize, 0U, streamId, false, true, true); + connection->lastPing(now); + } else { + LogError(LOG_NET, "PEER %u error compressing PID list", peerId); } - - connection->lastPing(now); - } +} return; } diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 0b06e75a..a771573e 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -9,12 +9,13 @@ */ #include "fne/Defines.h" #include "common/network/json/json.h" -#include "common/zlib/zlib.h" +#include "common/zlib/Compression.h" #include "common/Log.h" #include "common/Utils.h" #include "fne/network/PeerNetwork.h" using namespace network; +using namespace compress; #include #include @@ -120,86 +121,50 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) ::memcpy(buffer + 0U, TAG_PEER_LINK, 4U); ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // initialize compression - if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) { - LogError(LOG_NET, "PEER %u error initializing ZLIB", m_peerId); - return false; - } - - // set input data - strm.avail_in = len; - strm.next_in = (uint8_t*)buffer; - - // compress data - std::vector compressedData; - int ret; - do { - // resize the output buffer as needed - compressedData.resize(compressedData.size() + 16384); - strm.avail_out = 16384; - strm.next_out = compressedData.data() + compressedData.size() - 16384; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u error compressing active peer list", m_peerId); - deflateEnd(&strm); - return false; - } - } while (ret != Z_STREAM_END); + uint32_t compressedLen = 0U; + uint8_t* compressed = Compression::compress((uint8_t*)buffer, len, &compressedLen); + if (compressed != nullptr) + { + // transmit peer link active peer list + uint32_t streamId = createStreamId(); + uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); + uint32_t offs = 0U; + for (uint8_t i = 0U; i < blockCnt; i++) { + // build dataset + uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); + UInt8Array __payload = std::make_unique(bufSize); + uint8_t* payload = __payload.get(); + ::memset(payload, 0x00U, bufSize); + + if (i == 0U) { + __SET_UINT32(len, payload, 0U); + __SET_UINT32(compressedLen, payload, 4U); + } - // resize the output buffer to the actual compressed data size - compressedData.resize(strm.total_out); + payload[8U] = i; + payload[9U] = blockCnt - 1U; - // cleanup - deflateEnd(&strm); + uint32_t blockSize = PEER_LINK_BLOCK_SIZE; + if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) + blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - uint32_t compressedLen = strm.total_out; - uint8_t* compressed = compressedData.data(); + ::memcpy(payload + 10U, compressed + offs, blockSize); - // Utils::dump(1U, "Compressed Payload", compressed, compressedLen); + if (m_debug) + Utils::dump(1U, "Peer-Link Active Peer Payload", payload, bufSize); - // transmit peer link active peer list - uint32_t streamId = createStreamId(); - uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); - uint32_t offs = 0U; - for (uint8_t i = 0U; i < blockCnt; i++) { - // build dataset - uint16_t bufSize = 10U + (PEER_LINK_BLOCK_SIZE); - UInt8Array __payload = std::make_unique(bufSize); - uint8_t* payload = __payload.get(); - ::memset(payload, 0x00U, bufSize); + offs += PEER_LINK_BLOCK_SIZE; - if (i == 0U) { - __SET_UINT32(len, payload, 0U); - __SET_UINT32(compressedLen, payload, 4U); + writeMaster({ NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_ACT_PEER_LIST }, + payload, bufSize, RTP_END_OF_CALL_SEQ, streamId, false, true); + Thread::sleep(60U); // pace block transmission } - payload[8U] = i; - payload[9U] = blockCnt - 1U; - - uint32_t blockSize = PEER_LINK_BLOCK_SIZE; - if (offs + PEER_LINK_BLOCK_SIZE > compressedLen) - blockSize = PEER_LINK_BLOCK_SIZE - ((offs + PEER_LINK_BLOCK_SIZE) - compressedLen); - - ::memcpy(payload + 10U, compressed + offs, blockSize); - - if (m_debug) - Utils::dump(1U, "Peer-Link Active Peer Payload", payload, bufSize); - - offs += PEER_LINK_BLOCK_SIZE; - - writeMaster({ NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_ACT_PEER_LIST }, - payload, bufSize, RTP_END_OF_CALL_SEQ, streamId, false, true); - Thread::sleep(60U); // pace block transmission + return true; + } else { + LogError(LOG_NET, "PEER %u error compressing active peer list", m_peerId); + return false; } - - return true; } return false; @@ -245,61 +210,14 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco ::memcpy(m_tgidBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE); // Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE); - // Utils::dump(1U, "Compressed Payload", m_tgidBuffer, m_tgidCompressedSize); - - // handle last block - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // set input data - strm.avail_in = m_tgidCompressedSize; - strm.next_in = m_tgidBuffer; - - // initialize decompression - int ret = inflateInit(&strm); - if (ret != Z_OK) { - LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId); - - m_tgidSize = 0U; - m_tgidCompressedSize = 0U; - if (m_tgidBuffer != nullptr) - delete[] m_tgidBuffer; - m_tgidBuffer = nullptr; - break; - } - - // decompress data - std::vector decompressedData; - uint8_t outbuffer[1024]; - do { - strm.avail_out = sizeof(outbuffer); - strm.next_out = outbuffer; - - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u error decompressing TGID list", peerId); - inflateEnd(&strm); - goto tid_lookup_cleanup; // yes - I hate myself; but this is quick - } - - decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out); - } while (ret != Z_STREAM_END); - - // cleanup - inflateEnd(&strm); // scope is intentional { - uint32_t decompressedLen = strm.total_out; - uint8_t* decompressed = decompressedData.data(); - - // Utils::dump(1U, "Raw TGID Data", decompressed, decompressedLen); + uint32_t decompressedLen = 0U; + uint8_t* decompressed = Compression::decompress(m_tgidBuffer, m_tgidCompressedSize, &decompressedLen); // check that we got the appropriate data - if (decompressedLen == m_tgidSize) { + if (decompressedLen == m_tgidSize && decompressed != nullptr) { if (m_tidLookup == nullptr) { LogError(LOG_NET, "Talkgroup ID lookup not available yet."); goto tid_lookup_cleanup; // yes - I hate myself; but this is quick @@ -383,61 +301,14 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco ::memcpy(m_ridBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE); // Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE); - // Utils::dump(1U, "Compressed Payload", m_ridBuffer, m_ridCompressedSize); - - // handle last block - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // set input data - strm.avail_in = m_ridCompressedSize; - strm.next_in = m_ridBuffer; - - // initialize decompression - int ret = inflateInit(&strm); - if (ret != Z_OK) { - LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId); - - m_ridSize = 0U; - m_ridCompressedSize = 0U; - if (m_ridBuffer != nullptr) - delete[] m_ridBuffer; - m_ridBuffer = nullptr; - break; - } - - // decompress data - std::vector decompressedData; - uint8_t outbuffer[1024]; - do { - strm.avail_out = sizeof(outbuffer); - strm.next_out = outbuffer; - - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u error decompressing RID list", peerId); - inflateEnd(&strm); - goto rid_lookup_cleanup; // yes - I hate myself; but this is quick - } - - decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out); - } while (ret != Z_STREAM_END); - - // cleanup - inflateEnd(&strm); // scope is intentional { - uint32_t decompressedLen = strm.total_out; - uint8_t* decompressed = decompressedData.data(); - - // Utils::dump(1U, "Raw RID Data", decompressed, decompressedLen); + uint32_t decompressedLen = 0U; + uint8_t* decompressed = Compression::decompress(m_ridBuffer, m_ridCompressedSize, &decompressedLen); // check that we got the appropriate data - if (decompressedLen == m_ridSize) { + if (decompressedLen == m_ridSize && decompressed != nullptr) { if (m_ridLookup == nullptr) { LogError(LOG_NET, "Radio ID lookup not available yet."); goto rid_lookup_cleanup; // yes - I hate myself; but this is quick @@ -521,61 +392,14 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco ::memcpy(m_pidBuffer + offs, data + 10U, PEER_LINK_BLOCK_SIZE); // Utils::dump(1U, "Block Payload", data, 10U + PEER_LINK_BLOCK_SIZE); - // Utils::dump(1U, "Compressed Payload", m_pidBuffer, m_pidCompressedSize); - - // handle last block - // compression structures - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // set input data - strm.avail_in = m_pidCompressedSize; - strm.next_in = m_pidBuffer; - - // initialize decompression - int ret = inflateInit(&strm); - if (ret != Z_OK) { - LogError(LOG_NET, "PEER %u error initializing ZLIB", peerId); - - m_pidSize = 0U; - m_pidCompressedSize = 0U; - if (m_pidBuffer != nullptr) - delete[] m_pidBuffer; - m_pidBuffer = nullptr; - break; - } - - // decompress data - std::vector decompressedData; - uint8_t outbuffer[1024]; - do { - strm.avail_out = sizeof(outbuffer); - strm.next_out = outbuffer; - - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) { - LogError(LOG_NET, "PEER %u error decompressing peer list", peerId); - inflateEnd(&strm); - goto pid_lookup_cleanup; // yes - I hate myself; but this is quick - } - - decompressedData.insert(decompressedData.end(), outbuffer, outbuffer + sizeof(outbuffer) - strm.avail_out); - } while (ret != Z_STREAM_END); - - // cleanup - inflateEnd(&strm); // scope is intentional { - uint32_t decompressedLen = strm.total_out; - uint8_t* decompressed = decompressedData.data(); - - // Utils::dump(1U, "Raw Peer List Data", decompressed, decompressedLen); + uint32_t decompressedLen = 0U; + uint8_t* decompressed = Compression::decompress(m_pidBuffer, m_pidCompressedSize, &decompressedLen); // check that we got the appropriate data - if (decompressedLen == m_pidSize) { + if (decompressedLen == m_pidSize && decompressed != nullptr) { if (m_pidLookup == nullptr) { LogError(LOG_NET, "Peer ID lookup not available yet."); goto pid_lookup_cleanup; // yes - I hate myself; but this is quick From 00d4492d0b2df1bc2b497d175794289f097b8ac7 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 24 Apr 2025 10:12:26 -0400 Subject: [PATCH 26/41] stylecop file formatting; --- 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 7125c24e..2183ce3e 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -2397,7 +2397,7 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) } else { LogError(LOG_NET, "PEER %u error compressing PID list", peerId); } -} + } return; } From 89d7e3ad89833e7e6d3518da47959f66f465f797 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 24 Apr 2025 11:59:11 -0400 Subject: [PATCH 27/41] add table locking and remove at find; --- src/common/lookups/AffiliationLookup.cpp | 87 +++++++++++++++++------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 0c9764fc..630d26ce 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -98,11 +98,14 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) m_unitRegTimers[srcId].stop(); // remove dynamic unit registration table entry + m_unitRegTable.lock(true); if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { auto it = std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId); + m_unitRegTable.unlock(); m_unitRegTable.erase(it); ret = true; } + m_unitRegTable.unlock(); if (ret) { if (m_unitDereg != nullptr) { @@ -161,10 +164,13 @@ uint32_t AffiliationLookup::unitRegTimer(uint32_t srcId) bool AffiliationLookup::isUnitReg(uint32_t srcId) const { // lookup dynamic unit registration table entry + m_unitRegTable.lock(false); if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { + m_unitRegTable.unlock(); return true; } else { + m_unitRegTable.unlock(); return false; } } @@ -224,11 +230,15 @@ bool AffiliationLookup::groupUnaff(uint32_t srcId) bool AffiliationLookup::hasGroupAff(uint32_t dstId) const { + // lookup dynamic affiliation table entry + m_grpAffTable.lock(false); for (auto entry : m_grpAffTable) { if (entry.second == dstId) { + m_grpAffTable.unlock(); return true; } } + m_grpAffTable.unlock(); return false; } @@ -238,15 +248,15 @@ bool AffiliationLookup::hasGroupAff(uint32_t dstId) const bool AffiliationLookup::isGroupAff(uint32_t srcId, uint32_t dstId) const { // lookup dynamic affiliation table entry + m_grpAffTable.lock(false); if (m_grpAffTable.find(srcId) != m_grpAffTable.end()) { uint32_t tblDstId = m_grpAffTable.at(srcId); if (tblDstId == dstId) { + m_grpAffTable.unlock(); return true; } - else { - return false; - } } + m_grpAffTable.unlock(); return false; } @@ -403,11 +413,14 @@ bool AffiliationLookup::isChBusy(uint32_t chNo) const } // lookup dynamic channel grant table entry + m_grantChTable.lock(false); for (auto entry : m_grantChTable) { if (entry.second == chNo) { + m_grantChTable.unlock(); return true; } } + m_grantChTable.unlock(); return false; } @@ -421,17 +434,19 @@ bool AffiliationLookup::isGranted(uint32_t dstId) const } // lookup dynamic channel grant table entry - try { - uint32_t chNo = m_grantChTable.at(dstId); - if (chNo != 0U) { + m_grantChTable.lock(false); + for (auto entry : m_grantChTable) { + uint32_t gntDstId = entry.first; + uint32_t chNo = entry.second; + + if (gntDstId == dstId && chNo != 0U) { + m_grantChTable.unlock(); return true; } - else { - return false; - } - } catch (...) { - return false; } + m_grantChTable.unlock(); + + return false; } /* Helper to determine if the destination ID is network granted. */ @@ -442,13 +457,20 @@ bool AffiliationLookup::isGroup(uint32_t dstId) const return true; } - // lookup dynamic channel grant table entry - try { - bool uu = m_uuGrantedTable.at(dstId); - return !uu; - } catch (...) { - return true; + // lookup U-U grant flag table entry + m_uuGrantedTable.lock(false); + for (auto entry : m_uuGrantedTable) { + uint32_t gntDstId = entry.first; + bool uu = entry.second; + + if (gntDstId == dstId) { + m_uuGrantedTable.unlock(); + return !uu; + } } + m_uuGrantedTable.unlock(); + + return true; } /* Helper to determine if the destination ID is network granted. */ @@ -459,13 +481,20 @@ bool AffiliationLookup::isNetGranted(uint32_t dstId) const return false; } - // lookup dynamic channel grant table entry - try { - bool net = m_netGrantedTable.at(dstId); - return net; - } catch (...) { - return false; + // lookup net granted flag table entry + m_netGrantedTable.lock(false); + for (auto entry : m_netGrantedTable) { + uint32_t gntDstId = entry.first; + bool net = entry.second; + + if (gntDstId == dstId) { + m_netGrantedTable.unlock(); + return net; + } } + m_netGrantedTable.unlock(); + + return false; } /* Helper to get the channel granted for the given destination ID. */ @@ -487,10 +516,15 @@ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId) uint32_t AffiliationLookup::getGrantedDstByCh(uint32_t chNo) { + // lookup dynamic channel grant table entry + m_grantChTable.lock(false); for (auto entry : m_grantChTable) { - if (entry.second == chNo) + if (entry.second == chNo) { + m_grantChTable.unlock(); return entry.first; + } } + m_grantChTable.unlock(); return 0U; } @@ -503,12 +537,15 @@ uint32_t AffiliationLookup::getGrantedBySrcId(uint32_t srcId) return 0U; } - // lookup dynamic channel grant table entry + // lookup dynamic channel grant source table entry + m_grantSrcIdTable.lock(false); for (auto entry : m_grantSrcIdTable) { if (entry.second == srcId) { + m_grantSrcIdTable.unlock(); return entry.first; } } + m_grantSrcIdTable.unlock(); return 0U; } From 046e65cfd1d10a89b4c6aad48375b116ce20b4f4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 24 Apr 2025 15:23:30 -0400 Subject: [PATCH 28/41] deduplicate implementation; --- src/common/concurrent/concurrent_lock.h | 101 +++++++++++++++++++++++ src/common/concurrent/deque.h | 73 +++++----------- src/common/concurrent/map.h | 82 ++++-------------- src/common/concurrent/unordered_map.h | 82 ++++-------------- src/common/concurrent/vector.h | 96 +++++---------------- src/common/lookups/AffiliationLookup.cpp | 2 +- 6 files changed, 169 insertions(+), 267 deletions(-) create mode 100644 src/common/concurrent/concurrent_lock.h diff --git a/src/common/concurrent/concurrent_lock.h b/src/common/concurrent/concurrent_lock.h new file mode 100644 index 00000000..0352d3a2 --- /dev/null +++ b/src/common/concurrent/concurrent_lock.h @@ -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 + * + */ +/** + * @file concurrent_lock.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_CONCURRENT_LOCK_H__) +#define __CONCURRENCY_CONCURRENT_LOCK_H__ + +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Base class for a concurrently locked container. + * @ingroup concurrency + */ + class concurrent_lock + { + public: + /** + * @brief Initializes a new instance of the concurrent_lock class. + */ + concurrent_lock() : + m_mutex(), + m_locked(false) + { + /* stub */ + } + + /** + * @brief Locks the object. + * @param readLock Flag indicating whether or not to use read locking. + */ + void lock(bool readLock = true) const { __lock(readLock); } + /** + * @brief Unlocks the object. + */ + void unlock() const { __unlock(); } + /** + * @brief Flag indicating whether or not the object is read locked. + * @return bool True if the object is read locked, false otherwise. + */ + bool isReadLocked() const { return m_locked; } + /** + * @brief Spins until the object is unlocked. + */ + void spinlock() const { __spinlock(); } + + protected: + mutable std::mutex m_mutex; //! Mutex used for change locking. + mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + /** + * @brief Lock the object. + * @param readLock Flag indicating whether or not to use read locking. + */ + inline void __lock(bool readLock = true) const + { + m_mutex.lock(); + if (readLock) + m_locked = true; + } + + /** + * @brief Unlock the object. + */ + inline void __unlock() const + { + m_mutex.unlock(); + m_locked = false; + } + + /** + * @brief Spins until the object is read unlocked. + */ + inline void __spinlock() const + { + if (m_locked) { + while (m_locked) + Thread::sleep(1U); + } + } + }; +} // namespace concurrent + +#endif // __CONCURRENCY_CONCURRENT_LOCK_H__ diff --git a/src/common/concurrent/deque.h b/src/common/concurrent/deque.h index 431f7535..d203038f 100644 --- a/src/common/concurrent/deque.h +++ b/src/common/concurrent/deque.h @@ -14,6 +14,7 @@ #if !defined(__CONCURRENCY_DEQUE_H__) #define __CONCURRENCY_DEQUE_H__ +#include "common/concurrent/concurrent_lock.h" #include "common/Thread.h" #include @@ -30,7 +31,7 @@ namespace concurrent * @ingroup concurrency */ template - class deque + class deque : public concurrent_lock { using __std = std::deque; public: @@ -40,9 +41,7 @@ namespace concurrent /** * @brief Initializes a new instance of the deque class. */ - deque() : - m_mutex(), - m_locked(false), + deque() : concurrent_lock(), m_deque() { /* stub */ @@ -51,9 +50,7 @@ namespace concurrent * @brief Initializes a new instance of the deque class. * @param size Initial size of the deque. */ - deque(size_t size) : - m_mutex(), - m_locked(false), + deque(size_t size) : concurrent_lock(), m_deque(size) { /* stub */ @@ -72,7 +69,7 @@ namespace concurrent */ deque& operator=(const deque& other) { - __lock(); + __lock(false); m_deque = other.m_deque; __unlock(); return *this; @@ -83,7 +80,7 @@ namespace concurrent */ deque& operator=(const std::deque& other) { - __lock(); + __lock(false); m_deque = other; __unlock(); return *this; @@ -94,7 +91,7 @@ namespace concurrent */ deque& operator=(deque& other) { - __lock(); + __lock(false); m_deque = other.m_deque; __unlock(); return *this; @@ -105,7 +102,7 @@ namespace concurrent */ deque& operator=(std::deque& other) { - __lock(); + __lock(false); m_deque = other; __unlock(); return *this; @@ -118,7 +115,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(); + __lock(false); m_deque.assign(size, value); __unlock(); } @@ -206,7 +203,7 @@ namespace concurrent */ void resize(size_t size) { - __lock(); + __lock(false); m_deque.resize(size); __unlock(); } @@ -280,7 +277,7 @@ namespace concurrent */ void push_back(const T& value) { - __lock(); + __lock(false); m_deque.push_back(value); __unlock(); } @@ -290,7 +287,7 @@ namespace concurrent */ void push_front(const T& value) { - __lock(); + __lock(false); m_deque.push_front(value); __unlock(); } @@ -299,7 +296,7 @@ namespace concurrent */ void pop_back() { - __lock(); + __lock(false); m_deque.pop_back(); __unlock(); } @@ -308,7 +305,7 @@ namespace concurrent */ void pop_front() { - __lock(); + __lock(false); m_deque.pop_front(); __unlock(); } @@ -356,7 +353,7 @@ namespace concurrent */ void erase(size_t index) { - __lock(); + __lock(false); m_deque.erase(m_deque.begin() + index); __unlock(); } @@ -366,7 +363,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(); + __lock(false); m_deque.erase(position); __unlock(); } @@ -377,7 +374,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(); + __lock(false); m_deque.erase(first, last); __unlock(); } @@ -388,7 +385,7 @@ namespace concurrent */ void swap(deque& other) { - __lock(); + __lock(false); m_deque.swap(other.m_deque); __unlock(); } @@ -398,7 +395,7 @@ namespace concurrent */ void clear() { - __lock(); + __lock(false); m_deque.clear(); __unlock(); } @@ -423,39 +420,7 @@ namespace concurrent } private: - mutable std::mutex m_mutex; //! Mutex used for change locking. - mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. - std::deque m_deque; - - /** - * @brief Lock the vector. - * @param readLock Flag indicating whether or not to use read locking. - */ - inline void __lock(bool readLock = false) const - { - m_mutex.lock(); - if (!readLock) - m_locked = true; - } - /** - * @brief Unlocks the deque. - */ - inline void __unlock() const - { - m_mutex.unlock(); - m_locked = false; - } - /** - * @brief Spins until the deque is read unlocked. - */ - inline void __spinlock() const - { - if (m_locked) { - while (m_locked) - Thread::sleep(1U); - } - } }; } // namespace concurrent diff --git a/src/common/concurrent/map.h b/src/common/concurrent/map.h index 2f6c08a2..a8eda6f8 100644 --- a/src/common/concurrent/map.h +++ b/src/common/concurrent/map.h @@ -14,6 +14,7 @@ #if !defined(__CONCURRENCY_MAP_H__) #define __CONCURRENCY_MAP_H__ +#include "common/concurrent/concurrent_lock.h" #include "common/Thread.h" #include @@ -30,7 +31,7 @@ namespace concurrent * @ingroup concurrency */ template - class map + class map : public concurrent_lock { using __std = std::map; public: @@ -40,9 +41,7 @@ namespace concurrent /** * @brief Initializes a new instance of the map class. */ - map() : - m_mutex(), - m_locked(false), + map() : concurrent_lock(), m_map() { /* stub */ @@ -51,9 +50,7 @@ namespace concurrent * @brief Initializes a new instance of the map class. * @param size Initial size of the map. */ - map(size_t size) : - m_mutex(), - m_locked(false), + map(size_t size) : concurrent_lock(), m_map(size) { /* stub */ @@ -72,7 +69,7 @@ namespace concurrent */ map& operator=(const map& other) { - __lock(); + __lock(false); m_map = other.m_map; __unlock(); return *this; @@ -83,7 +80,7 @@ namespace concurrent */ map& operator=(const std::map& other) { - __lock(); + __lock(false); m_map = other; __unlock(); return *this; @@ -94,7 +91,7 @@ namespace concurrent */ map& operator=(map& other) { - __lock(); + __lock(false); m_map = other.m_map; __unlock(); return *this; @@ -105,7 +102,7 @@ namespace concurrent */ map& operator=(std::map& other) { - __lock(); + __lock(false); m_map = other; __unlock(); return *this; @@ -118,7 +115,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(); + __lock(false); m_map.assign(size, value); __unlock(); } @@ -271,7 +268,7 @@ namespace concurrent */ void insert(const Key& key, const T& value) { - __lock(); + __lock(false); m_map.insert({key, value}); __unlock(); } @@ -282,7 +279,7 @@ namespace concurrent */ void erase(const Key& key) { - __lock(); + __lock(false); m_map.erase(key); __unlock(); } @@ -292,7 +289,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(); + __lock(false); m_map.erase(position); __unlock(); } @@ -303,7 +300,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(); + __lock(false); m_map.erase(first, last); __unlock(); } @@ -313,7 +310,7 @@ namespace concurrent */ void clear() { - __lock(); + __lock(false); m_map.clear(); __unlock(); } @@ -371,59 +368,8 @@ namespace concurrent return m_map; } - /** - * @brief Locks the map. - * @param readLock Flag indicating whether or not to use read locking. - */ - void lock(bool readLock = false) const { __lock(readLock); } - /** - * @brief Unlocks the map. - */ - void unlock() const { __unlock(); } - /** - * @brief Flag indicating whether or not the map is read locked. - * @return bool True if the map is read locked, false otherwise. - */ - bool isReadLocked() const { return m_locked; } - /** - * @brief Spins until the map is unlocked. - */ - void spinlock() const { __spinlock(); } - private: - mutable std::mutex m_mutex; //! Mutex used for change locking. - mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. - std::map m_map; - - /** - * @brief Lock the vector. - * @param readLock Flag indicating whether or not to use read locking. - */ - inline void __lock(bool readLock = false) const - { - m_mutex.lock(); - if (readLock) - m_locked = true; - } - /** - * @brief Unlocks the map. - */ - inline void __unlock() const - { - m_mutex.unlock(); - m_locked = false; - } - /** - * @brief Spins until the map is read unlocked. - */ - inline void __spinlock() const - { - if (m_locked) { - while (m_locked) - Thread::sleep(1U); - } - } }; } // namespace concurrent diff --git a/src/common/concurrent/unordered_map.h b/src/common/concurrent/unordered_map.h index c9e7afbf..af4a98c3 100644 --- a/src/common/concurrent/unordered_map.h +++ b/src/common/concurrent/unordered_map.h @@ -14,6 +14,7 @@ #if !defined(__CONCURRENCY_UNORDERED_MAP_H__) #define __CONCURRENCY_UNORDERED_MAP_H__ +#include "common/concurrent/concurrent_lock.h" #include "common/Thread.h" #include @@ -30,7 +31,7 @@ namespace concurrent * @ingroup concurrency */ template - class unordered_map + class unordered_map : public concurrent_lock { using __std = std::unordered_map; public: @@ -40,9 +41,7 @@ namespace concurrent /** * @brief Initializes a new instance of the unordered_map class. */ - unordered_map() : - m_mutex(), - m_locked(false), + unordered_map() : concurrent_lock(), m_map() { /* stub */ @@ -51,9 +50,7 @@ namespace concurrent * @brief Initializes a new instance of the unordered_map class. * @param size Initial size of the unordered_map. */ - unordered_map(size_t size) : - m_mutex(), - m_locked(false), + unordered_map(size_t size) : concurrent_lock(), m_map(size) { /* stub */ @@ -72,7 +69,7 @@ namespace concurrent */ unordered_map& operator=(const unordered_map& other) { - __lock(); + __lock(false); m_map = other.m_map; __unlock(); return *this; @@ -83,7 +80,7 @@ namespace concurrent */ unordered_map& operator=(const std::unordered_map& other) { - __lock(); + __lock(false); m_map = other; __unlock(); return *this; @@ -94,7 +91,7 @@ namespace concurrent */ unordered_map& operator=(unordered_map& other) { - __lock(); + __lock(false); m_map = other.m_map; __unlock(); return *this; @@ -105,7 +102,7 @@ namespace concurrent */ unordered_map& operator=(std::unordered_map& other) { - __lock(); + __lock(false); m_map = other; __unlock(); return *this; @@ -118,7 +115,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(); + __lock(false); m_map.assign(size, value); __unlock(); } @@ -271,7 +268,7 @@ namespace concurrent */ void insert(const Key& key, const T& value) { - __lock(); + __lock(false); m_map.insert({key, value}); __unlock(); } @@ -282,7 +279,7 @@ namespace concurrent */ void erase(const Key& key) { - __lock(); + __lock(false); m_map.erase(key); __unlock(); } @@ -292,7 +289,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(); + __lock(false); m_map.erase(position); __unlock(); } @@ -303,7 +300,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(); + __lock(false); m_map.erase(first, last); __unlock(); } @@ -313,7 +310,7 @@ namespace concurrent */ void clear() { - __lock(); + __lock(false); m_map.clear(); __unlock(); } @@ -371,59 +368,8 @@ namespace concurrent return m_map; } - /** - * @brief Locks the unordered_map. - * @param readLock Flag indicating whether or not to use read locking. - */ - void lock(bool readLock = true) const { __lock(readLock); } - /** - * @brief Unlocks the unordered_map. - */ - void unlock() const { __unlock(); } - /** - * @brief Flag indicating whether or not the unordered_map is read locked. - * @return bool True if the unordered_map is read locked, false otherwise. - */ - bool isReadLocked() const { return m_locked; } - /** - * @brief Spins until the unordered_map is unlocked. - */ - void spinlock() const { __spinlock(); } - private: - mutable std::mutex m_mutex; //! Mutex used for change locking. - mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. - std::unordered_map m_map; - - /** - * @brief Lock the vector. - * @param readLock Flag indicating whether or not to use read locking. - */ - inline void __lock(bool readLock = true) const - { - m_mutex.lock(); - if (readLock) - m_locked = true; - } - /** - * @brief Unlocks the unordered_map. - */ - inline void __unlock() const - { - m_mutex.unlock(); - m_locked = false; - } - /** - * @brief Spins until the unordered_map is read unlocked. - */ - inline void __spinlock() const - { - if (m_locked) { - while (m_locked) - Thread::sleep(1U); - } - } }; } // namespace concurrent diff --git a/src/common/concurrent/vector.h b/src/common/concurrent/vector.h index aa377a2d..0af136e9 100644 --- a/src/common/concurrent/vector.h +++ b/src/common/concurrent/vector.h @@ -18,7 +18,8 @@ #if !defined(__CONCURRENCY_VECTOR_H__) #define __CONCURRENCY_VECTOR_H__ -#include +#include "common/concurrent/concurrent_lock.h" +#include "common/Thread.h" #include #include @@ -34,7 +35,7 @@ namespace concurrent * @ingroup concurrency */ template - class vector + class vector : public concurrent_lock { using __std = std::vector; public: @@ -44,9 +45,7 @@ namespace concurrent /** * @brief Initializes a new instance of the vector class. */ - vector() : - m_mutex(), - m_locked(false), + vector() : concurrent_lock(), m_vector() { /* stub */ @@ -55,9 +54,7 @@ namespace concurrent * @brief Initializes a new instance of the vector class. * @param size Initial size of the vector. */ - vector(size_t size) : - m_mutex(), - m_locked(false), + vector(size_t size) : concurrent_lock(), m_vector(size) { /* stub */ @@ -76,7 +73,7 @@ namespace concurrent */ vector& operator=(const vector& other) { - __lock(); + __lock(false); m_vector = other.m_vector; __unlock(); return *this; @@ -87,7 +84,7 @@ namespace concurrent */ vector& operator=(const std::vector& other) { - __lock(); + __lock(false); m_vector = other; __unlock(); return *this; @@ -98,7 +95,7 @@ namespace concurrent */ vector& operator=(vector& other) { - __lock(); + __lock(false); m_vector = other.m_vector; __unlock(); return *this; @@ -109,7 +106,7 @@ namespace concurrent */ vector& operator=(std::vector& other) { - __lock(); + __lock(false); m_vector = other; __unlock(); return *this; @@ -122,7 +119,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(); + __lock(false); m_vector.assign(size, value); __unlock(); } @@ -210,7 +207,7 @@ namespace concurrent */ void resize(size_t size) { - __lock(); + __lock(false); m_vector.resize(size); __unlock(); } @@ -322,7 +319,7 @@ namespace concurrent */ void push_back(const T& value) { - __lock(); + __lock(false); m_vector.push_back(value); __unlock(); } @@ -332,7 +329,7 @@ namespace concurrent */ void push_back(T&& value) { - __lock(); + __lock(false); m_vector.push_back(std::move(value)); __unlock(); } @@ -342,7 +339,7 @@ namespace concurrent */ void pop_back() { - __lock(); + __lock(false); m_vector.pop_back(); __unlock(); } @@ -355,7 +352,7 @@ namespace concurrent */ iterator insert(iterator position, const T& value) { - __lock(); + __lock(false); auto it = m_vector.insert(position, value); __unlock(); return it; @@ -367,7 +364,7 @@ namespace concurrent */ void erase(size_t index) { - __lock(); + __lock(false); m_vector.erase(m_vector.begin() + index); __unlock(); } @@ -377,7 +374,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(); + __lock(false); m_vector.erase(position); __unlock(); } @@ -388,7 +385,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(); + __lock(false); m_vector.erase(first, last); __unlock(); } @@ -399,7 +396,7 @@ namespace concurrent */ void swap(vector& other) { - __lock(); + __lock(false); m_vector.swap(other.m_vector); __unlock(); } @@ -409,7 +406,7 @@ namespace concurrent */ void clear() { - __lock(); + __lock(false); m_vector.clear(); __unlock(); } @@ -433,61 +430,8 @@ namespace concurrent return m_vector; } - /** - * @brief Locks the vector. - * @param readLock Flag indicating whether or not to use read locking. - */ - void lock(bool readLock = true) const { __lock(readLock); } - /** - * @brief Unlocks the vector. - */ - void unlock() const { __unlock(); } - /** - * @brief Flag indicating whether or not the vector is read locked. - * @return bool True if the vector is read locked, false otherwise. - */ - bool isReadLocked() const { return m_locked; } - /** - * @brief Spins until the vector is unlocked. - */ - void spinlock() const { __spinlock(); } - private: - mutable std::mutex m_mutex; //! Mutex used for change locking. - mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. - std::vector m_vector; - - /** - * @brief Lock the vector. - * @param readLock Flag indicating whether or not to use read locking. - */ - inline void __lock(bool readLock = true) const - { - m_mutex.lock(); - if (readLock) - m_locked = true; - } - - /** - * @brief Unlock the vector. - */ - inline void __unlock() const - { - m_mutex.unlock(); - m_locked = false; - } - - /** - * @brief Spins until the vector is read unlocked. - */ - inline void __spinlock() const - { - if (m_locked) { - while (m_locked) - Thread::sleep(1U); - } - } }; } // namespace concurrent diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 630d26ce..d0b53033 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -98,7 +98,7 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) m_unitRegTimers[srcId].stop(); // remove dynamic unit registration table entry - m_unitRegTable.lock(true); + m_unitRegTable.lock(false); if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { auto it = std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId); m_unitRegTable.unlock(); From dddde23d43a08829091240124cf0a937a32edd02 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 24 Apr 2025 21:02:22 -0400 Subject: [PATCH 29/41] cleanup unused label; change FrameQueue's unordered_map to a concurrent one; fix incorrect setting of __lock() for concurrent containers; --- src/common/concurrent/deque.h | 30 +++++++++++++-------------- src/common/concurrent/map.h | 20 +++++++++--------- src/common/concurrent/unordered_map.h | 20 +++++++++--------- src/common/concurrent/vector.h | 30 +++++++++++++-------------- src/common/network/FrameQueue.h | 4 ++-- src/fne/network/DiagNetwork.cpp | 1 - 6 files changed, 52 insertions(+), 53 deletions(-) diff --git a/src/common/concurrent/deque.h b/src/common/concurrent/deque.h index d203038f..9af8f3d9 100644 --- a/src/common/concurrent/deque.h +++ b/src/common/concurrent/deque.h @@ -69,7 +69,7 @@ namespace concurrent */ deque& operator=(const deque& other) { - __lock(false); + __lock(); m_deque = other.m_deque; __unlock(); return *this; @@ -80,7 +80,7 @@ namespace concurrent */ deque& operator=(const std::deque& other) { - __lock(false); + __lock(); m_deque = other; __unlock(); return *this; @@ -91,7 +91,7 @@ namespace concurrent */ deque& operator=(deque& other) { - __lock(false); + __lock(); m_deque = other.m_deque; __unlock(); return *this; @@ -102,7 +102,7 @@ namespace concurrent */ deque& operator=(std::deque& other) { - __lock(false); + __lock(); m_deque = other; __unlock(); return *this; @@ -115,7 +115,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(false); + __lock(); m_deque.assign(size, value); __unlock(); } @@ -203,7 +203,7 @@ namespace concurrent */ void resize(size_t size) { - __lock(false); + __lock(); m_deque.resize(size); __unlock(); } @@ -277,7 +277,7 @@ namespace concurrent */ void push_back(const T& value) { - __lock(false); + __lock(); m_deque.push_back(value); __unlock(); } @@ -287,7 +287,7 @@ namespace concurrent */ void push_front(const T& value) { - __lock(false); + __lock(); m_deque.push_front(value); __unlock(); } @@ -296,7 +296,7 @@ namespace concurrent */ void pop_back() { - __lock(false); + __lock(); m_deque.pop_back(); __unlock(); } @@ -305,7 +305,7 @@ namespace concurrent */ void pop_front() { - __lock(false); + __lock(); m_deque.pop_front(); __unlock(); } @@ -353,7 +353,7 @@ namespace concurrent */ void erase(size_t index) { - __lock(false); + __lock(); m_deque.erase(m_deque.begin() + index); __unlock(); } @@ -363,7 +363,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(false); + __lock(); m_deque.erase(position); __unlock(); } @@ -374,7 +374,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(false); + __lock(); m_deque.erase(first, last); __unlock(); } @@ -385,7 +385,7 @@ namespace concurrent */ void swap(deque& other) { - __lock(false); + __lock(); m_deque.swap(other.m_deque); __unlock(); } @@ -395,7 +395,7 @@ namespace concurrent */ void clear() { - __lock(false); + __lock(); m_deque.clear(); __unlock(); } diff --git a/src/common/concurrent/map.h b/src/common/concurrent/map.h index a8eda6f8..83116080 100644 --- a/src/common/concurrent/map.h +++ b/src/common/concurrent/map.h @@ -69,7 +69,7 @@ namespace concurrent */ map& operator=(const map& other) { - __lock(false); + __lock(); m_map = other.m_map; __unlock(); return *this; @@ -80,7 +80,7 @@ namespace concurrent */ map& operator=(const std::map& other) { - __lock(false); + __lock(); m_map = other; __unlock(); return *this; @@ -91,7 +91,7 @@ namespace concurrent */ map& operator=(map& other) { - __lock(false); + __lock(); m_map = other.m_map; __unlock(); return *this; @@ -102,7 +102,7 @@ namespace concurrent */ map& operator=(std::map& other) { - __lock(false); + __lock(); m_map = other; __unlock(); return *this; @@ -115,7 +115,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(false); + __lock(); m_map.assign(size, value); __unlock(); } @@ -268,7 +268,7 @@ namespace concurrent */ void insert(const Key& key, const T& value) { - __lock(false); + __lock(); m_map.insert({key, value}); __unlock(); } @@ -279,7 +279,7 @@ namespace concurrent */ void erase(const Key& key) { - __lock(false); + __lock(); m_map.erase(key); __unlock(); } @@ -289,7 +289,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(false); + __lock(); m_map.erase(position); __unlock(); } @@ -300,7 +300,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(false); + __lock(); m_map.erase(first, last); __unlock(); } @@ -310,7 +310,7 @@ namespace concurrent */ void clear() { - __lock(false); + __lock(); m_map.clear(); __unlock(); } diff --git a/src/common/concurrent/unordered_map.h b/src/common/concurrent/unordered_map.h index af4a98c3..0a131c38 100644 --- a/src/common/concurrent/unordered_map.h +++ b/src/common/concurrent/unordered_map.h @@ -69,7 +69,7 @@ namespace concurrent */ unordered_map& operator=(const unordered_map& other) { - __lock(false); + __lock(); m_map = other.m_map; __unlock(); return *this; @@ -80,7 +80,7 @@ namespace concurrent */ unordered_map& operator=(const std::unordered_map& other) { - __lock(false); + __lock(); m_map = other; __unlock(); return *this; @@ -91,7 +91,7 @@ namespace concurrent */ unordered_map& operator=(unordered_map& other) { - __lock(false); + __lock(); m_map = other.m_map; __unlock(); return *this; @@ -102,7 +102,7 @@ namespace concurrent */ unordered_map& operator=(std::unordered_map& other) { - __lock(false); + __lock(); m_map = other; __unlock(); return *this; @@ -115,7 +115,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(false); + __lock(); m_map.assign(size, value); __unlock(); } @@ -268,7 +268,7 @@ namespace concurrent */ void insert(const Key& key, const T& value) { - __lock(false); + __lock(); m_map.insert({key, value}); __unlock(); } @@ -279,7 +279,7 @@ namespace concurrent */ void erase(const Key& key) { - __lock(false); + __lock(); m_map.erase(key); __unlock(); } @@ -289,7 +289,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(false); + __lock(); m_map.erase(position); __unlock(); } @@ -300,7 +300,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(false); + __lock(); m_map.erase(first, last); __unlock(); } @@ -310,7 +310,7 @@ namespace concurrent */ void clear() { - __lock(false); + __lock(); m_map.clear(); __unlock(); } diff --git a/src/common/concurrent/vector.h b/src/common/concurrent/vector.h index 0af136e9..a365bf33 100644 --- a/src/common/concurrent/vector.h +++ b/src/common/concurrent/vector.h @@ -73,7 +73,7 @@ namespace concurrent */ vector& operator=(const vector& other) { - __lock(false); + __lock(); m_vector = other.m_vector; __unlock(); return *this; @@ -84,7 +84,7 @@ namespace concurrent */ vector& operator=(const std::vector& other) { - __lock(false); + __lock(); m_vector = other; __unlock(); return *this; @@ -95,7 +95,7 @@ namespace concurrent */ vector& operator=(vector& other) { - __lock(false); + __lock(); m_vector = other.m_vector; __unlock(); return *this; @@ -106,7 +106,7 @@ namespace concurrent */ vector& operator=(std::vector& other) { - __lock(false); + __lock(); m_vector = other; __unlock(); return *this; @@ -119,7 +119,7 @@ namespace concurrent */ void assign(size_t size, const T& value) { - __lock(false); + __lock(); m_vector.assign(size, value); __unlock(); } @@ -207,7 +207,7 @@ namespace concurrent */ void resize(size_t size) { - __lock(false); + __lock(); m_vector.resize(size); __unlock(); } @@ -319,7 +319,7 @@ namespace concurrent */ void push_back(const T& value) { - __lock(false); + __lock(); m_vector.push_back(value); __unlock(); } @@ -329,7 +329,7 @@ namespace concurrent */ void push_back(T&& value) { - __lock(false); + __lock(); m_vector.push_back(std::move(value)); __unlock(); } @@ -339,7 +339,7 @@ namespace concurrent */ void pop_back() { - __lock(false); + __lock(); m_vector.pop_back(); __unlock(); } @@ -352,7 +352,7 @@ namespace concurrent */ iterator insert(iterator position, const T& value) { - __lock(false); + __lock(); auto it = m_vector.insert(position, value); __unlock(); return it; @@ -364,7 +364,7 @@ namespace concurrent */ void erase(size_t index) { - __lock(false); + __lock(); m_vector.erase(m_vector.begin() + index); __unlock(); } @@ -374,7 +374,7 @@ namespace concurrent */ void erase(const_iterator position) { - __lock(false); + __lock(); m_vector.erase(position); __unlock(); } @@ -385,7 +385,7 @@ namespace concurrent */ void erase(const_iterator first, const_iterator last) { - __lock(false); + __lock(); m_vector.erase(first, last); __unlock(); } @@ -396,7 +396,7 @@ namespace concurrent */ void swap(vector& other) { - __lock(false); + __lock(); m_vector.swap(other.m_vector); __unlock(); } @@ -406,7 +406,7 @@ namespace concurrent */ void clear() { - __lock(false); + __lock(); m_vector.clear(); __unlock(); } diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 413c69d9..1a8a6520 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -17,12 +17,12 @@ #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 { @@ -116,7 +116,7 @@ namespace network private: uint32_t m_peerId; - std::unordered_map m_streamTimestamps; + concurrent::unordered_map m_streamTimestamps; static std::mutex m_fqTimestampLock; /** diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 44e24f81..d547204e 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -495,7 +495,6 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) } } - pal_lookup_cleanup: pkt.size = 0U; pkt.compressedSize = 0U; From 70ce04a4b20ad83b097d5c487e96e84594bc25c2 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 25 Apr 2025 09:35:20 -0400 Subject: [PATCH 30/41] add more timestream map locking; --- src/common/network/FrameQueue.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index c0a51afe..0d2cf3b2 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -208,6 +208,7 @@ 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_fqTimestampLock); + m_streamTimestamps.lock(false); auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { timestamp = entry->second; @@ -219,6 +220,7 @@ 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; } + m_streamTimestamps.unlock(); } uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES + length; @@ -243,12 +245,15 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { std::lock_guard lock(m_fqTimestampLock); + m_streamTimestamps.lock(false); 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); + m_streamTimestamps.unlock(); m_streamTimestamps.erase(streamId); } + m_streamTimestamps.unlock(); } RTPFNEHeader fneHeader = RTPFNEHeader(); From ae14ac970616a406813fd0cebaa4260e1bd1204c Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 25 Apr 2025 11:15:53 -0400 Subject: [PATCH 31/41] fix incorrect behavior when deriving initial timestamp for a call stream; correct incorrect behavior inserting new stream in to timestamps table for frame queue; fix incorrect behavior deriving timestamp for bridge RTP; --- src/bridge/HostBridge.cpp | 11 ++++++++--- src/common/network/FrameQueue.cpp | 20 ++++++++------------ src/common/network/FrameQueue.h | 4 +--- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index bd495850..e4cd1c9e 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -22,9 +22,10 @@ #include "common/p25/P25Utils.h" #include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" -#include "common/Log.h" +#include "common/Clock.h" #include "common/StopWatch.h" #include "common/Thread.h" +#include "common/Log.h" #include "common/Utils.h" #include "bridge/ActivityLog.h" #include "HostBridge.h" @@ -2708,14 +2709,18 @@ uint8_t* HostBridge::generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq) uint8_t* buffer = new uint8_t[RTP_HEADER_LENGTH_BYTES + msgLen]; ::memset(buffer, 0x00U, RTP_HEADER_LENGTH_BYTES + msgLen); - header.encode(buffer); - if (timestamp == INVALID_TS) { if (m_debug) LogDebugEx(LOG_NET, "HostBridge::generateRTPHeaders()", "RTP, initial TS = %u, rtpSeq = %u", header.getTimestamp(), rtpSeq); + + timestamp = (uint32_t)system_clock::ntp::now(); + header.setTimestamp(timestamp); + m_rtpTimestamp = header.getTimestamp(); } + header.encode(buffer); + return buffer; } diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 0d2cf3b2..53751e0a 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -14,6 +14,7 @@ #include "network/RTPHeader.h" #include "network/RTPExtensionHeader.h" #include "network/RTPFNEHeader.h" +#include "Clock.h" #include "Log.h" #include "Utils.h" @@ -23,12 +24,6 @@ using namespace network::frame; #include #include -// --------------------------------------------------------------------------- -// Static Class Members -// --------------------------------------------------------------------------- - -std::mutex FrameQueue::m_fqTimestampLock; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -189,7 +184,6 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::clearTimestamps() { - std::lock_guard lock(m_fqTimestampLock); m_streamTimestamps.clear(); } @@ -207,7 +201,6 @@ 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_fqTimestampLock); m_streamTimestamps.lock(false); auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { @@ -235,16 +228,19 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui header.setSequence(rtpSeq); header.setSSRC(ssrc); - header.encode(buffer); - if (streamId != 0U && timestamp == INVALID_TS && rtpSeq != RTP_END_OF_CALL_SEQ) { if (m_debug) LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, initial TS = %u, rtpSeq = %u", streamId, header.getTimestamp(), rtpSeq); - m_streamTimestamps[streamId] = header.getTimestamp(); + + timestamp = (uint32_t)system_clock::ntp::now(); + header.setTimestamp(timestamp); + + m_streamTimestamps.insert(streamId, timestamp); } + header.encode(buffer); + if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { - std::lock_guard lock(m_fqTimestampLock); m_streamTimestamps.lock(false); auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 1a8a6520..6806d51f 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -22,8 +22,6 @@ #include "common/network/RTPFNEHeader.h" #include "common/network/RawFrameQueue.h" -#include - namespace network { // --------------------------------------------------------------------------- @@ -116,8 +114,8 @@ namespace network private: uint32_t m_peerId; + concurrent::unordered_map m_streamTimestamps; - static std::mutex m_fqTimestampLock; /** * @brief Generate RTP message for the frame queue. From 08cc9ff41492ab7b340a97738e2397d4b231f7c4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 25 Apr 2025 19:16:12 -0400 Subject: [PATCH 32/41] fix issue with naive approach to handling PL_ACT_PEER_LIST data fragementation; --- src/fne/network/DiagNetwork.cpp | 31 +++++++++++++++++++++++++------ src/fne/network/FNENetwork.cpp | 6 ++---- src/fne/network/PeerNetwork.cpp | 3 +-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index d547204e..e2c97da1 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -446,7 +446,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) buffer = new uint8_t[pkt.size + 1U]; ::memcpy(buffer, rawPayload + 10U, PEER_LINK_BLOCK_SIZE); - // Utils::dump(1U, "Block Payload", buffer, PEER_LINK_BLOCK_SIZE); + Utils::dump(1U, "Block Payload", buffer, PEER_LINK_BLOCK_SIZE); pkt.fragments.insert({curBlock, buffer}); } @@ -454,13 +454,30 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) // do we have all the blocks? if (pkt.fragments.size() == blockCnt + 1U) { - uint8_t* buffer = new uint8_t[pkt.size + 1U]; + uint8_t* buffer = nullptr; + if (pkt.size == 0U) { + LogError(LOG_NET, "PEER %u Peer-Link, Active Peer List, error missing size information", peerId); + goto pl_act_cleanup; + } + + if (pkt.compressedSize == 0U) { + LogError(LOG_NET, "PEER %u Peer-Link, Active Peer List, error missing compressed size information", peerId); + goto pl_act_cleanup; + } + + buffer = new uint8_t[pkt.size + 1U]; ::memset(buffer, 0x00U, pkt.size + 1U); - for (uint8_t i = 0U; i < pkt.fragments.size(); i++) { - uint32_t offs = i * PEER_LINK_BLOCK_SIZE; - ::memcpy(buffer + offs, pkt.fragments[i], PEER_LINK_BLOCK_SIZE); + if (pkt.fragments.size() == 1U) { + ::memcpy(buffer, pkt.fragments[0U], pkt.size); + } else { + for (uint8_t i = 0U; i < pkt.fragments.size(); i++) { + uint32_t offs = i * PEER_LINK_BLOCK_SIZE; + ::memcpy(buffer + offs, pkt.fragments[i], PEER_LINK_BLOCK_SIZE); + } } + // Utils::dump(1U, "Peer-Link Active Peer List", buffer, pkt.size + 1U); + // scope is intentional { uint32_t decompressedLen = 0U; @@ -495,10 +512,12 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) } } + pl_act_cleanup: pkt.size = 0U; pkt.compressedSize = 0U; - delete[] buffer; + if (buffer != nullptr) + delete[] buffer; for (auto& frag : pkt.fragments) { if (frag.second != nullptr) delete[] frag.second; diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 2183ce3e..bd2d3b99 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -355,7 +355,6 @@ void FNENetwork::clock(uint32_t ms) // send active peer list to Peer-Link masters if (m_host->m_peerNetworks.size() > 0) { - json::array peers = json::array(); for (auto peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isPeerLink()) { @@ -366,7 +365,8 @@ void FNENetwork::clock(uint32_t ms) }); } - if (m_peers.size() > 0 && peers.size() == 0) { + if (m_peers.size() > 0) { + json::array peers = json::array(); for (auto entry : m_peers) { uint32_t peerId = entry.first; network::FNEPeerConnection* peerConn = entry.second; @@ -377,9 +377,7 @@ void FNENetwork::clock(uint32_t ms) peers.push_back(json::value(peerObj)); } } - } - if (peers.size() > 0) { peer.second->writePeerLinkPeers(&peers); } } diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index a771573e..f4e70c74 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -123,8 +123,7 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) uint32_t compressedLen = 0U; uint8_t* compressed = Compression::compress((uint8_t*)buffer, len, &compressedLen); - if (compressed != nullptr) - { + if (compressed != nullptr) { // transmit peer link active peer list uint32_t streamId = createStreamId(); uint8_t blockCnt = (compressedLen / PEER_LINK_BLOCK_SIZE) + (compressedLen % PEER_LINK_BLOCK_SIZE ? 1U : 0U); From 853e847dc3110475532fe87d32796dda3d95c630 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 25 Apr 2025 19:20:44 -0400 Subject: [PATCH 33/41] disable accidental debug code; --- src/fne/network/DiagNetwork.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index e2c97da1..1c0b4511 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -446,7 +446,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) buffer = new uint8_t[pkt.size + 1U]; ::memcpy(buffer, rawPayload + 10U, PEER_LINK_BLOCK_SIZE); - Utils::dump(1U, "Block Payload", buffer, PEER_LINK_BLOCK_SIZE); + // Utils::dump(1U, "Block Payload", buffer, PEER_LINK_BLOCK_SIZE); pkt.fragments.insert({curBlock, buffer}); } From 9b77d3df5e168133fcc3ef36552732e0c3f8b048 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 27 Apr 2025 09:50:18 -0400 Subject: [PATCH 34/41] instead of asserting, throw a log error message and discard network packet; --- src/common/network/FrameQueue.cpp | 30 ++++++++++++++++++++++------ src/common/network/RawFrameQueue.cpp | 22 +++++++++++++++----- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 53751e0a..880691a8 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -136,8 +136,14 @@ UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint3 bool FrameQueue::write(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) { - assert(message != nullptr); - assert(length > 0U); + if (message == nullptr) { + LogError(LOG_NET, "FrameQueue::write(), message is null"); + return false; + } + if (length == 0U) { + LogError(LOG_NET, "FrameQueue::write(), message length is zero"); + return false; + } uint32_t bufferLen = 0U; uint8_t* buffer = generateMessage(message, length, streamId, peerId, ssrc, opcode, rtpSeq, &bufferLen); @@ -165,8 +171,14 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) { - assert(message != nullptr); - assert(length > 0U); + if (message == nullptr) { + LogError(LOG_NET, "FrameQueue::enqueueMessage(), message is null"); + return; + } + if (length == 0U) { + LogError(LOG_NET, "FrameQueue::enqueueMessage(), message length is zero"); + return; + } uint32_t bufferLen = 0U; uint8_t* buffer = generateMessage(message, length, streamId, peerId, ssrc, opcode, rtpSeq, &bufferLen); @@ -196,8 +208,14 @@ void FrameQueue::clearTimestamps() uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, uint32_t* outBufferLen) { - assert(message != nullptr); - assert(length > 0U); + if (message == nullptr) { + LogError(LOG_NET, "FrameQueue::generateMessage(), message is null"); + return nullptr; + } + if (length == 0U) { + LogError(LOG_NET, "FrameQueue::generateMessage(), message length is zero"); + return nullptr; + } uint32_t timestamp = INVALID_TS; if (streamId != 0U) { diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index a0125936..cd250b73 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.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 "Defines.h" @@ -90,8 +90,14 @@ UInt8Array RawFrameQueue::read(int& messageLength, sockaddr_storage& address, ui bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen, ssize_t* lenWritten) { - assert(message != nullptr); - assert(length > 0U); + if (message == nullptr) { + LogError(LOG_NET, "RawFrameQueue::write(), message is null"); + return false; + } + if (length == 0U) { + LogError(LOG_NET, "RawFrameQueue::write(), message length is zero"); + return false; + } uint8_t* buffer = new uint8_t[length]; ::memset(buffer, 0x00U, length); @@ -114,8 +120,14 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen) { - assert(message != nullptr); - assert(length > 0U); + if (message == nullptr) { + LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), message is null"); + return; + } + if (length == 0U) { + LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), message length is zero"); + return; + } // if the queue is flushing -- don't attempt to enqueue any messages if (m_queueFlushing) { From 22263cfdd4cf63770e7793450ab0958e84f5ecc3 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Apr 2025 11:08:48 -0400 Subject: [PATCH 35/41] lock the talkgroup tables when a find is in progress (this will prevent some weird concurrency behaviors because talkgroup rules does not use the concurrent vector); --- src/common/lookups/TalkgroupRulesLookup.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index b1d5aadc..90fa0148 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -232,6 +232,7 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::find(uint32_t id, uint8_t slot) __SPINLOCK(); + std::lock_guard lock(m_mutex); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { @@ -258,6 +259,7 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByRewrite(uint32_t peerId, uin __SPINLOCK(); + std::lock_guard lock(m_mutex); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { From f0341378c7adff89f55907b7ac5f0adbca510d5d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Apr 2025 20:43:10 -0400 Subject: [PATCH 36/41] if filter headers or terminators is enabled, and the target peer is in the exclusion list, do not send headers or terminators; --- src/fne/network/callhandler/TagP25Data.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 1f169839..43ddb791 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -936,6 +936,15 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, 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; + } + } + // is this a U2U call? lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); if (!rid.radioDefault() && rid.radioEnabled()) { @@ -969,6 +978,15 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, 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; + } + } + // is this a U2U call? lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); if (!rid.radioDefault() && rid.radioEnabled()) { From 00f91c16e04d1183d4e59fa56423867ef32b0a29 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Apr 2025 20:44:34 -0400 Subject: [PATCH 37/41] exclusion check should happen before the rewrite check; --- src/fne/network/callhandler/TagP25Data.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 43ddb791..2d2a0b0c 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -931,11 +931,6 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, return true; } - tg = m_network->m_tidLookup->findByRewrite(peerId, control.getDstId()); - if (!tg.isInvalid()) { - return true; - } - // is this peer excluded from the group? std::vector exclusion = tg.config().exclusion(); if (exclusion.size() > 0) { @@ -945,6 +940,11 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, } } + 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()) { @@ -973,11 +973,6 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, return true; } - tg = m_network->m_tidLookup->findByRewrite(peerId, control.getDstId()); - if (!tg.isInvalid()) { - return true; - } - // is this peer excluded from the group? std::vector exclusion = tg.config().exclusion(); if (exclusion.size() > 0) { @@ -987,6 +982,11 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, } } + 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()) { From abe8653252e9bc25d16af87b0176c3b19085a04a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 28 Apr 2025 22:50:41 -0400 Subject: [PATCH 38/41] implement dvmpatch, this is a new utility that allows simple TG to TG patching; --- CMakeLists.txt | 36 +- README.md | 21 + configs/patch-config.example.yml | 81 +++ src/CMakeLists.txt | 12 + src/patch/ActivityLog.cpp | 155 +++++ src/patch/ActivityLog.h | 45 ++ src/patch/CMakeLists.txt | 15 + src/patch/Defines.h | 41 ++ src/patch/HostPatch.cpp | 1078 +++++++++++++++++++++++++++++ src/patch/HostPatch.h | 134 ++++ src/patch/PatchMain.cpp | 228 ++++++ src/patch/PatchMain.h | 53 ++ src/patch/network/PeerNetwork.cpp | 360 ++++++++++ src/patch/network/PeerNetwork.h | 128 ++++ 14 files changed, 2377 insertions(+), 10 deletions(-) create mode 100644 configs/patch-config.example.yml create mode 100644 src/patch/ActivityLog.cpp create mode 100644 src/patch/ActivityLog.h create mode 100644 src/patch/CMakeLists.txt create mode 100644 src/patch/Defines.h create mode 100644 src/patch/HostPatch.cpp create mode 100644 src/patch/HostPatch.h create mode 100644 src/patch/PatchMain.cpp create mode 100644 src/patch/PatchMain.h create mode 100644 src/patch/network/PeerNetwork.cpp create mode 100644 src/patch/network/PeerNetwork.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 13b81907..30f17591 100644 --- a/CMakeLists.txt +++ b/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 # * # */ @@ -267,7 +267,8 @@ install(TARGETS sysview DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS tged DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) install(TARGETS dvmbridge DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) -install(FILES configs/config.example.yml configs/fne-config.example.yml configs/fne-sysview.example.yml configs/monitor-config.example.yml configs/iden_table.example.dat configs/RSSI.dat configs/rid_acl.example.dat configs/talkgroup_rules.example.yml configs/bridge-config.example.yml DESTINATION ${CMAKE_INSTALL_PREFIX}/etc) +install(TARGETS dvmpatch DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install(FILES configs/config.example.yml configs/fne-config.example.yml configs/fne-sysview.example.yml configs/monitor-config.example.yml configs/iden_table.example.dat configs/RSSI.dat configs/rid_acl.example.dat configs/talkgroup_rules.example.yml configs/bridge-config.example.yml configs/patch-config.example.yml DESTINATION ${CMAKE_INSTALL_PREFIX}/etc) install(PROGRAMS tools/start-dvm.sh tools/stop-dvm.sh tools/dvm-watchdog.sh tools/stop-watchdog.sh tools/fne-watchdog.sh tools/start-dvm-fne.sh DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/filePath: ./filePath: \\\\/var\\\\/log\\\\//' /usr/local/etc/config.example.yml\")") install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/activityFilePath: ./activityFilePath: \\\\/var\\\\/log\\\\//' /usr/local/etc/config.example.yml\")") @@ -289,13 +290,15 @@ if (NOT TARGET strip) COMMAND arm-linux-gnueabihf-strip -s sysview COMMAND arm-linux-gnueabihf-strip -s tged COMMAND arm-linux-gnueabihf-strip -s peered - COMMAND arm-linux-gnueabihf-strip -s dvmbridge) + COMMAND arm-linux-gnueabihf-strip -s dvmbridge + COMMAND arm-linux-gnueabihf-strip -s dvmpatch) else() add_custom_target(strip COMMAND arm-linux-gnueabihf-strip -s dvmhost COMMAND arm-linux-gnueabihf-strip -s dvmfne COMMAND arm-linux-gnueabihf-strip -s dvmcmd - COMMAND arm-linux-gnueabihf-strip -s dvmbridge) + COMMAND arm-linux-gnueabihf-strip -s dvmbridge + COMMAND arm-linux-gnueabihf-strip -s dvmpatch) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) elseif (CROSS_COMPILE_AARCH64) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) @@ -307,13 +310,15 @@ if (NOT TARGET strip) COMMAND aarch64-linux-gnu-strip -s sysview COMMAND aarch64-linux-gnu-strip -s tged COMMAND aarch64-linux-gnu-strip -s peered - COMMAND aarch64-linux-gnu-strip -s dvmbridge) + COMMAND aarch64-linux-gnu-strip -s dvmbridge + COMMAND aarch64-linux-gnu-strip -s dvmpatch) else() add_custom_target(strip COMMAND aarch64-linux-gnu-strip -s dvmhost COMMAND aarch64-linux-gnu-strip -s dvmfne COMMAND aarch64-linux-gnu-strip -s dvmcmd - COMMAND aarch64-linux-gnu-strip -s dvmbridge) + COMMAND aarch64-linux-gnu-strip -s dvmbridge + COMMAND aarch64-linux-gnu-strip -s dvmpatch) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) elseif (CROSS_COMPILE_RPI_ARM) if (NOT WITH_RPI_ARM_TOOLS) @@ -321,13 +326,15 @@ if (NOT TARGET strip) COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmhost COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmfne COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge) + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmpatch) else() add_custom_target(strip COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmhost COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmfne COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd - COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge) + COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge + COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmpatch) endif () else() if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) @@ -339,13 +346,15 @@ if (NOT TARGET strip) COMMAND strip -s sysview COMMAND strip -s tged COMMAND strip -s peered - COMMAND strip -s dvmbridge) + COMMAND strip -s dvmbridge + COMMAND strip -s dvmpatch) else() add_custom_target(strip COMMAND strip -s dvmhost COMMAND strip -s dvmfne COMMAND strip -s dvmcmd - COMMAND strip -s dvmbridge) + COMMAND strip -s dvmbridge + COMMAND strip -s dvmpatch) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (CROSS_COMPILE_ARM) endif (NOT TARGET strip) @@ -375,6 +384,7 @@ if (NOT TARGET tarball) COMMAND cp -v peered ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin + COMMAND cp -v dvmpatch ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp ${CMAKE_SOURCE_DIR}/tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND chmod +x ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/*.sh COMMAND cp -v ${CMAKE_SOURCE_DIR}/configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm @@ -408,6 +418,7 @@ if (NOT TARGET tarball) COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin + COMMAND cp -v dvmpatch ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp ${CMAKE_SOURCE_DIR}/tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND chmod +x ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/*.sh COMMAND cp -v ${CMAKE_SOURCE_DIR}/configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm @@ -454,6 +465,7 @@ if (NOT TARGET tarball_notools) COMMAND cp -v peered ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin + COMMAND cp -v dvmpatch ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v ${CMAKE_SOURCE_DIR}/configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema COMMAND cp -v ${CMAKE_SOURCE_DIR}/configs/schema/*.json ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema @@ -485,6 +497,7 @@ if (NOT TARGET tarball_notools) COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin + COMMAND cp -v dvmpatch ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v ${CMAKE_SOURCE_DIR}/configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema COMMAND cp -v ${CMAKE_SOURCE_DIR}/configs/schema/*.json ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/schema @@ -530,6 +543,7 @@ add_custom_target(old_install COMMAND install -m 755 peered ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmfne ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmbridge ${CMAKE_LEGACY_INSTALL_PREFIX}/bin + COMMAND install -m 755 dvmpatch ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/config.example.yml COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/fne-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/fne-config.example.yml COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/fne-sysview.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/fne-sysview.example.yml @@ -539,6 +553,7 @@ add_custom_target(old_install COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/rid_acl.example.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/rid_acl.dat COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/talkgroup_rules.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/talkgroup_rules.example.yml COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/bridge-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/bridge-config.example.yml + COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/patch-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/patch-config.example.yml COMMAND install -m 644 ${CMAKE_SOURCE_DIR}/configs/schema/talkgroup_rules.yaml_schema.json ${CMAKE_LEGACY_INSTALL_PREFIX}/schema/talkgroup_rules.yaml_schema.json COMMAND install -m 755 ${CMAKE_SOURCE_DIR}/tools/start-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX} COMMAND install -m 755 ${CMAKE_SOURCE_DIR}/tools/stop-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX} @@ -581,6 +596,7 @@ add_custom_target(old_install-service COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/rid_acl.example.dat COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/talkgroup_rules.example.yml COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/bridge-config.example.yml + COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/patch-config.example.yml COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/log COMMAND cp ../linux/dvmhost.service /lib/systemd/system/ COMMAND bash \"-c\" \"sed -i 's/\\\\/usr\\\\/local\\\\/bin/\\\\/opt\\\\/dvm\\\\/bin/' /lib/systemd/system/dvmhost.service\" diff --git a/README.md b/README.md index 2bd49a5c..5e202475 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This project suite generates a few executables: - `dvmhost` host software that connects to the DVM modems (both air interface for repeater and hotspot or P25 DFSI for commerical P25 hardware) and is the primary data processing application for digital modes. [See configuration](#dvmhost-configuration) to configure and calibrate. - `dvmfne` a network "core", this provides a central server for `dvmhost` instances to connect to and be networked with, allowing relay of traffic and other data between `dvmhost` instances and other `dvmfne` instances. [See configuration](#dvmfne-configuration) to configure. - `dvmbridge` a analog/PCM audio bridge, this provides the capability for analog or PCM audio resources to be connected to a `dvmfne` instance, allowing realtime vocoding of traffic. [See configuration](#dvmbridge-configuration) to configure. +- `dvmpatch` a talkgroup patching utility, this provides the capability to manually patch talkgroups of the same digital mode together. [See configuration](#dvmpatch-configuration) to configure. - `dvmcmd` a simple command-line utility to send remote control commands to a `dvmhost` or `dvmfne` instance with REST API configured. ### Supplementary Support Applications @@ -215,6 +216,12 @@ using the command line parameter `-wasapi` will force `dvmbridge` to utilize WAS There is no other real configuration for a `dvmbridge` instance other then setting the appropriate parameters within the configuration files. +## dvmpatch Configuration + +This source repository contains configuration example files within the configs folder, please review `patch-config.example.yml` for the `dvmpatch` for details on various configurable options. + +There is no other real configuration for a `dvmpatch` instance other then setting the appropriate parameters within the configuration files. + ## Command Line Parameters ### dvmhost Command Line Parameters @@ -285,6 +292,20 @@ Audio Output Devices: ... ... ``` +### dvmpatch Command Line Parameters + +``` +usage: ./dvmpatch [-vhf][-c ] + + -v show version information + -h show this screen + -f foreground mode + + -c specifies the configuration file to use + + -- stop handling options +``` + ### dvmcmd Command Line Parameters ``` diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml new file mode 100644 index 00000000..d2997368 --- /dev/null +++ b/configs/patch-config.example.yml @@ -0,0 +1,81 @@ +# +# Digital Voice Modem - TG Patch +# + +# Flag indicating whether the host will run as a background or foreground task. +daemon: true + +# +# Logging Configuration +# +# Logging Levels: +# 1 - Debug +# 2 - Message +# 3 - Informational +# 4 - Warning +# 5 - Error +# 6 - Fatal +# +log: + # Console display logging level (used when in foreground). + displayLevel: 1 + # File logging level. + fileLevel: 1 + # Flag indicating file logs should be sent to syslog instead of a file. + useSyslog: false + # Full path for the directory to store the log files. + filePath: . + # Full path for the directory to store the activity log files. + activityFilePath: . + # Log filename prefix. + fileRoot: dvmpatch + +# +# Network Configuration +# +network: + # Network Peer ID + id: 9000123 + # Hostname/IP address of FNE master to connect to. + address: 127.0.0.1 + # Port number to connect to. + port: 62031 + # FNE access password. + password: RPT1234 + + # Flag indicating whether or not host endpoint networking is encrypted. + encrypted: false + # AES-256 32-byte Preshared Key + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + + # Flag indicating whether or not the host diagnostic log will be sent to the network. + allowDiagnosticTransfer: true + + # Flag indicating whether or not verbose debug logging is enabled. + debug: false + + # Source Talkgroup ID for transmitted/received audio frames. + sourceTGID: 1 + # Source Slot for received/transmitted audio frames. + sourceSlot: 1 + # Destination Talkgroup ID for transmitted/received audio frames. + destinationTGID: 1 + # Destination Slot for received/transmitted audio frames. + destinationSlot: 1 + + # Flag indicating whether or not the patch is two-way. + twoWay: false + +system: + # Textual Name + identity: PATCH + + # Digital mode (1 - DMR, 2 - P25). + digiMode: 1 + + # Flag indicating whether or not trace logging is enabled. + trace: false + # Flag indicating whether or not debug logging is enabled. + debug: false diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1a15fa78..489fbfd4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,3 +142,15 @@ else () target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) endif (COMPILE_WIN32) target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} src src/bridge) + +# +## dvmpatch +# +include(src/patch/CMakeLists.txt) +add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC}) +if (COMPILE_WIN32) + 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) +endif (COMPILE_WIN32) +target_include_directories(dvmpatch PRIVATE ${OPENSSL_INCLUDE_DIR} src src/patch) diff --git a/src/patch/ActivityLog.cpp b/src/patch/ActivityLog.cpp new file mode 100644 index 00000000..2680141f --- /dev/null +++ b/src/patch/ActivityLog.cpp @@ -0,0 +1,155 @@ +// 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 + * + */ +#include "ActivityLog.h" +#include "common/network/BaseNetwork.h" +#include "common/Log.h" // for CurrentLogFileLevel() and LogGetNetwork() + +#if defined(_WIN32) +#include "common/Clock.h" +#else +#include +#endif // defined(_WIN32) + +#if defined(CATCH2_TEST_COMPILATION) +#include +#endif + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define EOL "\r\n" + +const uint32_t ACT_LOG_BUFFER_LEN = 501U; + +// --------------------------------------------------------------------------- +// Global Variables +// --------------------------------------------------------------------------- + +static std::string m_actFilePath; +static std::string m_actFileRoot; + +static FILE* m_actFpLog = nullptr; + +static struct tm m_actTm; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/* Helper to open the activity log file, file handle. */ + +static bool ActivityLogOpen() +{ + if (CurrentLogFileLevel() == 0U) + return true; + + time_t now; + ::time(&now); + + struct tm* tm = ::gmtime(&now); + + if (tm->tm_mday == m_actTm.tm_mday && tm->tm_mon == m_actTm.tm_mon && tm->tm_year == m_actTm.tm_year) { + if (m_actFpLog != nullptr) + return true; + } + else { + if (m_actFpLog != nullptr) + ::fclose(m_actFpLog); + } + + char filename[200U]; + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", LogGetFilePath().c_str(), LogGetFileRoot().c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + + m_actFpLog = ::fopen(filename, "a+t"); + m_actTm = *tm; + + return m_actFpLog != nullptr; +} + +/* Initializes the activity log. */ + +bool ActivityLogInitialise(const std::string& filePath, const std::string& fileRoot) +{ +#if defined(CATCH2_TEST_COMPILATION) + return true; +#endif + m_actFilePath = filePath; + m_actFileRoot = fileRoot; + + return ::ActivityLogOpen(); +} + +/* Finalizes the activity log. */ + +void ActivityLogFinalise() +{ +#if defined(CATCH2_TEST_COMPILATION) + return; +#endif + if (m_actFpLog != nullptr) + ::fclose(m_actFpLog); +} + +/* Writes a new entry to the activity log. */ + +void ActivityLog(const char* msg, ...) +{ +#if defined(CATCH2_TEST_COMPILATION) + return; +#endif + assert(msg != nullptr); + + char buffer[ACT_LOG_BUFFER_LEN]; + time_t now; + ::time(&now); + struct tm* tm = ::localtime(&now); + + struct timeval nowMillis; + ::gettimeofday(&nowMillis, NULL); + + ::sprintf(buffer, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U); + + va_list vl, vl_len; + va_start(vl, msg); + va_copy(vl_len, vl); + + size_t len = ::vsnprintf(nullptr, 0U, msg, vl_len); + ::vsnprintf(buffer + ::strlen(buffer), len + 1U, msg, vl); + + va_end(vl_len); + va_end(vl); + + bool ret = ::ActivityLogOpen(); + if (!ret) + return; + + if (LogGetNetwork() != nullptr) { + network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork();; + network->writeActLog(buffer); + } + + if (CurrentLogFileLevel() == 0U) + return; + + ::fprintf(m_actFpLog, "%s\n", buffer); + ::fflush(m_actFpLog); + + if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { + ::fprintf(stdout, "%s" EOL, buffer); + ::fflush(stdout); + } +} diff --git a/src/patch/ActivityLog.h b/src/patch/ActivityLog.h new file mode 100644 index 00000000..8f9ffec8 --- /dev/null +++ b/src/patch/ActivityLog.h @@ -0,0 +1,45 @@ +// 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 + * + */ +/** + * @file ActivityLog.h + * @ingroup patch + * @file ActivityLog.cpp + * @ingroup patch + */ +#if !defined(__ACTIVITY_LOG_H__) +#define __ACTIVITY_LOG_H__ + +#include "Defines.h" + +#include + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/** + * @brief Initializes the activity log. + * @param filePath File path for the log file. + * @param fileRoot Root name for log file. + */ +extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const std::string& fileRoot); +/** + * @brief Finalizes the activity log. + */ +extern HOST_SW_API void ActivityLogFinalise(); +/** + * @brief Writes a new entry to the activity log. + * @param msg String format. + * + * This is a variable argument function. + */ +extern HOST_SW_API void ActivityLog(const char* msg, ...); + +#endif // __ACTIVITY_LOG_H__ diff --git a/src/patch/CMakeLists.txt b/src/patch/CMakeLists.txt new file mode 100644 index 00000000..d04d5004 --- /dev/null +++ b/src/patch/CMakeLists.txt @@ -0,0 +1,15 @@ +# 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 +# * +# */ +file(GLOB patch_SRC + "src/patch/network/*.h" + "src/patch/network/*.cpp" + "src/patch/*.h" + "src/patch/*.cpp" +) diff --git a/src/patch/Defines.h b/src/patch/Defines.h new file mode 100644 index 00000000..394c328c --- /dev/null +++ b/src/patch/Defines.h @@ -0,0 +1,41 @@ +// 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 + * + */ +/** + * @defgroup patch TG Patch + * @brief Digital Voice Modem - TG Patch + * @details Talkgroup patching utility, this provides facilities to patch two talkgroups together. + * @ingroup patch + * + * @file Defines.h + * @ingroup patch + */ +#if !defined(__DEFINES_H__) +#define __DEFINES_H__ + +#include "common/Defines.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#undef __PROG_NAME__ +#define __PROG_NAME__ "Digital Voice Modem (DVM) TG Patch" +#undef __EXE_NAME__ +#define __EXE_NAME__ "patch" + +#undef __NETVER__ +#define __NETVER__ "PATCH_R" VERSION_MAJOR VERSION_REV VERSION_MINOR + +#undef DEFAULT_CONF_FILE +#define DEFAULT_CONF_FILE "patch-config.yml" +#undef DEFAULT_LOCK_FILE +#define DEFAULT_LOCK_FILE "/tmp/dvmpatch.lock" + +#endif // __DEFINES_H__ diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp new file mode 100644 index 00000000..6f575669 --- /dev/null +++ b/src/patch/HostPatch.cpp @@ -0,0 +1,1078 @@ +// 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 + * + */ +#include "Defines.h" +#include "common/dmr/DMRDefines.h" +#include "common/dmr/data/EMB.h" +#include "common/dmr/data/NetData.h" +#include "common/dmr/lc/FullLC.h" +#include "common/dmr/SlotType.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/LowSpeedData.h" +#include "common/p25/dfsi/DFSIDefines.h" +#include "common/p25/dfsi/LC.h" +#include "common/p25/lc/LC.h" +#include "common/p25/P25Utils.h" +#include "common/network/RTPHeader.h" +#include "common/network/udp/Socket.h" +#include "common/Clock.h" +#include "common/StopWatch.h" +#include "common/Thread.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "patch/ActivityLog.h" +#include "HostPatch.h" +#include "PatchMain.h" + +using namespace network; +using namespace network::frame; +using namespace network::udp; + +#include +#include +#include +#include + +#if !defined(_WIN32) +#include +#include +#endif // !defined(_WIN32) + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex HostPatch::m_networkMutex; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the HostPatch class. */ + +HostPatch::HostPatch(const std::string& confFile) : + m_confFile(confFile), + m_conf(), + m_network(nullptr), + m_srcTGId(0U), + m_srcSlot(1U), + m_dstTGId(0U), + m_dstSlot(1U), + m_identity(), + m_digiMode(1U), + m_dmrEmbeddedData(), + m_grantDemand(false), + m_callInProgress(false), + m_rxStartTime(0U), + m_rxStreamId(0U), + m_running(false), + m_trace(false), + m_debug(false) +{ + /* stub */ +} + +/* Finalizes a instance of the HostPatch class. */ + +HostPatch::~HostPatch() = default; + +/* Executes the main FNE processing loop. */ + +int HostPatch::run() +{ + bool ret = false; + try { + ret = yaml::Parse(m_conf, m_confFile.c_str()); + if (!ret) { + ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + } + } + catch (yaml::OperationException const& e) { + ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); + } + + bool m_daemon = m_conf["daemon"].as(false); + if (m_daemon && g_foreground) + m_daemon = false; + + // initialize system logging + yaml::Node logConf = m_conf["log"]; + ret = ::LogInitialise(logConf["filePath"].as(), logConf["fileRoot"].as(), + logConf["fileLevel"].as(0U), logConf["displayLevel"].as(0U)); + if (!ret) { + ::fatal("unable to open the log file\n"); + } + + ret = ::ActivityLogInitialise(logConf["activityFilePath"].as(), logConf["fileRoot"].as()); + if (!ret) { + ::fatal("unable to open the activity log file\n"); + } + +#if !defined(_WIN32) + // handle POSIX process forking + if (m_daemon) { + // create new process + pid_t pid = ::fork(); + if (pid == -1) { + ::fprintf(stderr, "%s: Couldn't fork() , exiting\n", g_progExe.c_str()); + ::LogFinalise(); + return EXIT_FAILURE; + } + else if (pid != 0) { + ::LogFinalise(); + exit(EXIT_SUCCESS); + } + + // create new session and process group + if (::setsid() == -1) { + ::fprintf(stderr, "%s: Couldn't setsid(), exiting\n", g_progExe.c_str()); + ::LogFinalise(); + return EXIT_FAILURE; + } + + // set the working directory to the root directory + if (::chdir("/") == -1) { + ::fprintf(stderr, "%s: Couldn't cd /, exiting\n", g_progExe.c_str()); + ::LogFinalise(); + return EXIT_FAILURE; + } + + ::close(STDIN_FILENO); + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + } +#endif // !defined(_WIN32) + + ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ + "Copyright (c) 2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ + "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ + ">> Talkgroup Patch\r\n"); + + // read base parameters from configuration + ret = readParams(); + if (!ret) + return EXIT_FAILURE; + + yaml::Node systemConf = m_conf["system"]; + + // initialize peer networking + ret = createNetwork(); + if (!ret) + return EXIT_FAILURE; + + /* + ** Initialize Threads + */ + + if (!Thread::runAsThread(this, threadNetworkProcess)) + return EXIT_FAILURE; + + ::LogInfoEx(LOG_HOST, "Patch is up and running"); + + m_running = true; + + StopWatch stopWatch; + stopWatch.start(); + + // main execution loop + while (!g_killed) { + uint32_t ms = stopWatch.elapsed(); + + ms = stopWatch.elapsed(); + stopWatch.start(); + + // ------------------------------------------------------ + // -- Network Clocking -- + // ------------------------------------------------------ + + if (m_network != nullptr) { + std::lock_guard lock(HostPatch::m_networkMutex); + m_network->clock(ms); + } + + if (ms < 2U) + Thread::sleep(1U); + } + + ::LogSetNetwork(nullptr); + if (m_network != nullptr) { + m_network->close(); + delete m_network; + } + + return EXIT_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Reads basic configuration parameters from the YAML configuration file. */ + +bool HostPatch::readParams() +{ + yaml::Node systemConf = m_conf["system"]; + + m_identity = systemConf["identity"].as(); + + m_digiMode = (uint8_t)systemConf["digiMode"].as(1U); + if (m_digiMode < TX_MODE_DMR) + m_digiMode = TX_MODE_DMR; + if (m_digiMode > TX_MODE_P25) + m_digiMode = TX_MODE_P25; + + m_grantDemand = systemConf["grantDemand"].as(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"); + + if (m_debug) { + LogInfo(" Debug: yes"); + } + + return true; +} + +/* Initializes network connectivity. */ + +bool HostPatch::createNetwork() +{ + yaml::Node networkConf = m_conf["network"]; + + std::string address = networkConf["address"].as(); + uint16_t port = (uint16_t)networkConf["port"].as(TRAFFIC_DEFAULT_PORT); + uint16_t local = (uint16_t)networkConf["local"].as(0U); + uint32_t id = networkConf["id"].as(1000U); + std::string password = networkConf["password"].as(); + bool allowDiagnosticTransfer = networkConf["allowDiagnosticTransfer"].as(false); + bool debug = networkConf["debug"].as(false); + + m_srcTGId = (uint32_t)networkConf["sourceTGID"].as(1U); + m_srcSlot = (uint8_t)networkConf["sourceSlot"].as(1U); + m_dstTGId = (uint32_t)networkConf["destinationTGID"].as(1U); + m_dstSlot = (uint8_t)networkConf["destinationSlot"].as(1U); + m_twoWayPatch = networkConf["twoWay"].as(false); + + // make sure our destination ID is sane + if (m_srcTGId == 0U) { + ::LogError(LOG_HOST, "Patch source TGID cannot be set to 0."); + return false; + } + + if (m_dstTGId == 0U) { + ::LogError(LOG_HOST, "Patch destination TGID cannot be set to 0."); + return false; + } + + if (m_srcTGId == m_dstTGId) { + ::LogError(LOG_HOST, "Patch source TGID and destination TGID cannot be the same."); + return false; + } + + // make sure we're range checked + switch (m_digiMode) { + case TX_MODE_DMR: + { + if (m_srcTGId > 16777215) { + ::LogError(LOG_HOST, "Patch source TGID cannot be greater than 16777215."); + return false; + } + + if (m_dstTGId > 16777215) { + ::LogError(LOG_HOST, "Patch source TGID cannot be greater than 16777215."); + return false; + } + } + break; + case TX_MODE_P25: + { + if (m_srcTGId > 65535) { + ::LogError(LOG_HOST, "Patch source TGID cannot be greater than 65535."); + return false; + } + + if (m_dstTGId > 65535) { + ::LogError(LOG_HOST, "Patch destination TGID cannot be greater than 65535."); + return false; + } + } + break; + } + + bool encrypted = networkConf["encrypted"].as(false); + std::string key = networkConf["presharedKey"].as(); + uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + if (!key.empty()) { + if (key.size() == 32) { + // bryanb: shhhhhhh....dirty nasty hacks + key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs) + LogWarning(LOG_HOST, "Half-length network preshared encryption key detected, doubling key on itself."); + } + + if (key.size() == 64) { + if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { + const char* keyPtr = key.c_str(); + ::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = { keyPtr[0], keyPtr[1], 0 }; + presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + } + else { + LogWarning(LOG_HOST, "Invalid characters in the network preshared encryption key. Encryption disabled."); + encrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + encrypted = false; + } + } + + if (id > 999999999U) { + ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); + return false; + } + + LogInfo("Network Parameters"); + LogInfo(" Peer ID: %u", id); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + if (local > 0U) + LogInfo(" Local: %u", local); + else + LogInfo(" Local: random"); + + LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + + LogInfo(" Source TGID: %u", m_srcTGId); + LogInfo(" Source DMR Slot: %u", m_srcSlot); + LogInfo(" Destination TGID: %u", m_dstTGId); + LogInfo(" Destination DMR Slot: %u", m_dstSlot); + LogInfo(" Two-Way Patch: %s", m_twoWayPatch ? "yes" : "no"); + + if (debug) { + LogInfo(" Debug: yes"); + } + + bool dmr = false, p25 = false; + switch (m_digiMode) { + case TX_MODE_DMR: + dmr = true; + break; + case TX_MODE_P25: + p25 = 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->setMetadata(m_identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); + m_network->setConventional(true); + + if (encrypted) { + m_network->setPresharedKey(presharedKey); + } + + m_network->enable(true); + bool ret = m_network->open(); + if (!ret) { + delete m_network; + m_network = nullptr; + LogError(LOG_HOST, "failed to initialize traffic networking!"); + return false; + } + + ::LogSetNetwork(m_network); + + return true; +} + +/* Helper to process DMR network traffic. */ + +void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) +{ + assert(buffer != nullptr); + using namespace dmr; + using namespace dmr::defines; + + if (m_digiMode != TX_MODE_DMR) + return; + + // process network message header + uint32_t seqNo = buffer[4U]; + + uint32_t srcId = __GET_UINT16(buffer, 5U); + uint32_t dstId = __GET_UINT16(buffer, 8U); + + uint8_t controlByte = buffer[14U]; + + FLCO::E flco = (buffer[15U] & 0x40U) == 0x40U ? FLCO::PRIVATE : FLCO::GROUP; + + uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + + if (slotNo > 3U) { + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); + return; + } + + // DMO mode slot disabling + if (slotNo == 1U && !m_network->getDuplex()) { + LogError(LOG_DMR, "DMR/DMO, invalid slot, slotNo = %u", slotNo); + return; + } + + // Individual slot disabling + if (slotNo == 1U && !m_network->getDMRSlot1()) { + LogError(LOG_DMR, "DMR, invalid slot, slot 1 disabled, slotNo = %u", slotNo); + return; + } + if (slotNo == 2U && !m_network->getDMRSlot2()) { + LogError(LOG_DMR, "DMR, invalid slot, slot 2 disabled, slotNo = %u", slotNo); + return; + } + + bool dataSync = (buffer[15U] & 0x20U) == 0x20U; + bool voiceSync = (buffer[15U] & 0x10U) == 0x10U; + + if (m_debug) { + LogDebug(LOG_NET, "DMR, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u", seqNo, srcId, dstId, flco, slotNo, length); + } + + // process raw DMR data bytes + 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); + } + else if (voiceSync) { + ::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); + } + + if (flco == FLCO::GROUP) { + if (srcId == 0) + return; + + // ensure destination ID matches and slot matches + if (dstId != m_srcTGId && dstId != m_dstTGId) + return; + if (slotNo != m_srcSlot && slotNo != m_dstSlot) + return; + + uint32_t actualDstId = m_dstTGId; + if (m_twoWayPatch) { + if (dstId == m_dstTGId) + actualDstId = m_srcTGId; + } else { + if (dstId == m_dstTGId) + return; + } + + // is this a new call stream? + if (m_network->getDMRStreamId(slotNo) != m_rxStreamId) { + m_callInProgress = true; + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_rxStartTime = now; + + LogMessage(LOG_HOST, "DMR, call start, srcId = %u, dstId = %u, slot = %u", srcId, dstId, slotNo); + } + + if (dataSync && (dataType == DataType::TERMINATOR_WITH_LC)) { + // generate DMR network frame + data::NetData dmrData; + dmrData.setSlotNo(m_dstSlot); + dmrData.setDataType(DataType::TERMINATOR_WITH_LC); + dmrData.setSrcId(srcId); + dmrData.setDstId(actualDstId); + dmrData.setFLCO(flco); + dmrData.setControl(controlByte); + + uint8_t n = data[15U] & 0x0FU; + + dmrData.setN(n); + dmrData.setSeqNo(seqNo); + dmrData.setBER(0U); + dmrData.setRSSI(0U); + + dmrData.setData(data.get()); + + m_network->writeDMRTerminator(dmrData, &seqNo, &n, m_dmrEmbeddedData); + m_network->resetDMR(dmrData.getSlotNo()); + + 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, "DMR, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + } + + m_callInProgress = false; + m_rxStartTime = 0U; + m_rxStreamId = 0U; + return; + } + + m_rxStreamId = m_network->getDMRStreamId(slotNo); + + uint8_t* buffer = nullptr; + + // if we can, use the LC from the voice header as to keep all options intact + if (dataSync && (dataType == DataType::VOICE_LC_HEADER)) { + lc::LC lc = lc::LC(); + lc::FullLC fullLC = lc::FullLC(); + lc = *fullLC.decode(data.get(), DataType::VOICE_LC_HEADER); + + LogMessage(LOG_HOST, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X", m_srcSlot, + lc.getSrcId(), lc.getDstId(), flco); + + // send DMR voice header + buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + + lc.setDstId(actualDstId); + m_dmrEmbeddedData.setLC(lc); + + // generate the Slot TYpe + SlotType slotType = SlotType(); + slotType.setDataType(DataType::VOICE_LC_HEADER); + slotType.encode(buffer); + + fullLC.encode(lc, buffer, DataType::VOICE_LC_HEADER); + + // generate DMR network frame + data::NetData dmrData; + dmrData.setSlotNo(m_dstSlot); + dmrData.setDataType(DataType::VOICE_LC_HEADER); + dmrData.setSrcId(srcId); + dmrData.setDstId(actualDstId); + dmrData.setFLCO(flco); + dmrData.setControl(controlByte); + if (m_grantDemand) { + dmrData.setControl(0x80U); // DMR remote grant demand flag + } else { + dmrData.setControl(0U); + } + + uint8_t n = data[15U] & 0x0FU; + + dmrData.setN(n); + dmrData.setSeqNo(seqNo); + dmrData.setBER(0U); + dmrData.setRSSI(0U); + + dmrData.setData(buffer); + + m_network->writeDMR(dmrData, false); + delete[] buffer; + } + + // if we can, use the PI LC from the PI voice header as to keep all options intact + if (dataSync && (dataType == DataType::VOICE_PI_HEADER)) { + lc::PrivacyLC lc = lc::PrivacyLC(); + lc::FullLC fullLC = lc::FullLC(); + lc = *fullLC.decodePI(data.get()); + + LogMessage(LOG_HOST, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_srcSlot, + lc.getAlgId(), lc.getKId(), lc.getDstId()); + + // send DMR voice header + buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + + lc.setDstId(actualDstId); + + // generate the Slot TYpe + SlotType slotType = SlotType(); + slotType.setDataType(DataType::VOICE_PI_HEADER); + slotType.encode(buffer); + + fullLC.encodePI(lc, buffer); + + // generate DMR network frame + data::NetData dmrData; + dmrData.setSlotNo(m_dstSlot); + dmrData.setDataType(DataType::VOICE_PI_HEADER); + dmrData.setSrcId(srcId); + dmrData.setDstId(actualDstId); + dmrData.setFLCO(flco); + dmrData.setControl(controlByte); + if (m_grantDemand) { + dmrData.setControl(0x80U); // DMR remote grant demand flag + } else { + dmrData.setControl(0U); + } + + uint8_t n = data[15U] & 0x0FU; + + dmrData.setN(n); + dmrData.setSeqNo(seqNo); + dmrData.setBER(0U); + dmrData.setRSSI(0U); + + dmrData.setData(buffer); + + m_network->writeDMR(dmrData, false); + delete[] buffer; + } + + if (dataType == DataType::VOICE_SYNC || dataType == DataType::VOICE) { + // send DMR voice + buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + ::memcpy(buffer, data.get(), DMR_FRAME_LENGTH_BYTES); + + uint8_t n = data[15U] & 0x0FU; + + DataType::E dataType = DataType::VOICE_SYNC; + if (n == 0) + dataType = DataType::VOICE_SYNC; + else { + dataType = DataType::VOICE; + + uint8_t lcss = m_dmrEmbeddedData.getData(buffer, n); + + // generated embedded signalling + data::EMB emb = data::EMB(); + emb.setColorCode(0U); + emb.setLCSS(lcss); + emb.encode(buffer); + } + + LogMessage(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_srcSlot, seqNo); + + // generate DMR network frame + data::NetData dmrData; + dmrData.setSlotNo(m_dstSlot); + dmrData.setDataType(dataType); + dmrData.setSrcId(srcId); + dmrData.setDstId(actualDstId); + dmrData.setFLCO(flco); + dmrData.setN(n); + dmrData.setSeqNo(seqNo); + dmrData.setBER(0U); + dmrData.setRSSI(0U); + + dmrData.setData(buffer); + + m_network->writeDMR(dmrData, false); + } + } +} + +/* Helper to process P25 network traffic. */ + +void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) +{ + assert(buffer != nullptr); + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + 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; + + // process network message header + DUID::E duid = (DUID::E)buffer[22U]; + uint8_t MFId = buffer[15U]; + + if (duid == DUID::HDU || duid == DUID::TSDU || duid == DUID::PDU) + return; + + // process raw P25 data bytes + UInt8Array data; + uint8_t frameLength = buffer[23U]; + if (duid == DUID::PDU) { + frameLength = length; + data = std::unique_ptr(new uint8_t[length]); + ::memset(data.get(), 0x00U, length); + ::memcpy(data.get(), buffer, length); + } + else { + if (frameLength <= 24) { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + } + else { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer + 24U, frameLength); + } + } + + // handle LDU, TDU or TSDU frame + uint8_t lco = buffer[4U]; + + uint32_t srcId = __GET_UINT16(buffer, 5U); + uint32_t dstId = __GET_UINT16(buffer, 8U); + + uint8_t lsd1 = buffer[20U]; + uint8_t lsd2 = buffer[21U]; + + FrameType::E frameType = (FrameType::E)buffer[180U]; + + lc::LC control; + data::LowSpeedData lsd; + + control.setLCO(lco); + control.setSrcId(srcId); + control.setDstId(dstId); + control.setMFId(MFId); + + if (!control.isStandardMFId()) { + control.setLCO(LCO::GROUP); + } + else { + if (control.getLCO() == LCO::GROUP_UPDT || control.getLCO() == LCO::RFSS_STS_BCAST) { + control.setLCO(LCO::GROUP); + } + } + + lsd.setLSD1(lsd1); + lsd.setLSD2(lsd2); + + if (control.getLCO() == LCO::GROUP) { + if (srcId == 0) + return; + + // ensure destination ID matches + 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; + } + + if (m_network->getP25StreamId() != m_rxStreamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { + m_callInProgress = true; + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_rxStartTime = now; + + LogMessage(LOG_HOST, "P25, call start, srcId = %u, dstId = %u", srcId, dstId); + + 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); + } + } + + if ((duid == DUID::TDU) || (duid == DUID::TDULC)) { + // ignore TDU's that are grant demands + if (grantDemand) + return; + + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(p25::defines::LCO::GROUP); + lc.setDstId(actualDstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + LogMessage(LOG_HOST, P25_TDU_STR); + + 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(); + uint64_t diff = now - m_rxStartTime; + + LogMessage(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + } + + m_rxStartTime = 0U; + m_rxStreamId = 0U; + + m_callInProgress = false; + m_rxStartTime = 0U; + m_rxStreamId = 0U; + return; + } + + m_rxStreamId = m_network->getP25StreamId(); + + uint8_t* netLDU = new uint8_t[9U * 25U]; + ::memset(netLDU, 0x00U, 9U * 25U); + + int count = 0; + switch (duid) + { + case DUID::LDU1: + if ((data[0U] == DFSIFrameType::LDU1_VOICE1) && (data[22U] == DFSIFrameType::LDU1_VOICE2) && + (data[36U] == DFSIFrameType::LDU1_VOICE3) && (data[53U] == DFSIFrameType::LDU1_VOICE4) && + (data[70U] == DFSIFrameType::LDU1_VOICE5) && (data[87U] == DFSIFrameType::LDU1_VOICE6) && + (data[104U] == DFSIFrameType::LDU1_VOICE7) && (data[121U] == DFSIFrameType::LDU1_VOICE8) && + (data[138U] == DFSIFrameType::LDU1_VOICE9)) { + + dfsi::LC dfsiLC = dfsi::LC(control, lsd); + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE1); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 10U); + count += DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE2); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 26U); + count += DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE3); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 55U); + count += DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE4); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 80U); + count += DFSI_LDU1_VOICE4_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE5); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 105U); + count += DFSI_LDU1_VOICE5_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE6); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 130U); + count += DFSI_LDU1_VOICE6_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE7); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 155U); + count += DFSI_LDU1_VOICE7_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE8); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 180U); + count += DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE9); + dfsiLC.decodeLDU1(data.get() + count, netLDU + 204U); + count += DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; + + LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); + + control = lc::LC(*dfsiLC.control()); + + control.setSrcId(srcId); + control.setDstId(actualDstId); + + // 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]; + if (algoId != ALGO_UNENCRYPT) { + uint16_t kid = __GET_UINT16B(buffer, 182U); + + 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]; + } + + control.setAlgId(algoId); + control.setKId(kid); + control.setMI(mi); + } + } + + m_network->writeP25LDU1(control, lsd, netLDU, frameType); + } + break; + case DUID::LDU2: + if ((data[0U] == DFSIFrameType::LDU2_VOICE10) && (data[22U] == DFSIFrameType::LDU2_VOICE11) && + (data[36U] == DFSIFrameType::LDU2_VOICE12) && (data[53U] == DFSIFrameType::LDU2_VOICE13) && + (data[70U] == DFSIFrameType::LDU2_VOICE14) && (data[87U] == DFSIFrameType::LDU2_VOICE15) && + (data[104U] == DFSIFrameType::LDU2_VOICE16) && (data[121U] == DFSIFrameType::LDU2_VOICE17) && + (data[138U] == DFSIFrameType::LDU2_VOICE18)) { + + dfsi::LC dfsiLC = dfsi::LC(control, lsd); + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE10); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 10U); + count += DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE11); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 26U); + count += DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE12); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 55U); + count += DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE13); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 80U); + count += DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE14); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 105U); + count += DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE15); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 130U); + count += DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE16); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 155U); + count += DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE17); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 180U); + count += DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE18); + dfsiLC.decodeLDU2(data.get() + count, netLDU + 204U); + count += DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; + + LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); + + control = lc::LC(*dfsiLC.control()); + + control.setSrcId(srcId); + control.setDstId(actualDstId); + + m_network->writeP25LDU2(control, lsd, netLDU); + } + break; + + case DUID::HDU: + case DUID::PDU: + case DUID::TDU: + case DUID::TDULC: + case DUID::TSDU: + case DUID::VSELP1: + case DUID::VSELP2: + default: + // this makes GCC happy + break; + } + } +} + +/* Entry point to network processing thread. */ + +void* HostPatch::threadNetworkProcess(void* arg) +{ + 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: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_DMR) { + std::lock_guard lock(HostPatch::m_networkMutex); + UInt8Array dmrBuffer = patch->m_network->readDMR(netReadRet, length); + if (netReadRet) { + patch->processDMRNetwork(dmrBuffer.get(), length); + } + } + + if (patch->m_digiMode == TX_MODE_P25) { + std::lock_guard lock(HostPatch::m_networkMutex); + UInt8Array p25Buffer = patch->m_network->readP25(netReadRet, length); + if (netReadRet) { + patch->processP25Network(p25Buffer.get(), length); + } + } + + 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) +{ + if (data == nullptr) + return; + + // clear buffer for next sequence + ::memset(data, 0x00U, 9U * 25U); + + // fill with null + if (!encrypted) { + ::memcpy(data + 10U, P25DEF::NULL_IMBE, 11U); + ::memcpy(data + 26U, P25DEF::NULL_IMBE, 11U); + ::memcpy(data + 55U, P25DEF::NULL_IMBE, 11U); + + ::memcpy(data + 80U, P25DEF::NULL_IMBE, 11U); + ::memcpy(data + 105U, P25DEF::NULL_IMBE, 11U); + ::memcpy(data + 130U, P25DEF::NULL_IMBE, 11U); + + ::memcpy(data + 155U, P25DEF::NULL_IMBE, 11U); + ::memcpy(data + 180U, P25DEF::NULL_IMBE, 11U); + ::memcpy(data + 204U, P25DEF::NULL_IMBE, 11U); + } + else { + ::memcpy(data + 10U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + ::memcpy(data + 26U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + ::memcpy(data + 55U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + + ::memcpy(data + 80U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + ::memcpy(data + 105U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + ::memcpy(data + 130U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + + ::memcpy(data + 155U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + ::memcpy(data + 180U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + ::memcpy(data + 204U, P25DEF::ENCRYPTED_NULL_IMBE, 11U); + } +} diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h new file mode 100644 index 00000000..2e91a899 --- /dev/null +++ b/src/patch/HostPatch.h @@ -0,0 +1,134 @@ +// 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 + * + */ +/** + * @file HostPatch.h + * @ingroup patch + * @file HostPatch.cpp + * @ingroup patch + */ +#if !defined(__HOST_PATCH_H__) +#define __HOST_PATCH_H__ + +#include "Defines.h" +#include "common/dmr/data/EmbeddedData.h" +#include "common/dmr/lc/LC.h" +#include "common/dmr/lc/PrivacyLC.h" +#include "common/network/udp/Socket.h" +#include "common/yaml/Yaml.h" +#include "common/Timer.h" +#include "network/PeerNetwork.h" + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t TX_MODE_DMR = 1U; +const uint8_t TX_MODE_P25 = 2U; + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements the core service logic. + * @ingroup bridge + */ +class HOST_SW_API HostPatch { +public: + /** + * @brief Initializes a new instance of the HostPatch class. + * @param confFile Full-path to the configuration file. + */ + HostPatch(const std::string& confFile); + /** + * @brief Finalizes a instance of the HostPatch class. + */ + ~HostPatch(); + + /** + * @brief Executes the main host processing loop. + * @returns int Zero if successful, otherwise error occurred. + */ + int run(); + +private: + const std::string& m_confFile; + yaml::Node m_conf; + + network::PeerNetwork* m_network; + + uint32_t m_srcTGId; + uint8_t m_srcSlot; + uint32_t m_dstTGId; + uint8_t m_dstSlot; + bool m_twoWayPatch; + + std::string m_identity; + + uint8_t m_digiMode; + + dmr::data::EmbeddedData m_dmrEmbeddedData; + + bool m_grantDemand; + + bool m_callInProgress; + uint64_t m_rxStartTime; + uint32_t m_rxStreamId; + + bool m_running; + bool m_trace; + bool m_debug; + + static std::mutex m_networkMutex; + + /** + * @brief Reads basic configuration parameters from the INI. + * @returns bool True, if configuration was read successfully, otherwise false. + */ + bool readParams(); + /** + * @brief Initializes network connectivity. + * @returns bool True, if network connectivity was initialized, otherwise false. + */ + bool createNetwork(); + + /** + * @brief Helper to process DMR network traffic. + * @param buffer + * @param length + */ + void processDMRNetwork(uint8_t* buffer, uint32_t length); + + /** + * @brief Helper to process P25 network traffic. + * @param buffer + * @param length + */ + void processP25Network(uint8_t* buffer, uint32_t length); + + /** + * @brief Entry point to network processing thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadNetworkProcess(void* arg); + + /** + * @brief Helper to reset IMBE buffer with null frames. + * @param data Buffer containing frame data. + * @param encrypted Flag indicating whether or not the data is encrypted. + */ + void resetWithNullAudio(uint8_t* data, bool encrypted); +}; + +#endif // __HOST_PATCH_H__ diff --git a/src/patch/PatchMain.cpp b/src/patch/PatchMain.cpp new file mode 100644 index 00000000..382c05cf --- /dev/null +++ b/src/patch/PatchMain.cpp @@ -0,0 +1,228 @@ +// 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 + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "patch/ActivityLog.h" +#include "PatchMain.h" +#include "HostPatch.h" + +using namespace network; +using namespace lookups; + +#include +#include +#include + +#include + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +#define IS(s) (::strcmp(argv[i], s) == 0) + +// --------------------------------------------------------------------------- +// Global Variables +// --------------------------------------------------------------------------- + +#ifndef SIGHUP +#define SIGHUP 1 +#endif + +int g_signal = 0; +std::string g_progExe = std::string(__EXE_NAME__); +std::string g_iniFile = std::string(DEFAULT_CONF_FILE); +std::string g_lockFile = std::string(DEFAULT_LOCK_FILE); + +bool g_foreground = false; +bool g_killed = false; +bool g_hideMessages = false; + +uint8_t* g_gitHashBytes = nullptr; + + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +#if !defined(CATCH2_TEST_COMPILATION) +/* Internal signal handler. */ + +static void sigHandler(int signum) +{ + g_signal = signum; + g_killed = true; +} +#endif + +/* Helper to print a fatal error message and exit. */ + +void fatal(const char* msg, ...) +{ + char buffer[400U]; + ::memset(buffer, 0x20U, 400U); + + va_list vl; + va_start(vl, msg); + + ::vsprintf(buffer, msg, vl); + + va_end(vl); + + ::fprintf(stderr, "%s: FATAL PANIC; %s\n", g_progExe.c_str(), buffer); + exit(EXIT_FAILURE); +} + +/* Helper to pring usage the command line arguments. (And optionally an error.) */ + +void usage(const char* message, const char* arg) +{ + ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); + ::fprintf(stdout, "Copyright (c) 2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); + if (message != nullptr) { + ::fprintf(stderr, "%s: ", g_progExe.c_str()); + ::fprintf(stderr, message, arg); + ::fprintf(stderr, "\n\n"); + } + + ::fprintf(stdout, + "usage: %s [-vhf]" + "[-c ]" + "\n\n" + " -v show version information\n" + " -h show this screen\n" + " -f foreground mode\n" + "\n" + " -c specifies the configuration file to use\n" + "\n" + " -- stop handling options\n", + g_progExe.c_str()); + + exit(EXIT_FAILURE); +} + +/* Helper to validate the command line arguments. */ + +int checkArgs(int argc, char* argv[]) +{ + int i, p = 0; + + // iterate through arguments + for (i = 1; i <= argc; i++) + { + if (argv[i] == nullptr) { + break; + } + + if (*argv[i] != '-') { + continue; + } + else if (IS("--")) { + ++p; + break; + } + else if (IS("-f")) { + g_foreground = true; + } + else if (IS("-c")) { + if (argc-- <= 0) + usage("error: %s", "must specify the configuration file to use"); + g_iniFile = std::string(argv[++i]); + + if (g_iniFile.empty()) + usage("error: %s", "configuration file cannot be blank!"); + + p += 2; + } + else if (IS("-v")) { + ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); + ::fprintf(stdout, "Copyright (c) 2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n"); + ::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n"); + if (argc == 2) + exit(EXIT_SUCCESS); + } + else if (IS("-h")) { + usage(nullptr, nullptr); + if (argc == 2) + exit(EXIT_SUCCESS); + } + else { + usage("unrecognized option `%s'", argv[i]); + } + } + + if (p < 0 || p > argc) { + p = 0; + } + + return ++p; +} + +// --------------------------------------------------------------------------- +// Program Entry Point +// --------------------------------------------------------------------------- +#if !defined(CATCH2_TEST_COMPILATION) +int main(int argc, char** argv) +{ + g_gitHashBytes = new uint8_t[4U]; + ::memset(g_gitHashBytes, 0x00U, 4U); + + uint32_t hash = ::strtoul(__GIT_VER_HASH__, 0, 16); + __SET_UINT32(hash, g_gitHashBytes, 0U); + + if (argv[0] != nullptr && *argv[0] != 0) + g_progExe = std::string(argv[0]); + + if (argc > 1) { + // check arguments + int i = checkArgs(argc, argv); + if (i < argc) { + argc -= i; + argv += i; + } + else { + argc--; + argv++; + } + } + + ::signal(SIGINT, sigHandler); + ::signal(SIGTERM, sigHandler); +#if !defined(_WIN32) + ::signal(SIGHUP, sigHandler); +#endif // !defined(_WIN32) + + int ret = 0; + + do { + g_signal = 0; + g_killed = false; + + HostPatch* patch = new HostPatch(g_iniFile); + ret = patch->run(); + delete patch; + + if (g_signal == SIGINT) + ::LogInfoEx(LOG_HOST, "Exited on receipt of SIGINT"); + + if (g_signal == SIGTERM) + ::LogInfoEx(LOG_HOST, "Exited on receipt of SIGTERM"); + + if (g_signal == SIGHUP) + ::LogInfoEx(LOG_HOST, "Restarting on receipt of SIGHUP"); + } while (g_signal == SIGHUP); + + ::LogFinalise(); + ::ActivityLogFinalise(); + + return ret; +} +#endif \ No newline at end of file diff --git a/src/patch/PatchMain.h b/src/patch/PatchMain.h new file mode 100644 index 00000000..9c2383a2 --- /dev/null +++ b/src/patch/PatchMain.h @@ -0,0 +1,53 @@ +// 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 + * + */ +/** + * @file PatchMain.h + * @ingroup bridge + * @file PatchMain.cpp + * @ingroup bridge + */ +#if !defined(__PATCH_MAIN_H__) +#define __PATCH_MAIN_H__ + +#include "Defines.h" + +#include + +// --------------------------------------------------------------------------- +// Externs +// --------------------------------------------------------------------------- + +/** @brief */ +extern int g_signal; +/** @brief */ +extern std::string g_progExe; +/** @brief */ +extern std::string g_iniFile; +/** @brief */ +extern std::string g_lockFile; + +/** @brief (Global) Flag indicating foreground operation. */ +extern bool g_foreground; +/** @brief (Global) Flag indicating the FNE should stop immediately. */ +extern bool g_killed; + +extern uint8_t* g_gitHashBytes; + +/** + * @brief Helper to trigger a fatal error message. This will cause the program to terminate + * immediately with an error message. + * + * @param msg String format. + * + * This is a variable argument function. + */ +extern HOST_SW_API void fatal(const char* msg, ...); + +#endif // __PATCH_MAIN_H__ diff --git a/src/patch/network/PeerNetwork.cpp b/src/patch/network/PeerNetwork.cpp new file mode 100644 index 00000000..63cd8c22 --- /dev/null +++ b/src/patch/network/PeerNetwork.cpp @@ -0,0 +1,360 @@ +// 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 + * + */ +#include "patch/Defines.h" +#include "common/dmr/data/EMB.h" +#include "common/dmr/lc/FullLC.h" +#include "common/dmr/SlotType.h" +#include "common/network/json/json.h" +#include "common/p25/dfsi/DFSIDefines.h" +#include "common/p25/dfsi/LC.h" +#include "common/Utils.h" +#include "network/PeerNetwork.h" + +using namespace network; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* 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) +{ + assert(!address.empty()); + assert(port > 0U); + assert(!password.empty()); +} + +/* 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) +{ + 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(); + } + + uint32_t messageLength = 0U; + UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType); + if (message == nullptr) { + return false; + } + + return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq(resetSeq), m_p25StreamId); +} + +/* 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) +{ + 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(); + } + + uint32_t messageLength = 0U; + UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data); + if (message == nullptr) { + return false; + } + + return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq(resetSeq), m_p25StreamId); +} + +/* Helper to send a DMR terminator with LC message. */ + +void PeerNetwork::writeDMRTerminator(dmr::data::NetData& data, uint32_t* seqNo, uint8_t* dmrN, dmr::data::EmbeddedData& embeddedData) +{ + using namespace dmr; + using namespace dmr::defines; + + uint8_t n = (uint8_t)((*seqNo - 3U) % 6U); + uint32_t fill = 6U - n; + + uint8_t* buffer = nullptr; + if (n > 0U) { + for (uint32_t i = 0U; i < fill; i++) { + // generate DMR AMBE data + buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + ::memcpy(buffer, SILENCE_DATA, DMR_FRAME_LENGTH_BYTES); + + uint8_t lcss = embeddedData.getData(buffer, n); + + // generated embedded signalling + data::EMB emb = data::EMB(); + emb.setColorCode(0U); + emb.setLCSS(lcss); + emb.encode(buffer); + + // generate DMR network frame + data.setData(buffer); + + writeDMR(data); + + seqNo++; + dmrN++; + delete[] buffer; + } + } + + buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + + // generate DMR LC + lc::LC dmrLC = lc::LC(); + dmrLC.setFLCO(FLCO::GROUP); + dmrLC.setSrcId(data.getSrcId()); + dmrLC.setDstId(data.getDstId()); + + // generate the Slot Type + SlotType slotType = SlotType(); + slotType.setDataType(DataType::TERMINATOR_WITH_LC); + slotType.encode(buffer); + + lc::FullLC fullLC = lc::FullLC(); + fullLC.encode(dmrLC, buffer, DataType::TERMINATOR_WITH_LC); + + // generate DMR network frame + data.setData(buffer); + + writeDMR(data); + + seqNo = 0; + dmrN = 0; +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/* Writes configuration to the network. */ + +bool PeerNetwork::writeConfig() +{ + if (m_loginStreamId == 0U) { + LogWarning(LOG_NET, "BUGBUG: tried to write network authorisation with no stream ID?"); + return false; + } + + const char* software = __NETVER__; + + json::object config = json::object(); + + // identity and frequency + config["identity"].set(m_metadata->identity); // Identity + config["rxFrequency"].set(m_metadata->rxFrequency); // Rx Frequency + config["txFrequency"].set(m_metadata->txFrequency); // Tx Frequency + + // system info + json::object sysInfo = json::object(); + sysInfo["latitude"].set(m_metadata->latitude); // Latitude + sysInfo["longitude"].set(m_metadata->longitude); // Longitude + + sysInfo["height"].set(m_metadata->height); // Height + sysInfo["location"].set(m_metadata->location); // Location + config["info"].set(sysInfo); + + // channel data + json::object channel = json::object(); + channel["txPower"].set(m_metadata->power); // Tx Power + channel["txOffsetMhz"].set(m_metadata->txOffsetMhz); // Tx Offset (Mhz) + channel["chBandwidthKhz"].set(m_metadata->chBandwidthKhz); // Ch. Bandwidth (khz) + channel["channelId"].set(m_metadata->channelId); // Channel ID + channel["channelNo"].set(m_metadata->channelNo); // Channel No + config["channel"].set(channel); + + // RCON + json::object rcon = json::object(); + rcon["password"].set(m_metadata->restApiPassword); // REST API Password + rcon["port"].set(m_metadata->restApiPort); // REST API Port + config["rcon"].set(rcon); + + config["software"].set(std::string(software)); // Software ID + + json::value v = json::value(config); + std::string json = v.serialize(); + + CharArray __buffer = std::make_unique(json.length() + 9U); + char* buffer = __buffer.get(); + + ::memcpy(buffer + 0U, TAG_REPEATER_CONFIG, 4U); + ::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); + } + + return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* 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) +{ + using namespace p25::defines; + using namespace p25::dfsi::defines; + assert(data != nullptr); + + p25::dfsi::LC dfsiLC = p25::dfsi::LC(control, lsd); + + uint8_t* buffer = new uint8_t[P25_LDU1_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); + + // construct P25 message header + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + + // pack DFSI data + uint32_t count = MSG_HDR_SIZE; + uint8_t imbe[RAW_IMBE_LENGTH_BYTES]; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE1); + ::memcpy(imbe, data + 10U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 24U, imbe); + count += DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE2); + ::memcpy(imbe, data + 26U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 46U, imbe); + count += DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE3); + ::memcpy(imbe, data + 55U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 60U, imbe); + count += DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE4); + ::memcpy(imbe, data + 80U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 77U, imbe); + count += DFSI_LDU1_VOICE4_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE5); + ::memcpy(imbe, data + 105U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 94U, imbe); + count += DFSI_LDU1_VOICE5_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE6); + ::memcpy(imbe, data + 130U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 111U, imbe); + count += DFSI_LDU1_VOICE6_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE7); + ::memcpy(imbe, data + 155U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 128U, imbe); + count += DFSI_LDU1_VOICE7_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE8); + ::memcpy(imbe, data + 180U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 145U, imbe); + count += DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU1_VOICE9); + ::memcpy(imbe, data + 204U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU1(buffer + 162U, imbe); + count += DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); + + length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD); + return UInt8Array(buffer); +} + +/* 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) +{ + using namespace p25::defines; + using namespace p25::dfsi::defines; + assert(data != nullptr); + + p25::dfsi::LC dfsiLC = p25::dfsi::LC(control, lsd); + + uint8_t* buffer = new uint8_t[P25_LDU2_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); + + // construct P25 message header + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + + // pack DFSI data + uint32_t count = MSG_HDR_SIZE; + uint8_t imbe[RAW_IMBE_LENGTH_BYTES]; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE10); + ::memcpy(imbe, data + 10U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 24U, imbe); + count += DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE11); + ::memcpy(imbe, data + 26U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 46U, imbe); + count += DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE12); + ::memcpy(imbe, data + 55U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 60U, imbe); + count += DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE13); + ::memcpy(imbe, data + 80U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 77U, imbe); + count += DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE14); + ::memcpy(imbe, data + 105U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 94U, imbe); + count += DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE15); + ::memcpy(imbe, data + 130U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 111U, imbe); + count += DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE16); + ::memcpy(imbe, data + 155U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 128U, imbe); + count += DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE17); + ::memcpy(imbe, data + 180U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 145U, imbe); + count += DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES; + + dfsiLC.setFrameType(DFSIFrameType::LDU2_VOICE18); + ::memcpy(imbe, data + 204U, RAW_IMBE_LENGTH_BYTES); + dfsiLC.encodeLDU2(buffer + 162U, imbe); + count += DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); + + length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD); + return UInt8Array(buffer); +} diff --git a/src/patch/network/PeerNetwork.h b/src/patch/network/PeerNetwork.h new file mode 100644 index 00000000..081856bc --- /dev/null +++ b/src/patch/network/PeerNetwork.h @@ -0,0 +1,128 @@ +// 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 + * + */ +/** + * @defgroup patch_network Networking + * @brief Implementation for the bridge networking. + * @ingroup patch + * + * @file PeerNetwork.h + * @ingroup patch_network + * @file PeerNetwork.cpp + * @ingroup patch_network + */ +#if !defined(__PEER_NETWORK_H__) +#define __PEER_NETWORK_H__ + +#include "Defines.h" +#include "common/dmr/data/EmbeddedData.h" +#include "common/network/Network.h" + +#include +#include + +namespace network +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the core peer networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API PeerNetwork : public Network { + public: + /** + * @brief Initializes a new instance of the PeerNetwork class. + * @param address Network Hostname/IP address to connect to. + * @param port Network port number. + * @param local + * @param peerId Unique ID on the 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); + + /** + * @brief Writes P25 LDU1 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 LDU1 data to send. + * @param[in] frameType DVM P25 frame type. + * @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; + /** + * @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. + * @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; + + /** + * @brief Helper to send a DMR terminator with LC message. + * @param data + * @param seqNo + * @param dmrN + * @param embeddedData + */ + void writeDMRTerminator(dmr::data::NetData& data, uint32_t* seqNo, uint8_t* dmrN, dmr::data::EmbeddedData& embeddedData); + + protected: + /** + * @brief Writes configuration to the network. + * @returns bool True, if configuration was sent, otherwise false. + */ + bool writeConfig() override; + + private: + /** + * @brief Creates an P25 LDU1 frame message. + * + * The data packed into a P25 LDU1 frame message is near standard DFSI messaging, just instead of + * 9 individual frames, they are packed into a single message one right after another. + * + * @param[out] length Length of network message buffer. + * @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 LDU1 data to send. + * @param[in] frameType DVM P25 frame type. + * @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); + /** + * @brief Creates an P25 LDU2 frame message. + * + * The data packed into a P25 LDU2 frame message is near standard DFSI messaging, just instead of + * 9 individual frames, they are packed into a single message one right after another. + * + * @param[out] length Length of network message buffer. + * @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. + * @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); + }; +} // namespace network + +#endif // __PEER_NETWORK_H__ From 853cbf2e6a4c182e5ed5397c62406e8434e01406 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 30 Apr 2025 09:33:59 -0400 Subject: [PATCH 39/41] ensure FNE downstream peers that report as peer link have implicit always rules applied to them (i.e. they will *always* receive *all* traffic); correct order of operations when deleting a peer entry (delete connection *AFTER* removing from peers table); --- src/fne/network/FNENetwork.cpp | 28 ++++++++++----------- src/fne/network/FNENetwork.h | 3 ++- src/fne/network/callhandler/TagDMRData.cpp | 18 +++++++++---- src/fne/network/callhandler/TagNXDNData.cpp | 18 +++++++++---- src/fne/network/callhandler/TagP25Data.cpp | 20 ++++++++++----- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index bd2d3b99..9ffb7843 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -339,12 +339,10 @@ void FNENetwork::clock(uint32_t ms) // remove any peers for (uint32_t peerId : peersToRemove) { FNEPeerConnection* connection = m_peers[peerId]; - m_peers.erase(peerId); + erasePeer(peerId); if (connection != nullptr) { delete connection; } - - erasePeerAffiliations(peerId); } // roll the RTP timestamp if no call is in progress @@ -701,8 +699,8 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - delete connection; network->erasePeer(peerId); + delete connection; } } } @@ -738,8 +736,8 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - delete connection; network->erasePeer(peerId); + delete connection; } } } else { @@ -748,8 +746,8 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) LogWarning(LOG_NET, "PEER %u (%s) RPTL NAK, bad connection state, connectionState = %u", peerId, connection->identity().c_str(), connection->connectionState()); - delete connection; network->erasePeer(peerId); + delete connection; } } else { network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); @@ -856,8 +854,8 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) LogWarning(LOG_NET, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState()); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - delete connection; network->erasePeer(peerId); + delete connection; } } } @@ -890,6 +888,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) LogWarning(LOG_NET, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); network->erasePeer(peerId); + delete connection; } else { // ensure parsed JSON is an object @@ -897,6 +896,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) LogWarning(LOG_NET, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); network->erasePeer(peerId); + delete connection; } else { connection->config(v.get()); @@ -986,6 +986,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) connection->connectionState()); network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); network->erasePeer(peerId); + delete connection; } } } @@ -1006,10 +1007,8 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // validate peer (simple validation really) if (connection->connected() && connection->address() == ip) { LogInfoEx(LOG_NET, "PEER %u (%s) is closing down", peerId, connection->identity().c_str()); - if (network->erasePeer(peerId)) { - network->erasePeerAffiliations(peerId); - delete connection; - } + network->erasePeer(peerId); + delete connection; } } } @@ -1692,7 +1691,7 @@ bool FNENetwork::erasePeerAffiliations(uint32_t peerId) /* Helper to erase the peer from the peers list. */ -bool FNENetwork::erasePeer(uint32_t peerId) +void FNENetwork::erasePeer(uint32_t peerId) { { auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); @@ -1717,7 +1716,8 @@ bool FNENetwork::erasePeer(uint32_t peerId) } } - return true; + // cleanup peer affiliations + erasePeerAffiliations(peerId); } @@ -1775,8 +1775,8 @@ bool FNENetwork::resetPeer(uint32_t peerId) writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_RESET, addr, addrLen); - delete connection; erasePeer(peerId); + delete connection; return true; } diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index b783f205..db18a3de 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -669,10 +669,11 @@ namespace network bool erasePeerAffiliations(uint32_t peerId); /** * @brief Helper to erase the peer from the peers list. + * @note This does not delete or otherwise free the FNEConnection instance! * @param peerId Peer ID. * @returns bool True, if peer was deleted, otherwise false. */ - bool erasePeer(uint32_t peerId); + void erasePeer(uint32_t peerId); /** * @brief Helper to resolve the peer ID to its identity string. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index e6df61ab..6f287dd9 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -664,6 +664,19 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t return false; } + FNEPeerConnection* connection = nullptr; + 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.getFLCO() == FLCO::GROUP) { lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId(), data.getSlotNo()); @@ -696,11 +709,6 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t } } - FNEPeerConnection* connection = nullptr; - if (peerId > 0 && (m_network->m_peers.find(peerId) != m_network->m_peers.end())) { - connection = m_network->m_peers[peerId]; - } - // is this peer a conventional peer? if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index be7e8765..b0d631d8 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -471,6 +471,19 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message return false; } + FNEPeerConnection* connection = nullptr; + 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 (lc.getGroup()) { lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(lc.getDstId()); @@ -503,11 +516,6 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message } } - FNEPeerConnection* connection = nullptr; - if (peerId > 0 && (m_network->m_peers.find(peerId) != m_network->m_peers.end())) { - connection = m_network->m_peers[peerId]; - } - // is this peer a conventional peer? if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 2d2a0b0c..80e36fe5 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -918,6 +918,19 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, return false; } + FNEPeerConnection* connection = nullptr; + 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 + } + } + // always permit a TSDU or PDU if (duid == DUID::TSDU || duid == DUID::PDU) return true; @@ -1023,7 +1036,7 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, } } - // peer always send list takes priority over any following affiliation rules + // peer always send list takes priority over any other rules std::vector alwaysSend = tg.config().alwaysSend(); if (alwaysSend.size() > 0) { auto it = std::find(alwaysSend.begin(), alwaysSend.end(), peerId); @@ -1032,11 +1045,6 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, } } - FNEPeerConnection* connection = nullptr; - if (peerId > 0 && (m_network->m_peers.find(peerId) != m_network->m_peers.end())) { - connection = m_network->m_peers[peerId]; - } - // is this peer a conventional peer? if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { From 4c66b259bd0febb655146353ad860dcd04e83b97 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 30 Apr 2025 09:41:20 -0400 Subject: [PATCH 40/41] document concern over possible null ref concurrency issue; --- src/fne/network/callhandler/TagDMRData.cpp | 4 +++- src/fne/network/callhandler/TagNXDNData.cpp | 4 +++- src/fne/network/callhandler/TagP25Data.cpp | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 6f287dd9..f5eb5dff 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -664,7 +664,9 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t return false; } - FNEPeerConnection* connection = nullptr; + 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]; } diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index b0d631d8..00264b98 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -471,7 +471,9 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message return false; } - FNEPeerConnection* connection = nullptr; + 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]; } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 80e36fe5..43ef6e3d 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -918,7 +918,9 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, return false; } - FNEPeerConnection* connection = nullptr; + 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]; } From 0be3b70d40de0eaf7f5b4e12a2f8818088d77fbf Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 30 Apr 2025 10:01:24 -0400 Subject: [PATCH 41/41] don't be overly aggressive with FNE process niceness, nice of -10 is more then sufficient; --- tools/start-dvm-fne.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/start-dvm-fne.sh b/tools/start-dvm-fne.sh index 8e4f05b0..51778e76 100755 --- a/tools/start-dvm-fne.sh +++ b/tools/start-dvm-fne.sh @@ -39,7 +39,7 @@ if [ -z $2 ]; then fi COMMAND="${R_PATH}/bin/dvmfne -c ${R_PATH}/${CONFIG}" -nice -n -20 ${COMMAND} +nice -n -10 ${COMMAND} if [ -e /tmp/${CONFIG}.pid ]; then rm -f /tmp/${CONFIG}.pid; fi