R04H30 Merge to Master (#88)
* 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; * 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; * don't use void* for the task routines; * rename ThreadPoolCallback to ThreadPoolTask; add some checking around task validness; correct a valgrind issue with RawFrameQueue() write not deleting the buffer before return; * valgrind cleanups; * gate active TG from CC to VC updates at 5s (prevent API spam); * update package version; * don't attempt to send active TG updates to 0.0.0.0; * make notification of active TGs CC -> VC optional; * 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); * lock queue; * 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; * remove mutex used for protecting udp packet deque; * more concurrency solidification; * further concurrency class usage; bump version number to R04H30; * add --boot commandline argument to reboot modem into bootloader without any interactive interface; * cleanup program -h usage display; * incorrect opcode define; * update README.md; * correct incorrect string format for non-useAlternatePortForDiagnostics; * update README.md; * simplify influxdb worker task function; * refactor PL_ACT_PEER_LIST opcode entirely, use zlib and compress list sent and properly block data sent; * don't waste cycles on building the peer list repeatedly, build it once for the cycle; * deduplicate compress/decompress code into a static C++ class; * stylecop file formatting; * add table locking and remove at find; * deduplicate implementation; * cleanup unused label; change FrameQueue's unordered_map to a concurrent one; fix incorrect setting of __lock() for concurrent containers; * add more timestream map locking; * 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; * fix issue with naive approach to handling PL_ACT_PEER_LIST data fragementation; * disable accidental debug code; * instead of asserting, throw a log error message and discard network packet; * 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); * if filter headers or terminators is enabled, and the target peer is in the exclusion list, do not send headers or terminators; * exclusion check should happen before the rewrite check; * implement dvmpatch, this is a new utility that allows simple TG to TG patching; * 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); * document concern over possible null ref concurrency issue; * don't be overly aggressive with FNE process niceness, nice of -10 is more then sufficient;4.30h_maint 2025-05-01
parent
c10087e6cd
commit
af2e7c9c8d
@ -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
|
||||||
@ -0,0 +1,236 @@
|
|||||||
|
// 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 <cerrno>
|
||||||
|
#include <signal.h>
|
||||||
|
#if !defined(_WIN32)
|
||||||
|
#include <unistd.h>
|
||||||
|
#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(ThreadPoolTask* task)
|
||||||
|
{
|
||||||
|
// scope is intentional
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> 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<std::mutex> 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<ThreadPoolTask>(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cond.notify_one();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Starts the thread pool. */
|
||||||
|
|
||||||
|
void ThreadPool::start()
|
||||||
|
{
|
||||||
|
// scope is intentional
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_queueMutex);
|
||||||
|
m_poolState = RUNNING;
|
||||||
|
|
||||||
|
for (uint32_t i = m_workers.size(); i < m_maxWorkerCnt; 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<std::mutex> 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!");
|
||||||
|
#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;
|
||||||
|
threadName << threadPool->m_name << ":worker";
|
||||||
|
#ifdef _GNU_SOURCE
|
||||||
|
::pthread_setname_np(thread->thread, threadName.str().c_str());
|
||||||
|
#endif // _GNU_SOURCE
|
||||||
|
|
||||||
|
ThreadPoolTask* task = nullptr;
|
||||||
|
while (threadPool->m_poolState != STOP) {
|
||||||
|
// scope is intentional
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
task = (threadPool->m_tasks.front()).release();
|
||||||
|
threadPool->m_tasks.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
task->run();
|
||||||
|
delete task;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return 0UL;
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif // defined(_WIN32)
|
||||||
|
}
|
||||||
@ -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 <vector>
|
||||||
|
#include <queue>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a task run by a thread pool worker thread.
|
||||||
|
* @ingroup threading
|
||||||
|
*/
|
||||||
|
class HOST_SW_API ThreadPoolTask {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the ThreadPoolTask class.
|
||||||
|
* @tparam F
|
||||||
|
* @tparam Args
|
||||||
|
* @param f
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
template<class F, class... Args>
|
||||||
|
ThreadPoolTask(F&& f, Args&&... args)
|
||||||
|
{
|
||||||
|
task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
virtual ~ThreadPoolTask() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts the task function.
|
||||||
|
*/
|
||||||
|
void run() { task(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<void()> task;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
* @tparam F
|
||||||
|
* @tparam Args
|
||||||
|
* @param f
|
||||||
|
* @param args
|
||||||
|
* @return ThreadPoolTask*
|
||||||
|
*/
|
||||||
|
template<class F, class... Args>
|
||||||
|
ThreadPoolTask* new_pooltask(F&& f, Args&&... args) { return new ThreadPoolTask(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(ThreadPoolTask* 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<pthread_t> m_workers;
|
||||||
|
std::queue<std::unique_ptr<ThreadPoolTask>> 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__
|
||||||
@ -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 <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
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__
|
||||||
@ -0,0 +1,427 @@
|
|||||||
|
// 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/concurrent/concurrent_lock.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace concurrent
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread-safe std::deque.
|
||||||
|
* @ingroup concurrency
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class deque : public concurrent_lock
|
||||||
|
{
|
||||||
|
using __std = std::deque<T>;
|
||||||
|
public:
|
||||||
|
using iterator = typename __std::iterator;
|
||||||
|
using const_iterator = typename __std::const_iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the deque class.
|
||||||
|
*/
|
||||||
|
deque() : concurrent_lock(),
|
||||||
|
m_deque()
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the deque class.
|
||||||
|
* @param size Initial size of the deque.
|
||||||
|
*/
|
||||||
|
deque(size_t size) : concurrent_lock(),
|
||||||
|
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<T>& 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<T>& 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 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.
|
||||||
|
*/
|
||||||
|
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<T>& Underlying deque.
|
||||||
|
*/
|
||||||
|
std::deque<T>& get()
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_deque;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Gets the underlying deque.
|
||||||
|
* @returns const std::deque<T>& Underlying deque.
|
||||||
|
*/
|
||||||
|
const std::deque<T>& get() const
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_deque;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<T> m_deque;
|
||||||
|
};
|
||||||
|
} // namespace concurrent
|
||||||
|
|
||||||
|
#endif // __CONCURRENCY_DEQUE_H__
|
||||||
@ -0,0 +1,376 @@
|
|||||||
|
// 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/concurrent/concurrent_lock.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace concurrent
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread-safe std::map.
|
||||||
|
* @ingroup concurrency
|
||||||
|
*/
|
||||||
|
template <typename Key, typename T>
|
||||||
|
class map : public concurrent_lock
|
||||||
|
{
|
||||||
|
using __std = std::map<Key, T>;
|
||||||
|
public:
|
||||||
|
using iterator = typename __std::iterator;
|
||||||
|
using const_iterator = typename __std::const_iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the map class.
|
||||||
|
*/
|
||||||
|
map() : concurrent_lock(),
|
||||||
|
m_map()
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the map class.
|
||||||
|
* @param size Initial size of the map.
|
||||||
|
*/
|
||||||
|
map(size_t size) : concurrent_lock(),
|
||||||
|
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<Key, T>& 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<Key, T>& 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 map. 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 map. 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<Key, T>& Underlying map.
|
||||||
|
*/
|
||||||
|
std::map<Key, T>& get()
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_map;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Gets the underlying map.
|
||||||
|
* @returns const std::map<Key, T>& Underlying map.
|
||||||
|
*/
|
||||||
|
const std::map<Key, T>& get() const
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<Key, T> m_map;
|
||||||
|
};
|
||||||
|
} // namespace concurrent
|
||||||
|
|
||||||
|
#endif // __CONCURRENCY_MAP_H__
|
||||||
@ -0,0 +1,376 @@
|
|||||||
|
// 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/concurrent/concurrent_lock.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace concurrent
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread-safe std::unordered_map.
|
||||||
|
* @ingroup concurrency
|
||||||
|
*/
|
||||||
|
template <typename Key, typename T>
|
||||||
|
class unordered_map : public concurrent_lock
|
||||||
|
{
|
||||||
|
using __std = std::unordered_map<Key, T>;
|
||||||
|
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() : concurrent_lock(),
|
||||||
|
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) : concurrent_lock(),
|
||||||
|
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<Key, T>& 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<Key, T>& 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 unordered_map. 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 unordered_map. 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<Key, T>& Underlying unordered_map.
|
||||||
|
*/
|
||||||
|
std::unordered_map<Key, T>& get()
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_map;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Gets the underlying unordered_map.
|
||||||
|
* @returns const std::unordered_map<Key, T>& Underlying unordered_map.
|
||||||
|
*/
|
||||||
|
const std::unordered_map<Key, T>& get() const
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<Key, T> m_map;
|
||||||
|
};
|
||||||
|
} // namespace concurrent
|
||||||
|
|
||||||
|
#endif // __CONCURRENCY_UNORDERED_MAP_H__
|
||||||
@ -0,0 +1,438 @@
|
|||||||
|
// 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 "common/concurrent/concurrent_lock.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace concurrent
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread-safe std::vector.
|
||||||
|
* @ingroup concurrency
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class vector : public concurrent_lock
|
||||||
|
{
|
||||||
|
using __std = std::vector<T>;
|
||||||
|
public:
|
||||||
|
using iterator = typename __std::iterator;
|
||||||
|
using const_iterator = typename __std::const_iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the vector class.
|
||||||
|
*/
|
||||||
|
vector() : concurrent_lock(),
|
||||||
|
m_vector()
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the vector class.
|
||||||
|
* @param size Initial size of the vector.
|
||||||
|
*/
|
||||||
|
vector(size_t size) : concurrent_lock(),
|
||||||
|
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<T>& 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<T>& 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<T>& Underlying vector.
|
||||||
|
*/
|
||||||
|
std::vector<T>& get()
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_vector;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Gets the underlying vector.
|
||||||
|
* @returns const std::vector<T>& Underlying vector.
|
||||||
|
*/
|
||||||
|
const std::vector<T>& get() const
|
||||||
|
{
|
||||||
|
__spinlock();
|
||||||
|
return m_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<T> m_vector;
|
||||||
|
};
|
||||||
|
} // namespace concurrent
|
||||||
|
|
||||||
|
#endif // __CONCURRENCY_VECTOR_H__
|
||||||
@ -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 <cassert>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<uint8_t> 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<uint8_t> 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;
|
||||||
|
}
|
||||||
@ -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__
|
||||||
@ -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 <sys/time.h>
|
||||||
|
#endif // defined(_WIN32)
|
||||||
|
|
||||||
|
#if defined(CATCH2_TEST_COMPILATION)
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 <string>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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__
|
||||||
@ -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"
|
||||||
|
)
|
||||||
@ -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__
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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 <string>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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__
|
||||||
@ -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 <cstdio>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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 <configuration file>]"
|
||||||
|
"\n\n"
|
||||||
|
" -v show version information\n"
|
||||||
|
" -h show this screen\n"
|
||||||
|
" -f foreground mode\n"
|
||||||
|
"\n"
|
||||||
|
" -c <file> 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
|
||||||
@ -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 <string>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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__
|
||||||
@ -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 <cassert>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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<std::string>(m_metadata->identity); // Identity
|
||||||
|
config["rxFrequency"].set<uint32_t>(m_metadata->rxFrequency); // Rx Frequency
|
||||||
|
config["txFrequency"].set<uint32_t>(m_metadata->txFrequency); // Tx Frequency
|
||||||
|
|
||||||
|
// system info
|
||||||
|
json::object sysInfo = json::object();
|
||||||
|
sysInfo["latitude"].set<float>(m_metadata->latitude); // Latitude
|
||||||
|
sysInfo["longitude"].set<float>(m_metadata->longitude); // Longitude
|
||||||
|
|
||||||
|
sysInfo["height"].set<int>(m_metadata->height); // Height
|
||||||
|
sysInfo["location"].set<std::string>(m_metadata->location); // Location
|
||||||
|
config["info"].set<json::object>(sysInfo);
|
||||||
|
|
||||||
|
// channel data
|
||||||
|
json::object channel = json::object();
|
||||||
|
channel["txPower"].set<uint32_t>(m_metadata->power); // Tx Power
|
||||||
|
channel["txOffsetMhz"].set<float>(m_metadata->txOffsetMhz); // Tx Offset (Mhz)
|
||||||
|
channel["chBandwidthKhz"].set<float>(m_metadata->chBandwidthKhz); // Ch. Bandwidth (khz)
|
||||||
|
channel["channelId"].set<uint8_t>(m_metadata->channelId); // Channel ID
|
||||||
|
channel["channelNo"].set<uint32_t>(m_metadata->channelNo); // Channel No
|
||||||
|
config["channel"].set<json::object>(channel);
|
||||||
|
|
||||||
|
// RCON
|
||||||
|
json::object rcon = json::object();
|
||||||
|
rcon["password"].set<std::string>(m_metadata->restApiPassword); // REST API Password
|
||||||
|
rcon["port"].set<uint16_t>(m_metadata->restApiPort); // REST API Port
|
||||||
|
config["rcon"].set<json::object>(rcon);
|
||||||
|
|
||||||
|
config["software"].set<std::string>(std::string(software)); // Software ID
|
||||||
|
|
||||||
|
json::value v = json::value(config);
|
||||||
|
std::string json = v.serialize();
|
||||||
|
|
||||||
|
CharArray __buffer = std::make_unique<char[]>(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);
|
||||||
|
}
|
||||||
@ -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 <string>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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__
|
||||||
Loading…
Reference in new issue