Merge R05A02 (r04k32_dev branch) into Master (#110)

Below is a summary from the various commits, this release updates the major version number. Backward compatibility, with previous versions should not be a problem. But YMMV and its important to note that FNE backward compatibility, specifically might be problematic (FNE's should be at the same release level).

This release changes significant under the hood implementations, including the lowest level of our network stack.

* update version number for next dev version;

* implement passing complex types as refs in lambda functions;

* move KMM NoService response into its own helper function;

* remove data call collisions (this code was really iffy); refactor log messages; refactor default handling of packets the FNE doesn't process; insert null bits before PDU;

* set RSI data properly for outgoing KMM frame;

* implement TEK encryption with an AES-256 KEK;

* add support for unwrapping (decrypting) a KEK encrypted TEK;

* reorganize code and organization for handling P25 OTAR KMMs for better separation and robustness (this is still a WIP and does not function!);

* force hard disable KMF services if OpenSSL isn't compiled in; add instance of P25 crypto to P25OTARService;

* add support to expose KMF services via DLI UDP; add plumbing to support encrypted KMM frames;

* add some debug dumping;

* BUGFIX: null reference when trying to perform old style lookup of timestamp when debugging is enabled;

* standardize KMM logging;

* begin adding support for V.24 PDU data;

* fix C++ namespace shenanigans;

* fix documentation error;

* add support for outgoing V.24 PDU data;

* minor alterations to bridge UDP audio logic; set better default for bridge udpJitter buffer;

* re-engineer entirely how source untimed raw PCM frames over UDP are handled and timed; refactor how udp end of call is handled;

* add support to resize the recv and send buffers on the raw UDP socket; adjust the recv buffer on bridge to use 131K system buffer for the socket (this allows us to hold ~394 frames worth of *raw* PCM + metadata in the socket's internal buffer in the system kernel;

* set socket buffer sizes to larger values then the default;

* annotate Linux limiting the maximum send and recv socket buffer sizes

* update dvmhost submodule;

* use a 2M buffer for bridge UDP audio;

* display the socket buffer resize as warnings and not errors;

* reduce UDP recv/send buffer size to a lower reasonable value of 512K;

* update README.md;

* add more class copy safety;

* correct missing parens;

* correct loop indexing; remove unused variable;

* deprecate unused DIU flag (this isn't what this byte meant in the first place);

* add call start marker to main application log;

* remove SIP classes (this will be done by a different team, and done differently); begin refactoring Log and ActivityLog implementations into C++-type functions to do away with the C-style va_args functions for log message handling; correct -Wstringop-trunction warnings from BaseNetwork.cpp (we hide the warning for these because we are intentionally copying these strings without the nul terminator);

* whoops accidentally blew away activity log transmission to the FNE, fix that...;

* add better concurrency protection to AffiliationLookup; fix issues when getting the granted source ID from a destination ID by properly ensuring the mapping table has an entry first; add srcId to the release grant callback (callback should never call AffiliationLookup gets as it can deadlock, so adding this parameter gives the source ID to the callback so that it doesn't have to do the lookup);

* remove __spinlock() from touchGrant() and isGranted();

* correct bad ordering of log message for call source switching;

* remove blockTrafficTo; implement promiscuous hub mode for FNE (this mode allows the FNE to pass any and all traffic transparently);

* fix typo;

* for the purposes of my OCD fix incorrect one-liner Doxygen documentation (its //!< and not just //!);

* better document peer ID and rid ACL list files;

* add backward for stacktrace support on crash;

* fix FNE compilation when SSL is not available; make Win32 builds work again;

* remove double error message;

* generate stacktrace file if main logger file is not initialized or unavailable;

* reduce the use of unordered_map::at();

* begin relabeling peer-link to peer replication/peer replica;

* implement identity with qualifier, this makes logs (and only logs) easier to trace by uniquely identifying certain peer types, a peer qualifier is simply a symbol appended to the beginning of a resolved peer identity in the FNE logs (@ = SysView, + = External/Linked FNE, % = Replica FNE); silence the Call Source Switched log messages, we will only actually print these if the call source ID changes; fix missed Peer-Link log branding/naming;

* lock m_status and m_statusPVCall before trying to update data elements;

* don't drop out-of-order packets (this was a bad idea, instead log an issue, the real fix for this will be some sort of RTP jitter buffer); if the current packet sequence reaches the maximum allowed, roll over to 0;

* update order of operations for peer ident;

* normalize log messages;

* differentiate a call end collision from a call collision;

* differentiate a call grant collision from a call collision;

* fix missing code commenting;

* initial implementation of naive round-robin HA mechanism. this mechanism allows the FNE master to communicate to a peer connected to it, to announce alternate IPs for the peer to connect to if the primary configured FNE master becomes inaccessible. the HA mechanism requires peer replication to create a loose cluster of FNEs, each FNE in the cluster is configured with the external WAN IP and port for connection to the FNE, and these IPs are then disseminated to all FNE replicas (and downstream connected peers) in the cluster automatically;

* hide debug messages unless debugging is enabled;

* remove old call in progress global; refactor call collisions, allow the call collision timeout be user definable (with 0 disabling call collisions, user beware);

* simplify sendmmsg implementation;

* BUGFIX: correct scenario where traffic from an upstream master to a downstream peer FNE would lose the RTP sequence numbering;

* refactor how RTP multiplexing by stream is handled;

* fix some odd behavior with very fast calls locking up grants on on DVRS channels;

* refactor RTP sequence count handling;

* whoops this stream ID check was intended for non-promiscuous operation only;

* fix memory leak with PacketBuffer::decode(), dont use a direct heap allocated buffer, instead use a unique_ptr buffer;

* simplify implementation;

* rename backtrace namespace to log_stacktrace, when using simple mode the namespace conflicts with a global function backtrace(); add package element for libdw-dev:arm64 for cross compile instructions;

* whoops forgot to update BridgeMain.cpp for log_stacktrace;

* lock the peer list when writing traffic data to prevent peer removal during traffic operations;

* utilize a shared_timed_mutex for peer list locking on the FNE, this better keeps the peer list locked during traffic operations using lock counting vs the original spinlock mechanism;

* better handling locking peer connections during critical state changes;

* whoops this lock should only take place for a connected peer;

* reflect higher requirements for FNE; fix issue on host where sometimes a stuck network call would cause network traffic to stop incorrectly hanging on the previous TG;

* change around some naming;

* typo;

* [EXPERIMENTAL] implement rudimentary spanning tree mechanism to prevent peer looping; refactor how FNENetwork handled peer disconnect cleanups and consolidate into a singular routine;

* [EXPERIMENTAL] enhance detection for case where FNE A and FNE B are cross-peered to each other (dont do this);

* add REST API endpoint to fetch the master tree;

* make sure master peer ID 0 doesnt ever happen;

* log condition where masterPeerId is 0

* SysView peers announce themselves as an external FNE, but we do not consider them FNE peer links;

* SysView masquarades its masterPeerId as itself;

* instead of killing a peer connection instantly on a duplicate conn drop, increase retry time to 30 minutes and allow up to 3 duplicate conn failures before killing;

* better handle duplicate connection disconnect NAKs; add REST API on FNE to force reset a upstream peer connection;

* fix issue with purely ACL virtual FNEs not being able to replicate configuration further down the master tree;

* deprecate the "external FNE" terming for upstream FNE connections and instead call them "neighbor FNE";

* more code cleanup, simplify naming;

* cleanup code; refactor log messages from the FNE to better categorize them; correct issue where a peer reconnecting may trip tree duplicate conn checking;

* enhance STP peer reconnect logic to allow peers announcing the same peerId and masterId to reconnect between spanning tree updates;

* update log colorizer to match new logging categories in the FNE;

* update log colorizer to match new logging categories in the FNE (round 2);

* update log colorizer to match new logging categories in the FNE (round 3);

* update log colorizer to match new logging categories in the FNE (round 4);

* bump major version numbering in a preliminary fashion, at least until group talks about it are done (so this could be permenant);

* fix messaging for fast peer reconnect (it was misleading as a RPTC NAK which it isnt);

* allow reparenting of a STP node if it moves from one tree node to another;

* implement tree cleanups if the downstream announcement removes child leaves or reports no children at all;

* relabel MasterTree to SpanningTree proper;

* whoops errant i++;

* fix up some concurrency problems when dealing with parrot transmissions, due to migration of parrot playback into its own thread;

* make sure to share lock peers while processing in maintainence loop;

* add mutex locking around spanning tree updates;

* simplify log levels, deprecate LogMessage log level in favor of just using LogInfoEx (message and info logs are basically the same thing);

* better classify log messages;

* simplify FNE configuration for peers, make identity a global applying to all peer connections, remove bogus frequency data;

* better report global identity;

* allow overriding the global identity for upstream connections; implement use of identity for spanning tree;

* update config examples to reflect new log levels;

* add back peer "name" field, this is strictly informational to make the config file easier on the brain;

* Win32: bump version number for file resource metadata;

* add documentation for packet payloads for: writeLogin, writeAuthorisation, writeConfig and writePing;

* add some more packet payload documentation;

* continue packet payload documenting;

* minor comment alteration;

* correct DMR data handling; refactor FNE DMR data calls to be structured more akin to P25 data calls;

* use WASAPI by default on Windows;

* better handle out of order blocks for PDUs; reduce packet retry to 2; correct handling ack response packets;

* refactor how buffered UDP datagram queuing is performed;

* relabel static class variables to use s_ and globals to use g_

* enhance colorize-host.sh to terminate color properly;

* enhance colorize-host.sh to terminate color properly;

* RTS PTT will only assert when audio is present.  Add holdoff timer before removing RTS. (#103)

* add option to enable/disable upstream call start/end event logging;

* more work towards a working DLD-type OTAR service for P25;

* fix typos;

* Add carrier operated relay support to dvmbridge. (#104)

* refactor KMMFrame to properly support message number and add framework to support MAC; refactor all derived KMM classes to properly determine KMM frame length and body offsets; change collision in class naming for KeyItem in the CryptoContainer and KeyItem in the p25::kmm namespace; cleanup magic numbers; add initial code for transmitting a rekey command; add KEK crypto wrapping testcase;

* begin work on generating KMM CMAC for message authentication;

* document sections of doc where a test originates test data;

* begin work on generating CBC-MAC for message authentication;

* minor cleanups;

* test should be using generated key;

* CBC-MAC now works properly; CMAC MAC generation works (just gotta fix CMAC MAC key generation);

* complete CBC-MAC implemenation, at least for the provided samples from TIA-102.AACA-C this is now passing for the rekey example provided in section 14.3.4; anything compiling against libcommon.a while OpenSSL is enabled will require OpenSSL -lssl; fix delete vs free in various tests;

* typo

* implement OFB data encryption per TIA-102.AAAD-B; test proper final encrypted output for CBC-MAC check; implement use of cryptAES_PDU() in P25OTARService;

* add experimental DES crypto;

* move Git hash global defines out of Defines.h and into GitHash.h;

* fix copyrights;

* cleanup file headers containing lingering old comments; update fw/modem submodule; update fw/hotspot submodule;

* remove debug trace not needed anymore;

* add support for PDU auxiliary ES headers;

* complete refactor of how P25 PDUs are assembled from user data and disassembled to user data; various corrections for data path nullptr reference issues; implement several tests for testing the P25 PDU assembler;

* remove left over debug code; fix AMBT CRC-32 calculation error, AMBTs calculate the CRC-32 for the PDU themselves the Assembler does not need to do it; correct default value of p25TxNAC;

* add some better error handling for NetRPC;

* validate LC_CALL_TERM peer ID before allowing them to repeat, this fixes an issue where an errant peer on the network could spam LC_CALL_TERM to cause trunked nodes to terminate/kill a call in progress;

* fix issue with dvmpatch not properly evaluating the destination TEK;

* add support to patch to *disable* enc processing and allow frames to pass transparently; implement DES for bridge and patch;

* reorganize application files, I've wanted to do this for a while JSON isn't really a network service, and REST API while it is a network service, I want to be in a separate namespace;

* add multi-block PDU for V.24;

* add some guard rails on PDU reception so we dont overflow ourselves;

* it seems as if PDU_(UN)CONF_END is variable length, it will always be at least 1 block containing the last PDU block, and PDU_(UN)CONF_BLOCK_X is always 4 blocks;

* cleanup dataBlocks pointer array;

* preliminary code for sending PDU frames >3 blocks over V.24;

* use unsigned numbers for these loops the values should never be negative anyway;

* properly encode sequence opcode;

* whoops missing parens;

* initialize these unordered_maps to allow the max connection cap count number of entries (before it was zero and could result in many reallocations of these maps);

* well that was a little naive of me, I didnt multiply the count by the size of the stored elements...;

* reverse course lets do this more intelligently and implement the passthru for the reserve() function on the concurrency classes;

* add STP check to see if a downstream leaf is blown itself away on the tree;

* implement DMR data PDU assembler based on the P25 PDU assembler;

* expand on the files related to an FNE instance for clarity;

* add preliminary support for enabling peers with call priority, this will give those peers the ability to override any current call in progress;

* implement a more solidified peer call priority mechanism using ICC;

* bump WebSocketPP version to conform to new CMake minimum version; (#108)

* fix typo;

* add some documentation around newer protocol additions;

* whoops typo;

* start adding foundational work for future P25 Phase 2;

* decomplicate foundational changes to RS coders for Phase 2;

* added new peer config options to FNE REST api

* add some locks around pkt maps in DiagNetwork;

* better document the master peerId importance;

* ensure buffers are set to nullptr after deleting to prevent double frees;

* skip trying to transmit any buffer that is null;

* add thread safties to SpanningTree;

* correct bad memory allocation;

* revert previous change to add locking to SpanningTree (this is handled externally in FNENetwork and DiagNetwork); fix issue in FNENetwork which could cause a STP deadlock; fix issue incorrectly labeling a peer as allowing call priority in the log when infact the peer was not configured that way;

* make sure during resetPeer() we lock the connection;

* readd SpanningTree internal locking mechanisms;

* make sure pointers are set to null after delete;

* ensure when a FNE loses all its downstream leaves, that it will properly notify upstream FNEs; fix issue where dangling tree nodes were being incorrectly left in the flat peer node list for the spanning tree;

* fix defaultNetIdleTalkgroup being treated as a hex value instead of dec; enhance P25 defaultNetIdleTalkgroup slightly to better pass traffic after RF traffic; enhance P25 defaultNetIdleTalkgroup to pass traffic if there are affiliations to the group;

* how about we dont blatently leave debug messages enabled...;

* BUGFIX: hopefully correct crash condition when trying to erase child nodes;

* followup for last commit, simplify implementation;

* this will be unpopular, but I am deprecating support for cross-compiling for armhf/legacy RPi, maintaining this is already causing problems with OpenSSL and will ultimately handcuff us in the future if we upgrade C++ versions because the legacy toolchain uses GCC 4.9;

* remove deprecated patch;

* update README.md;

* correct some cross-compile shennigans for OpenSSL;

* correct some cross-compile shennigans for aarch64;

* BUGFIX: fix issue where the host would incorrectly reset the voice stream ID during a call;

* match code change from previous bugfix to DMR and NXDN;

* dont use the deprecated OpenSSL1.1 functions, use portable AESCrypto functions instead for low-level AES crypto;

* disable STP reparenting when deserializing children of a tree;

* BUGFIX: fix tged and peered in the case where an empty file or file with no entries is provided for editing;

* BUGFIX: add some validation checks around deserialization reparenting;

* implement P25P2 reed-solomon codes for future use;

* BUGFIX: correct bad check for parrot frames end of call;

---------

Co-authored-by: Lorenzo L. Romero <lorenzolrom@gmail.com>
Co-authored-by: Natalie <jelimoore@gmail.com>
Co-authored-by: W3AXL <29879554+W3AXL@users.noreply.github.com>
v24-dtr-reset-fix 2025-12-03
Bryan Biedenkapp 2 months ago committed by GitHub
parent e9abdf63aa
commit 274a8f23fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -52,7 +52,7 @@ jobs:
needs: [setup] needs: [setup]
strategy: strategy:
matrix: matrix:
arch: ["amd64", "arm", "aarch64", "armhf"] arch: ["amd64", "arm", "aarch64"]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.DATE }}-${{ matrix.arch }} PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.DATE }}-${{ matrix.arch }}
@ -105,12 +105,8 @@ jobs:
build_args="$build_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-s' -DCMAKE_CXX_FLAGS='-s'" build_args="$build_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-s' -DCMAKE_CXX_FLAGS='-s'"
fi fi
if [[ "${{ matrix.arch }}" == 'armhf' ]]; then
cmake $(echo $build_args) -DCROSS_COMPILE_RPI_ARM=1 .
else
cmake $(echo $build_args) \ cmake $(echo $build_args) \
-D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" . -D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" .
fi
make -j $(nproc) make -j $(nproc)
make tarball make tarball
@ -146,7 +142,7 @@ jobs:
needs: [setup, build, create-release] needs: [setup, build, create-release]
strategy: strategy:
matrix: matrix:
arch: ["amd64", "arm", "aarch64", "armhf"] arch: ["amd64", "arm", "aarch64"]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.DATE }}-${{ matrix.arch }} PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.DATE }}-${{ matrix.arch }}

@ -30,7 +30,7 @@ jobs:
needs: [setup] needs: [setup]
strategy: strategy:
matrix: matrix:
arch: ["amd64", "arm", "aarch64", "armhf"] arch: ["amd64", "arm", "aarch64"]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.VERSION }}-${{ matrix.arch }} PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.VERSION }}-${{ matrix.arch }}
@ -77,12 +77,8 @@ jobs:
build_args="$build_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-s' -DCMAKE_CXX_FLAGS='-s'" build_args="$build_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-s' -DCMAKE_CXX_FLAGS='-s'"
if [[ "${{ matrix.arch }}" == 'armhf' ]]; then
cmake $(echo $build_args) -DCROSS_COMPILE_RPI_ARM=1 .
else
cmake $(echo $build_args) \ cmake $(echo $build_args) \
-D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" . -D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" .
fi
make -j $(nproc) make -j $(nproc)
make strip make strip
@ -118,7 +114,7 @@ jobs:
needs: [setup, build, create-release] needs: [setup, build, create-release]
strategy: strategy:
matrix: matrix:
arch: ["amd64", "arm", "aarch64", "armhf"] arch: ["amd64", "arm", "aarch64"]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.VERSION }}-${{ matrix.arch }} PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.VERSION }}-${{ matrix.arch }}

@ -36,9 +36,11 @@ endif (DISABLE_WEBSOCKETS)
# Cross-compile options # Cross-compile options
option(CROSS_COMPILE_ARM "Cross-compile for 32-bit ARM" off) option(CROSS_COMPILE_ARM "Cross-compile for 32-bit ARM" off)
option(CROSS_COMPILE_AARCH64 "Cross-compile for 64-bit ARM" off) option(CROSS_COMPILE_AARCH64 "Cross-compile for 64-bit ARM" off)
option(CROSS_COMPILE_RPI_ARM "Cross-compile for (old RPi) 32-bit ARM" off)
option(COMPILE_WIN32 "Compile for Win32" off) option(COMPILE_WIN32 "Compile for Win32" off)
set(ENABLE_LIBDW_SUPPORT ON)
if (COMPILE_WIN32) if (COMPILE_WIN32)
set(ARCH amd64) set(ARCH amd64)
set(CMAKE_SYSTEM_PROCESSOR amd64) set(CMAKE_SYSTEM_PROCESSOR amd64)
@ -73,69 +75,37 @@ else()
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
if (CROSS_COMPILE_ARM) if (CROSS_COMPILE_ARM)
set(CMAKE_CROSSCOMPILING TRUE)
set(ARCH armhf CACHE STRING "Architecture" FORCE)
set(CMAKE_SYSTEM_PROCESSOR armhf CACHE STRING "System Processor" FORCE)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf CACHE STRING "Package Architecture" FORCE)
message(CHECK_START "Target Architecture - ${ARCH}")
set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE) set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE) set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE)
set(ARCH armhf)
set(CMAKE_SYSTEM_PROCESSOR armhf)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf)
set(OPENSSL_ROOT_DIR /usr/lib/arm-linux-gnueabihf) set(OPENSSL_ROOT_DIR /usr/lib/arm-linux-gnueabihf)
message(CHECK_START "Target Architecture - ${ARCH}") set(CMAKE_INCLUDE_PATH /usr/arm-linux-gnueabihf/include)
set(CMAKE_LIBRARY_PATH /usr/arm-linux-gnueabihf/lib)
message(CHECK_START "Cross compiling for 32-bit ARM - ${CMAKE_C_COMPILER}") message(CHECK_START "Cross compiling for 32-bit ARM - ${CMAKE_C_COMPILER}")
endif (CROSS_COMPILE_ARM) endif (CROSS_COMPILE_ARM)
if (CROSS_COMPILE_AARCH64) if (CROSS_COMPILE_AARCH64)
set(CMAKE_CROSSCOMPILING TRUE)
set(ARCH aarch64 CACHE STRING "Architecture" FORCE)
set(CMAKE_SYSTEM_PROCESSOR aarch64 CACHE STRING "System Processor" FORCE)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE aarch64 CACHE STRING "Package Architecture" FORCE)
message(CHECK_START "Target Architecture - ${ARCH}")
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc CACHE STRING "C compiler" FORCE) set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc CACHE STRING "C compiler" FORCE)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++ CACHE STRING "C++ compiler" FORCE) set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++ CACHE STRING "C++ compiler" FORCE)
set(ARCH arm64)
set(CMAKE_SYSTEM_PROCESSOR arm64)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64)
set(OPENSSL_ROOT_DIR /usr/lib/aarch64-linux-gnu) set(OPENSSL_ROOT_DIR /usr/lib/aarch64-linux-gnu)
message(CHECK_START "Target Architecture - ${ARCH}") set(CMAKE_INCLUDE_PATH /usr/aarch64-linux-gnu/include)
set(CMAKE_LIBRARY_PATH /usr/aarch64-linux-gnu/lib)
message(CHECK_START "Cross compiling for 64-bit ARM - ${CMAKE_C_COMPILER}") message(CHECK_START "Cross compiling for 64-bit ARM - ${CMAKE_C_COMPILER}")
endif (CROSS_COMPILE_AARCH64) endif (CROSS_COMPILE_AARCH64)
option(WITH_RPI_ARM_TOOLS "Specifies the location for the RPI ARM tools" off)
if (WITH_RPI_ARM_TOOLS)
set(RPI_ARM_TOOLS ${WITH_RPI_ARM_TOOLS})
message(CHECK_START "With RPi 1 Tools: ${RPI_ARM_TOOLS}")
endif (WITH_RPI_ARM_TOOLS)
if (CROSS_COMPILE_RPI_ARM)
if (NOT WITH_RPI_ARM_TOOLS)
message("-- Cloning legacy Raspberry Pi compilation toolchain")
include(FetchContent)
FetchContent_Declare(
RPiTools
GIT_REPOSITORY https://github.com/raspberrypi/tools.git
)
FetchContent_MakeAvailable(RPiTools)
set(CMAKE_C_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE)
set(CMAKE_CXX_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE)
message(CHECK_START "Apply OpenSSL library binaries for cross compling (old RPi) 32-bit ARM - ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src")
execute_process(COMMAND tar xzf contrib/openssl_cross_patch.RPI_ARM.tar.gz -C ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
set(OPENSSL_ROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/usr/lib)
else()
set(CMAKE_C_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE)
set(CMAKE_CXX_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE)
message(CHECK_START "Apply OpenSSL library binaries for cross compling (old RPi) 32-bit ARM - ${RPI_ARM_TOOLS}")
execute_process(COMMAND tar xzf contrib/openssl_cross_patch.RPI_ARM.tar.gz -C ${RPI_ARM_TOOLS} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
set(OPENSSL_ROOT_DIR ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/usr/lib)
endif ()
set(ARCH armhf)
set(CMAKE_SYSTEM_PROCESSOR armhf)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf)
message(CHECK_START "Target Architecture - ${ARCH}")
message(CHECK_START "Cross compiling for (old RPi) 32-bit ARM - ${CMAKE_C_COMPILER}")
# No TUI for this
set(ENABLE_TUI_SUPPORT OFF)
message(CHECK_START "Enable TUI support - no; for simplicity RPI_ARM cross-compiling does not support TUI.")
endif (CROSS_COMPILE_RPI_ARM)
# search for programs in the build host directories # search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
@ -174,11 +144,6 @@ if (CROSS_COMPILE_ARM)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-psabi") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-psabi")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-psabi") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-psabi")
endif (CROSS_COMPILE_ARM) endif (CROSS_COMPILE_ARM)
if (CROSS_COMPILE_RPI_ARM)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -std=c99")
endif (CROSS_COMPILE_RPI_ARM)
if (COMPILE_WIN32) if (COMPILE_WIN32)
set(CMAKE_C_FLAGS "/EHsc /D_WIN32 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR") set(CMAKE_C_FLAGS "/EHsc /D_WIN32 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR")
set(CMAKE_CXX_FLAGS "/EHsc /D_WIN32 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR") set(CMAKE_CXX_FLAGS "/EHsc /D_WIN32 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR")
@ -191,6 +156,7 @@ set(CMAKE_INSTALL_PREFIX "/usr/local")
# #
# Library Inclusions # Library Inclusions
# #
# ASIO
if (NOT ASIO_INCLUDED) if (NOT ASIO_INCLUDED)
option(WITH_ASIO "Manually specify the location for the ASIO library" off) option(WITH_ASIO "Manually specify the location for the ASIO library" off)
if (WITH_ASIO) if (WITH_ASIO)
@ -210,6 +176,7 @@ if (NOT ASIO_INCLUDED)
endif (WITH_ASIO) endif (WITH_ASIO)
endif (NOT ASIO_INCLUDED) endif (NOT ASIO_INCLUDED)
# WebSocket++
if (NOT DISABLE_WEBSOCKETS) if (NOT DISABLE_WEBSOCKETS)
if (NOT WEBSOCKETPP_INCLUDED) if (NOT WEBSOCKETPP_INCLUDED)
message("-- Cloning WebSocket++") message("-- Cloning WebSocket++")
@ -217,7 +184,7 @@ if (NOT DISABLE_WEBSOCKETS)
FetchContent_Declare( FetchContent_Declare(
WEBSOCKETPP WEBSOCKETPP
GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git
GIT_TAG 56123c87598f8b1dd471be83ca841ceae07f95ba # 0.8.2 GIT_TAG 4dfe1be74e684acca19ac1cf96cce0df9eac2a2d # 0.8.2 with modified CMake version
) )
FetchContent_MakeAvailable(WEBSOCKETPP) FetchContent_MakeAvailable(WEBSOCKETPP)
add_subdirectory(${websocketpp_SOURCE_DIR} EXCLUDE_FROM_ALL) add_subdirectory(${websocketpp_SOURCE_DIR} EXCLUDE_FROM_ALL)
@ -225,6 +192,33 @@ if (NOT DISABLE_WEBSOCKETS)
endif (NOT WEBSOCKETPP_INCLUDED) endif (NOT WEBSOCKETPP_INCLUDED)
endif (NOT DISABLE_WEBSOCKETS) endif (NOT DISABLE_WEBSOCKETS)
# elfutils (libdw-dev)
if (ENABLE_LIBDW_SUPPORT)
find_path(LIBDW_INCLUDE_DIR NAMES elfutils/libdw.h elfutils/libdwfl.h HINTS /usr /usr/local PATH_SUFFIXES include)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libdw DEFAULT_MSG LIBDW_INCLUDE_DIR)
if (LIBDW_FOUND)
message("-- libdw (libdw-dev) found, detailed backtrace support enabled")
add_definitions(-DBACKWARD_HAS_DW=1)
set(LIBDW_LIBRARY "dw")
else()
message("-- libdw (libdw-dev) not found, simple backtrace only")
add_definitions(-DBACKWARD_HAS_DW=0 -DBACKWARD_HAS_BACKTRACE_SYMBOL=1)
set(LIBDW_INCLUDE_DIR "")
set(LIBDW_LIBRARY "")
endif (LIBDW_FOUND)
mark_as_advanced(LIBDW_INCLUDE_DIR LIBDW_LIBRARY)
else()
message("-- libdw (libdw-dev) disabled, simple backtrace only")
add_definitions(-DBACKWARD_HAS_DW=0 -DBACKWARD_HAS_BACKTRACE_SYMBOL=1)
set(LIBDW_INCLUDE_DIR "")
set(LIBDW_LIBRARY "")
endif (ENABLE_LIBDW_SUPPORT)
# FinalCut
if (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED) if (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED)
message("-- Cloning finalcut") message("-- Cloning finalcut")
include(FetchContent) include(FetchContent)
@ -342,22 +336,6 @@ if (NOT TARGET strip)
COMMAND aarch64-linux-gnu-strip -s dvmbridge COMMAND aarch64-linux-gnu-strip -s dvmbridge
COMMAND aarch64-linux-gnu-strip -s dvmpatch) COMMAND aarch64-linux-gnu-strip -s dvmpatch)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
elseif (CROSS_COMPILE_RPI_ARM)
if (NOT WITH_RPI_ARM_TOOLS)
add_custom_target(strip
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmhost
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmfne
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmpatch)
else()
add_custom_target(strip
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmhost
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmfne
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmpatch)
endif ()
else() else()
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
add_custom_target(strip add_custom_target(strip

@ -32,14 +32,6 @@ aarch64:
&& make -j $(nproc) && make -j $(nproc)
@echo 'Successfully compiled for AARCH64' @echo 'Successfully compiled for AARCH64'
armhf:
@echo 'Cross-Compiling for ARMHF'
mkdir -p "build/$@" && cd "build/$@" \
&& cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-s" -DCMAKE_CXX_FLAGS="-s" \
-DCROSS_COMPILE_RPI_ARM=1 ../.. \
&& make -j $(nproc)
@echo 'Successfully compiled for ARMHF'
clean: clean:
@echo 'Removing all temporary files' @echo 'Removing all temporary files'
git clean -ffxd git clean -ffxd

@ -13,7 +13,7 @@ This project suite generates a few executables:
### Core Applications ### Core Applications
- `dvmhost` host software that connects to the DVM modems (both air interface for repeater and hotspot or P25 DFSI for commerical P25 hardware) and is the primary data processing application for digital modes. [See configuration](#dvmhost-configuration) to configure and calibrate. - `dvmhost` host software that connects to the DVM modems (both air interface for repeater and hotspot or P25 DFSI for commerical P25 hardware) and is the primary data processing application for digital modes. [See configuration](#dvmhost-configuration) to configure and calibrate.
- `dvmfne` a network "core", this provides a central server for `dvmhost` instances to connect to and be networked with, allowing relay of traffic and other data between `dvmhost` instances and other `dvmfne` instances. [See configuration](#dvmfne-configuration) to configure. - `dvmfne` a network core, this provides a central server for `dvmhost` instances (and other instances like consoles or `dvmbridge`s) to connect to and be networked with other instances, allowing switching of traffic and other data between `dvmhost` instances, as well as other peered `dvmfne` instances. [See configuration](#dvmfne-configuration) to configure.
- `dvmbridge` a analog/PCM audio bridge, this provides the capability for analog or PCM audio resources to be connected to a `dvmfne` instance, allowing realtime vocoding of traffic. [See configuration](#dvmbridge-configuration) to configure. - `dvmbridge` a analog/PCM audio bridge, this provides the capability for analog or PCM audio resources to be connected to a `dvmfne` instance, allowing realtime vocoding of traffic. [See configuration](#dvmbridge-configuration) to configure.
- `dvmpatch` a talkgroup patching utility, this provides the capability to manually patch talkgroups of the same digital mode together. [See configuration](#dvmpatch-configuration) to configure. - `dvmpatch` a talkgroup patching utility, this provides the capability to manually patch talkgroups of the same digital mode together. [See configuration](#dvmpatch-configuration) to configure.
- `dvmcmd` a simple command-line utility to send remote control commands to a `dvmhost` or `dvmfne` instance with REST API configured. - `dvmcmd` a simple command-line utility to send remote control commands to a `dvmhost` or `dvmfne` instance with REST API configured.
@ -43,11 +43,14 @@ The DVM Host software requires the library dependancies below. Generally, the so
Alternatively, if you download the ASIO library from the ASIO website and extract it to a location, you can specify the path to the ASIO library using: `-DWITH_ASIO=/path/to/asio`. This method is required when cross-compiling for old Raspberry Pi ARM 32 bit. Alternatively, if you download the ASIO library from the ASIO website and extract it to a location, you can specify the path to the ASIO library using: `-DWITH_ASIO=/path/to/asio`. This method is required when cross-compiling for old Raspberry Pi ARM 32 bit.
If you want detailed stacktrace output on a crash, for compilation ensure `libdw-dev` is also installed. (`apt-get install libdw-dev`). For runtime you will need the `elfutils` package to be installed. (`apt-get install elfutils`).
If cross-compiling ensure you install the appropriate libraries, for example for AARCH64/ARM64: If cross-compiling ensure you install the appropriate libraries, for example for AARCH64/ARM64:
``` ```
sudo dpkg --add-architecture arm64 sudo dpkg --add-architecture arm64
sudo apt-get update sudo apt-get update
sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 libssl-dev:arm64 sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 libssl-dev:arm64
sudo apt-get install libdw-dev:arm64
``` ```
### Build Instructions ### Build Instructions
@ -89,9 +92,8 @@ sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 libssl-dev:arm64
If cross-compiling is required (for either ARM 32bit, 64bit or old Raspberry Pi ARM 32bit), the CMake build system has some options: If cross-compiling is required (for either ARM 32bit, 64bit or old Raspberry Pi ARM 32bit), the CMake build system has some options:
- `-DCROSS_COMPILE_ARM=1` - This will cross-compile dvmhost for generic ARM 32bit. (RPi4 running 32-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer]) - `-DCROSS_COMPILE_ARM=1` - This will cross-compile dvmhost for generic ARM 32bit. (RPi4+ running 32-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer])
- `-DCROSS_COMPILE_AARCH64=1` - This will cross-compile dvmhost for generic ARM 64bit. (RPi4 running 64-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer]) - `-DCROSS_COMPILE_AARCH64=1` - This will cross-compile dvmhost for generic ARM 64bit. (RPi4+ running 64-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer])
- `-DCROSS_COMPILE_RPI_ARM=1` - This will cross-compile for old Raspberry Pi ARM 32 bit. (typically this will be the RPi1, 2 and 3 platforms; see build notes, linked below)
Please note cross-compliation requires you to have the appropriate development packages installed for your system. For ARM 32-bit, on Debian/Ubuntu OS install the "arm-linux-gnueabihf-gcc" and "arm-linux-gnueabihf-g++" packages. For ARM 64-bit, on Debian/Ubuntu OS install the "aarch64-linux-gnu-gcc" and "aarch64-linux-gnu-g++" packages. Please note cross-compliation requires you to have the appropriate development packages installed for your system. For ARM 32-bit, on Debian/Ubuntu OS install the "arm-linux-gnueabihf-gcc" and "arm-linux-gnueabihf-g++" packages. For ARM 64-bit, on Debian/Ubuntu OS install the "aarch64-linux-gnu-gcc" and "aarch64-linux-gnu-g++" packages.
@ -99,7 +101,7 @@ Please note cross-compliation requires you to have the appropriate development p
### Setup TUI (Text-based User Interface) ### Setup TUI (Text-based User Interface)
Since, DVM Host 3.5, the old calibration and setup modes have been deprecated in favor of a ncurses-based TUI. This TUI is optional, and DVM Host can still be compiled without it for systems or devices that cannot utilize it. The DVM TUI applications are optional, and dvmhost can still be compiled without it for systems or devices that cannot utilize it.
- `-DENABLE_SETUP_TUI=0` - This will disable the setup/calibration TUI interface. - `-DENABLE_SETUP_TUI=0` - This will disable the setup/calibration TUI interface.
- `-DENABLE_TUI_SUPPORT=0` - This will disable TUI support project wide. Any projects that require TUI support will not compile, or will have any TUI components disabled. - `-DENABLE_TUI_SUPPORT=0` - This will disable TUI support project wide. Any projects that require TUI support will not compile, or will have any TUI components disabled.
@ -203,6 +205,19 @@ for step 4 to observe frequency error.)
This source repository contains configuration example files within the configs folder, please review `fne-config.example.yml` for the `dvmfne` for details on various configurable options. When first setting up a FNE instance, it is important to properly configure a `talkgroup_rules.example.yml` file, this file defines all the various rules for valid talkgroups and other settings. This source repository contains configuration example files within the configs folder, please review `fne-config.example.yml` for the `dvmfne` for details on various configurable options. When first setting up a FNE instance, it is important to properly configure a `talkgroup_rules.example.yml` file, this file defines all the various rules for valid talkgroups and other settings.
The other configurables on a `dvmfne` instance are within the `fne-config.example.yml`. Some of these are Radio ID (RID) access control listings, Peer ID (PID) access control listings, adjacent site maps for systems with trunked `dvmhost` instances connected, annd many other parameters.
Here is a listing of files in the configs folder in this repo that pertain to FNE configuration:
- `adj_site_map.example.yml` - This is an example configuration file configuring adjacent site mappings for trunked `dvmhost` instances.
- `fne-config.example.yml` - This is the main/primary example configuration file for an FNE instance.
- `peer_list.example.dat` - This is a simple CSV-style file containing access control permissions for peers allowed to connect to the FNE (this includes both downstream peers (like `dvmhost` or `dvmbridge`) and other `dvmfne` instances connecting *to* the FNE instance).
- `rid_acl.example.dat` - This is a simple CSV-style file containing the access control permissions for radio ID (RID)s allowed to use a configured system/network.
- `talkgroup_rules.example.yml` - This is the second most important configuration file for an FNE, this file describes all the talkgroups and their related access control and configuration parameters.
There is another file that is attributed to the FNE that an example is not provided for and that is the `key-container.ekc` file. This file provides cryptographic material needed for providing keyloading functionality across a configured system/network.
Most parameters within the `fne-config.example.yml` should be set to reasonable defaults for simply just starting up a FNE, the only parameters in the configuration that *must* be reviewed before starting up an instance are proper file paths for the ACL and other files used by the FNE.
There is no other real configuration for a `dvmfne` instance other then setting the appropriate parameters within the configuration files. There is no other real configuration for a `dvmfne` instance other then setting the appropriate parameters within the configuration files.
## dvmbridge Configuration ## dvmbridge Configuration
@ -254,12 +269,14 @@ usage: ./dvmhost [-vhdf] [--syslog] [--setup] [--cal][--boot] [-c <configuration
### dvmfne Command Line Parameters ### dvmfne Command Line Parameters
``` ```
usage: ./dvmfne [-vhf][--syslog][-c <configuration file>] usage: ./dvmfne [-vhf][-p][--syslog][-c <configuration file>]
-v show version information -v show version information
-h show this screen -h show this screen
-f foreground mode -f foreground mode
-p promiscuous hub mode
--syslog force logging to syslog --syslog force logging to syslog
-c <file> specifies the configuration file to use -c <file> specifies the configuration file to use
@ -343,8 +360,8 @@ to run on hardware below the minimal requirements, its is unlikely to provide a
`dvmfne`'s requirements can change radically depending on network size. Larger, busier networks will require far more resources then smaller, less busy networks. (`dvmfne` has been tested with daily unique call `dvmfne`'s requirements can change radically depending on network size. Larger, busier networks will require far more resources then smaller, less busy networks. (`dvmfne` has been tested with daily unique call
counts of up to 100,000+ calls on a x86_64 Server with 8GB RAM and 8-core processor, and in this environment it runs comfortably.) counts of up to 100,000+ calls on a x86_64 Server with 8GB RAM and 8-core processor, and in this environment it runs comfortably.)
- Minimal Requirements (known "working"): x86_64 Server, 2MB RAM, Dual Core Processor. - Minimal Requirements (known "working"): x86_64 Server, 2GB RAM, Quad Core Processor.
- Requirements: x86_64 Server, 2GB RAM or better, Dual/Quad or better Core Processor. - Requirements: x86_64 Server, 4GB RAM or better, Quad or better Core Processor.
## Project Notes ## Project Notes
@ -355,12 +372,13 @@ counts of up to 100,000+ calls on a x86_64 Server with 8GB RAM and 8-core proces
- For maximize size reduction before performing a `make install`, `make old_install` or `make tarball` it is recommended to run `make strip` to strip any debug symbols or other unneeded information from the resultant binaries. - For maximize size reduction before performing a `make install`, `make old_install` or `make tarball` it is recommended to run `make strip` to strip any debug symbols or other unneeded information from the resultant binaries.
- By default when cross-compiling for old RPi 1 using the Debian/Ubuntu OS, the toolchain will attempt to fetch and clone the tools automatically. If you already have a copy of these tools, you can specify the location for them with the `-DWITH_RPI_ARM_TOOLS=/path/to/tools`
- For old RPi 1, 2 or 3 using Debian/Ubuntu OS install the standard ARM embedded toolchain (typically "arm-none-eabi-gcc" and "arm-none-eabi-g++"). The CMake build system will automatically attempt to clone down the compilation tools, if you already have the RPI_ARM compilation tools installed use the instructions the above bullet to point to them (this will prevent CMake from attempting to clone the compilation tools).
- The old RPi 1, 2 or 3 builds do not support the TUI when cross compiling. If you require the TUI on these platforms, you have to build the project directly on the target platform vs cross compiling.
- If you have old configuration files, missing comments or new parameters, there is a tool provided in the "tools" directory of the project called `config_annotator.py` this is a Python CLI tool designed to compare an existing configuration file against the example configuration file and recomment and add missing parameters (along with removing illegal/invalid parameters). It is recommended to backup your existing configuration file before running this tool on it. *This tool is only designed for the `dvmhost` configuration file, and no other configuration file!* - If you have old configuration files, missing comments or new parameters, there is a tool provided in the "tools" directory of the project called `config_annotator.py` this is a Python CLI tool designed to compare an existing configuration file against the example configuration file and recomment and add missing parameters (along with removing illegal/invalid parameters). It is recommended to backup your existing configuration file before running this tool on it. *This tool is only designed for the `dvmhost` configuration file, and no other configuration file!*
- By default Linux may restrict the maximum size of the receive and send buffers used by the kernel for network traffic. Please check the limits with `sudo sysctl net.core.rmem_max` and `sudo sysctl net.core.wmem_max`, these should be at least 512K (524288), while DVM will operate in lower
limits, you will see startup errors similar to: "Could not resize socket recv buffer, XXXXXX != 524288", or "Could not resize socket send buffer, XXXXXX != 524288". See the documentation for your specific distribution of Linux to adjust these parameters.
- Offically support for cross-compiling for RPi 1/2/3 was removed in DVM R05A02. There is no support for cross-compiling on these platforms. It is recommended *not* to run DVM on these platforms and to use more modern RPi4+.
## Security Warnings ## Security Warnings
It is highly recommended that the REST API interface not be exposed directly to the internet. If such exposure is wanted/needed, it is highly recommended to proxy the dvmhost REST API through a modern web server (like nginx for example) rather then directly exposing dvmhost's REST API port. It is highly recommended that the REST API interface not be exposed directly to the internet. If such exposure is wanted/needed, it is highly recommended to proxy the dvmhost REST API through a modern web server (like nginx for example) rather then directly exposing dvmhost's REST API port.

@ -10,11 +10,10 @@ daemon: true
# #
# Logging Levels: # Logging Levels:
# 1 - Debug # 1 - Debug
# 2 - Message # 2 - Informational
# 3 - Informational # 3 - Warning
# 4 - Warning # 4 - Error
# 5 - Error # 5 - Fatal
# 6 - Fatal
# #
log: log:
# Console display logging level (used when in foreground). # Console display logging level (used when in foreground).
@ -80,19 +79,9 @@ network:
# Flag indicating UDP audio should follow the USRP format. # Flag indicating UDP audio should follow the USRP format.
udpUsrp: false udpUsrp: false
# Delay in-between UDP audio frames (in ms). # Flag indicating UDP audio frame timing will be performed at the bridge.
# (Some applications will send RTP/PCM audio too fast, requiring a delay in-between packets to # (This allows the sending source to send audio as fast as it wants.)
# be added for appropriate IMBE audio pacing. For most cases a 20ms delay will properly pace udpFrameTiming: false
# audio frames. If set to 0, no frame pacing or jitter buffer will be applied and audio will be
# encoded as fast as it is received.)
udpInterFrameDelay: 0
# Jitter Buffer Length (in ms).
# (This is only applied if utilizing inter frame delay, otherwise packet timing is assumed to be
# properly handled by the source.)
udpJitter: 200
# Flag indicating the UDP audio will be padded with silence during hang time before end of call.
udpHangSilence: true
# Traffic Encryption # Traffic Encryption
tek: tek:
@ -190,3 +179,15 @@ system:
rtsPttEnable: false rtsPttEnable: false
# Serial port device for RTS PTT control (e.g., /dev/ttyUSB0). # Serial port device for RTS PTT control (e.g., /dev/ttyUSB0).
rtsPttPort: "/dev/ttyUSB0" rtsPttPort: "/dev/ttyUSB0"
# Hold-off time (ms) before clearing RTS PTT after last audio output.
rtsPttHoldoffMs: 250
# CTS COR Configuration
# Flag indicating whether CTS-based COR detection is enabled.
ctsCorEnable: false
# Serial port device for CTS COR (e.g., /dev/ttyUSB0). Often same as RTS PTT.
ctsCorPort: "/dev/ttyUSB0"
# Flag indicating whether to invert COR logic (if true, COR LOW triggers instead of HIGH).
ctsCorInvert: false
# Hold-off time (ms) before ending call after CTS COR deasserts.
ctsCorHoldoffMs: 250

@ -10,11 +10,10 @@ daemon: true
# #
# Logging Levels: # Logging Levels:
# 1 - Debug # 1 - Debug
# 2 - Message # 2 - Informational
# 3 - Informational # 3 - Warning
# 4 - Warning # 4 - Error
# 5 - Error # 5 - Fatal
# 6 - Fatal
# #
log: log:
# Flag indicating whether or not the NON-AUTHORITATIVE errors should be logged. # Flag indicating whether or not the NON-AUTHORITATIVE errors should be logged.
@ -640,10 +639,8 @@ system:
# V.24 Modem Configuration # V.24 Modem Configuration
# #
dfsi: dfsi:
# RT/RT flag enabled (0x02) or disabled (0x04) # RT/RT flag.
rtrt: true rtrt: true
# Use the DIU source flag (0x00) instead of the quantar source flag (0x02)
diu: true
# Jitter buffer length in ms # Jitter buffer length in ms
jitter: 200 jitter: 200
# Timer which will reset local/remote call flags if frames aren't received longer than this time in ms # Timer which will reset local/remote call flags if frames aren't received longer than this time in ms

@ -9,11 +9,10 @@ daemon: true
# Logging Configuration # Logging Configuration
# Logging Levels: # Logging Levels:
# 1 - Debug # 1 - Debug
# 2 - Message # 2 - Informational
# 3 - Informational # 3 - Warning
# 4 - Warning # 4 - Error
# 5 - Error # 5 - Fatal
# 6 - Fatal
# #
log: log:
# Console display logging level (used when in foreground). # Console display logging level (used when in foreground).
@ -31,9 +30,12 @@ log:
# #
# Master # Master
# (This is the endpoint that downstream peers connect to for this FNE instance.)
# #
master: master:
# Network Peer ID # Network Peer ID
# NOTE: This ID is a uniquely identifying number. It *MUST* be a unique number across any networks this FNE
# instance connects to. Failure to use a unique number *WILL* cause network issues.
peerId: 9000100 peerId: 9000100
# Hostname/IP address to listen on (blank for all). # Hostname/IP address to listen on (blank for all).
address: 0.0.0.0 address: 0.0.0.0
@ -48,9 +50,24 @@ master:
# Flag indicating whether or not verbose debug logging is enabled. # Flag indicating whether or not verbose debug logging is enabled.
debug: false debug: false
#
# High Availability
#
ha:
# Flag indicating high availability advertisements are enabled.
enable: false
# WAN IP address of this FNE master.
# This IP address is advertised to the network as a globally WAN accessible IP.
advertisedWANAddress: 1.2.3.4
# WAN port for this FNE master.
# This port is advertised to the network as a globally WAN accessible port.
advertisedWANPort: 62031
# Flag indicating whether or not denied traffic will be logged. # Flag indicating whether or not denied traffic will be logged.
# (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.) # (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.)
logDenials: false logDenials: false
# Flag indicating whether or not calls start/end events from a upstream peer will be logged.
logUpstreamCallStartEnd: true
# Maximum number of concurrent packet processing workers. # Maximum number of concurrent packet processing workers.
workers: 16 workers: 16
@ -58,6 +75,17 @@ master:
# Maximum permitted connections (hard maximum is 250 peers). # Maximum permitted connections (hard maximum is 250 peers).
connectionLimit: 100 connectionLimit: 100
# Flag indicating whether or not the peer spanning tree is enabled.
# NOTE: This should not be disabled. Disabling this can cause network loops
# and other issues in a multi-peer FNE network.
enableSpanningTree: true
# Flag indicating whether or not spanning tree changes will be logged.
logSpanningTreeChanges: false
# Flag indicating whether or not the spanning tree allows fast peer reconnects.
# (This is mainly useful for a peer announcing the same master to reconnect rapidly, inbetween
# spanning tree updates.)
spanningTreeFastReconnect: true
# Flag indicating whether or not peer pinging will be reported. # Flag indicating whether or not peer pinging will be reported.
reportPeerPing: true reportPeerPing: true
@ -91,6 +119,16 @@ master:
# Flag indicating whether or not a parrot TG call will only be sent to the originating peer. # Flag indicating whether or not a parrot TG call will only be sent to the originating peer.
parrotOnlyToOrginiatingPeer: false parrotOnlyToOrginiatingPeer: false
# Flag indicating whether or not P25 OTAR KMF services are enabled.
kmfServicesEnabled: false
# Port number to listen on for P25 OTAR KMF services.
kmfOtarPort: 64414
# Flag indicating whether or not verbose debug logging for P25 OTAR KMF services is enabled.
kmfDebug: false
# Amount of time in seconds for a call collision to last before switching over the source of a call.
callCollisionTimeout: 5
# Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating. # Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating.
restrictGrantToAffiliatedOnly: false restrictGrantToAffiliatedOnly: false
# Flag indicating whether or not a private call will only be routed to the network peers the RID registers with. # Flag indicating whether or not a private call will only be routed to the network peers the RID registers with.
@ -103,8 +141,10 @@ master:
# Flag indicating whether or not a conventional site can override affiliation rules. # Flag indicating whether or not a conventional site can override affiliation rules.
allowConvSiteAffOverride: true allowConvSiteAffOverride: true
# Flag indicating whether or not In-Call Control feedback is enabled. # Flag indicating whether or not In-Call Control feedback is enabled.
disallowInCallCtrl: false
# Flag indicating whether or not RID ACL In-Call Control feedback is enabled.
# (This will enforce RID ACLs network wide, regardless of local peer RID ACL setting.) # (This will enforce RID ACLs network wide, regardless of local peer RID ACL setting.)
enableInCallCtrl: false enableRIDInCallCtrl: false
# Flag indicating whether or not unknown/undefined RIDs will be rejected by the FNE. # Flag indicating whether or not unknown/undefined RIDs will be rejected by the FNE.
# (This is a strict rejection, any unknown or undefined RID not in the RID ACL list will be hard rejected.) # (This is a strict rejection, any unknown or undefined RID not in the RID ACL list will be hard rejected.)
rejectUnknownRID: false rejectUnknownRID: false
@ -147,7 +187,7 @@ master:
crypto_container: crypto_container:
# Flag indicating whether or not crypto services are enabled. # Flag indicating whether or not crypto services are enabled.
enable: false enable: false
# Full path to the talkgroup rules file. # Full path to the KFDtool crypto container file.
file: key_container.ekc file: key_container.ekc
# Container password. # Container password.
password: "PASSWORD" password: "PASSWORD"
@ -173,10 +213,11 @@ master:
time: 30 time: 30
# #
# External Peers # Upstream FNE Neighbor Peering
# (This is the list of connections to upstream FNEs this FNE instance should be connected to.)
# #
peers: peers:
- name: EXAMPLEPEER - name: MASTERFNE
# Flag indicating whether or not the peer is enabled. # Flag indicating whether or not the peer is enabled.
enable: true enable: true
# Hostname/IP address of the FNE master to connect to. # Hostname/IP address of the FNE master to connect to.
@ -185,26 +226,9 @@ peers:
masterPort: 32090 masterPort: 32090
# FNE access password. # FNE access password.
password: RPT1234 password: RPT1234
# Textual identity of this peer. # Network Peer ID of this peer on the upstream FNE master.
identity: EXPEER
# Network Peer ID
peerId: 9000990 peerId: 9000990
# List of peer IDs to block traffic to for this peer.
# The purpose of the blockTrafficTo peer ID list is to prevent traffic sourced from a listed peer ID from
# being resent/repeated to this peer. This usually *should* not needed to be configured, and is usually used
# on complex system configurations where traffic loops are possible due to duplicated or redundant peer
# connections.
#
# For example: If we have FNEs: A, B and C, where both B and C are connected to A, and B is also connected to
# C. On FNE B we would have blockTrafficTo entries for each external peer block listing the peer block peer ID's
# for external peer Cs ID on external peer A's entry, and external peer As ID on external peer Cs entry.
#
# Additionally, depending on configured talkgroup rules and other criteria, it may be necessary to also have
# FNE Bs peer ID on FNE Cs peer block entry for FNE A.
#
blockTrafficTo: []
# Flag indicating whether or not peer endpoint networking is encrypted. # Flag indicating whether or not peer endpoint networking is encrypted.
encrypted: false encrypted: false
# AES-256 32-byte Preshared Key # AES-256 32-byte Preshared Key
@ -212,10 +236,6 @@ peers:
# 0 - 9, A - F.) # 0 - 9, A - F.)
presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
#
rxFrequency: 0
#
txFrequency: 0
# Latitude. # Latitude.
latitude: 0.0 latitude: 0.0
# Longitude. # Longitude.
@ -230,6 +250,9 @@ peers:
# System Configuration # System Configuration
# #
system: system:
# Textual identity of this FNE (this is used when peering with upstream FNEs).
identity: MASTERFNE
# Time in seconds between pings to peers. # Time in seconds between pings to peers.
pingTime: 5 pingTime: 5
# Maximum number of missable pings before a peer is considered disconnected. # Maximum number of missable pings before a peer is considered disconnected.

@ -7,11 +7,10 @@
# #
# Logging Levels: # Logging Levels:
# 1 - Debug # 1 - Debug
# 2 - Message # 2 - Informational
# 3 - Informational # 3 - Warning
# 4 - Warning # 4 - Error
# 5 - Error # 5 - Fatal
# 6 - Fatal
# #
log: log:
# Console display logging level (used when in foreground). # Console display logging level (used when in foreground).

@ -10,11 +10,10 @@ daemon: true
# #
# Logging Levels: # Logging Levels:
# 1 - Debug # 1 - Debug
# 2 - Message # 2 - Informational
# 3 - Informational # 3 - Warning
# 4 - Warning # 4 - Error
# 5 - Error # 5 - Fatal
# 6 - Fatal
# #
log: log:
# Console display logging level (used when in foreground). # Console display logging level (used when in foreground).

@ -1,9 +1,32 @@
# #
# This file sets the valid peer IDs allowed on a FNE. # Digital Voice Modem - Peer ID Access Control List
# #
# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled)<newline>" # This file sets the valid peer IDs allowed on a FNE. This file should always end with an empty line!
#1234,,0,,1,0, #
#5678,MYSECUREPASSWORD,0,,0,0, # * PEER ID [REQUIRED] - Unique ID for a peer.
#9876,MYSECUREPASSWORD,1,,0,0, # Peer IDs are valid numbers between 1 and 999999999.
#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0, # * PEER PASSWORD [REQUIRED] - Unique password for this peer to use when authenticating.
#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0, # * PEER REPLICATION [OPTIONAL] - Flag indicating whether or not the peer connection is another FNE and will receive
# full configuration from this FNE. When peer replication is set, and the connection is
# another FNE, that FNE will receive all the talkgroups, radio ID lists, and
# peer lists from this FNE, it will also receive all system traffic.
# * PEER ALIAS [OPTIONAL] - Textual name alias for the peer.
# * CAN REQUEST KEYS [OPTIONAL] - Flag indicating the peer connection is allowed to request encryption keys.
# If this flag is disabled (0), and the connected peer requests and encryption key
# the encryption key request will be dropped and ignored.
# * CAN ISSUE INHIBIT [OPTIONAL] - Flag indicating the peer connection is capable of transmitting inhibit packets.
# If this flag is disabled (0), and the connected peer issues an inhibit to the network
# this FNE will drop the packet and ignore it.
# * HAS CALL PRIORITY [OPTIONAL] - Flag indicating the peer connection has call priority.
# If this flag is disabled (0), and the connected peer tries to transmit over an on going
# call, normal call collision rules are applied to the traffic being transmitted.
# If this flag is enabled (1), and the connected peer tries to transmit over an on going
# call, call collision rules are ignored, and the peer is given priority.
#
# Entry Format: "Peer ID,Peer Password,Peer Replication (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled),Has Call Priority (1 = Enabled / 0 = Disabled)<newline>"
# Examples:
#1234,,0,,1,0,0,
#5678,MYSECUREPASSWORD,0,,0,0,0,
#9876,MYSECUREPASSWORD,1,,0,0,0,
#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0,0,
#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0,0,

@ -1,6 +1,13 @@
# #
# This file sets the valid Radio IDs allowed on a repeater. # Digital Voice Modem - Radio ID Access Control List
# #
# Entry Format: "RID,Enabled (1 = Enabled / 0 = Disabled),Optional Alias,Optional IP Address,<newline>" # This file sets the valid Radio IDs allowed on a repeater. This file should always end with an empty line!
#
# * RID [REQUIRED] - Unique Radio ID.
# * ENABLED [REQUIRED] - Flag indicating whether or not this radio ID entry is enabled and valid.
# * ALIAS [OPTIONAL] - Textual string representing an alias for this radio ID entry.
# * IP ADDRESS [OPTIONAL] - IP Address assigned to this radio ID.
# #
# Entry Format: "RID,Enabled (1 = Enabled / 0 = Disabled),Optional Alias,Optional IP Address,<newline>"
# Example:
#1234,1,RID Alias,IP Address, #1234,1,RID Alias,IP Address,

@ -31,6 +31,8 @@ This document describes, in high-level, the general concepts and procedures for
* DMR: Digital Mobile Radio. (ETSI TS-102) * DMR: Digital Mobile Radio. (ETSI TS-102)
* P25: Project 25. (TIA-102) * P25: Project 25. (TIA-102)
* NXDN: Next Generation Digital Narrowband. * NXDN: Next Generation Digital Narrowband.
* ANALOG: Analog Audio.
* STP: Spanning Tree Protocol.
=== 2.2 General Concept === 2.2 General Concept
The DVM FNE Network Protocol defines a common and standard communications protocol between the, CFNE server application, and DVM end-point applications. The DVM FNE Network Protocol defines a common and standard communications protocol between the, CFNE server application, and DVM end-point applications.
@ -166,9 +168,13 @@ The function parameter of the extension header defines the major operation opcod
|$91 |$91
|This function is used to announce status related to Group Affiliation, Unit Registration and Voice Channel registration. |This function is used to announce status related to Group Affiliation, Unit Registration and Voice Channel registration.
|Peer-Link |Peer Replication
|$92 |$92
|This function is used from both Master -> Peer and Peer -> Master for linked CFNEs operating as a single network. |This function is used from both Master -> Peer and Peer -> Master for linked CFNEs operating as a single network.
|Network Tree
|$93
|This function is used from both Master -> Peer and Peer -> Master for linked CFNEs operating as a single network to transfer spanning tree data.
|=== |===
===== 2.3.1.2.2 Sub-Function ===== 2.3.1.2.2 Sub-Function
@ -193,6 +199,11 @@ The sub-function parameter of the extension header defines the minor operation o
|Protocol |Protocol
|This sub-function is used for NXDN traffic. |This sub-function is used for NXDN traffic.
|Analog
|$0F
|Protocol
|This sub-function is used for analog audio traffic.
|Whitelist RIDs |Whitelist RIDs
|$00 |$00
|Master |Master
@ -258,25 +269,40 @@ The sub-function parameter of the extension header defines the minor operation o
|Announce |Announce
|This sub-function is used for a peer to announce its list of registered voice channels to the master. |This sub-function is used for a peer to announce its list of registered voice channels to the master.
|Peer-Link Talkgroup Transfer |Peer Replication Talkgroup Transfer
|$00 |$00
|Peer-Link |Peer Replication
|This sub-function is used for a CFNE master to transfer the entire certified talkgroup rules configuration to a CFNE linked peer. |This sub-function is used for a CFNE master to transfer the entire certified talkgroup rules configuration to a CFNE peer replica.
|Peer-Link Radio ID Transfer |Peer Replication Radio ID Transfer
|$01 |$01
|Peer-Link |Peer Replication
|This sub-function is used for a CFNE master to transfer the entire certified radio ID lookup configuration to a CFNE linked peer. |This sub-function is used for a CFNE master to transfer the entire certified radio ID lookup configuration to a CFNE peer replica.
|Peer-Link Peer ID Transfer |Peer Replication Peer ID Transfer
|$02 |$02
|Peer-Link |Peer Replication
|This sub-function is used for a CFNE master to transfer the entire certified peer ID lookup configuration to a CFNE linked peer. |This sub-function is used for a CFNE master to transfer the entire certified peer ID lookup configuration to a CFNE peer replica.
|Peer-Link Active Peer List Transfer |Peer Replication Active Peer List Transfer
|$A2 |$A2
|Peer-Link |Peer Replication
|This sub-function is used for a CFNE linked peer to transfer the internal list of active peers to the CFNE master. |This sub-function is used for a CFNE peer replica to transfer the internal list of active peers to the CFNE master.
|Peer Replication HA Parameters
|$A3
|Peer Replication
|This sub-function is used for a CFNE peer replica to transfer the configured HA parameters to the CFNE master.
|Network Tree List
|$00
|Spanning Tree
|This sub-function is used for a CFNE peer to transfer the network tree list to/from the CFNE master.
|Network Tree Disconnect
|$01
|Spanning Tree
|This sub-function is used for a CFNE master to command a disconnect of a duplicated CFNE connection.
|=== |===
=== 2.3 NACK Types === 2.3 NACK Types
@ -321,6 +347,10 @@ This is the basic description of the various packet NACKs that may occur.
|FNE Max Connections |FNE Max Connections
|8 |8
|General failure of the CFNE having reached its maximum allowable connected peers. |General failure of the CFNE having reached its maximum allowable connected peers.
|FNE Duplicate Connection
|9
|Fatal failure where a downstream CFNE peer is already connected to the network.
|=== |===
=== 2.4 TAG Types === 2.4 TAG Types
@ -342,6 +372,10 @@ Some protocol commands (documented in the procedures below) utilize textual mark
|NXDD |NXDD
|Marker for NXDN data packets. |Marker for NXDN data packets.
|Analog Audio Data
|ANOD
|Marker for analog audio packets.
|Repeater/Peer Login |Repeater/Peer Login
|RPTL |RPTL
| |
@ -358,10 +392,18 @@ Some protocol commands (documented in the procedures below) utilize textual mark
|RPTP |RPTP
| |
|Ping Keep-Alive Response |Repeater Grant Request
|RPTG |RPTG
| |
|Repeater Key Request
|RKEY
|
|In-Call Control Request
|ICC
|
|Transfer Message |Transfer Message
|TRNS |TRNS
| |
@ -382,6 +424,10 @@ Some protocol commands (documented in the procedures below) utilize textual mark
|ANNC |ANNC
| |
|Replication
|REPL
|
|=== |===
== 3. Procedures == 3. Procedures

@ -17,9 +17,9 @@ add_library(common STATIC ${common_SRC} ${common_INCLUDE})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_link_libraries(common PRIVATE asio::asio Threads::Threads) target_link_libraries(common PRIVATE asio::asio Threads::Threads)
else () else ()
target_link_libraries(common PRIVATE asio::asio Threads::Threads util) target_link_libraries(common PRIVATE ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads util)
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
target_include_directories(common PRIVATE src src/common) target_include_directories(common PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/common)
# #
## vocoder ## vocoder
@ -39,16 +39,16 @@ endif (ENABLE_SETUP_TUI)
add_executable(dvmhost ${common_INCLUDE} ${dvmhost_SRC}) add_executable(dvmhost ${common_INCLUDE} ${dvmhost_SRC})
if (ENABLE_SETUP_TUI) if (ENABLE_SETUP_TUI)
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads util)
else () else ()
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmhost PRIVATE ${dvmhost_RC}) target_sources(dvmhost PRIVATE ${dvmhost_RC})
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads)
else () else ()
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads util)
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
endif (ENABLE_SETUP_TUI) endif (ENABLE_SETUP_TUI)
target_include_directories(dvmhost PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host) target_include_directories(dvmhost PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/host)
set(CPACK_SET_DESTDIR true) set(CPACK_SET_DESTDIR true)
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local") set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local")
@ -61,7 +61,7 @@ set(CPACK_PACKAGE_VENDOR "DVMProject")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors")
set(CPACK_DEBIAN_PACKAGE_VERSION "R04Jxx") set(CPACK_DEBIAN_PACKAGE_VERSION "R05A")
set(CPACK_DEBIAN_PACKAGE_RELEASE "0") set(CPACK_DEBIAN_PACKAGE_RELEASE "0")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject")
@ -80,8 +80,8 @@ add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmfne PRIVATE ${dvmfne_RC}) target_sources(dvmfne PRIVATE ${dvmfne_RC})
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads)
target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne) target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/fne)
# #
## dvmmon ## dvmmon
@ -89,8 +89,8 @@ target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
include(src/monitor/CMakeLists.txt) include(src/monitor/CMakeLists.txt)
add_executable(dvmmon ${common_INCLUDE} ${dvmmon_SRC}) add_executable(dvmmon ${common_INCLUDE} ${dvmmon_SRC})
target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads)
target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/monitor) target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/host src/monitor)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
# #
@ -99,11 +99,11 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
include(src/sysview/CMakeLists.txt) include(src/sysview/CMakeLists.txt)
add_executable(sysview ${common_INCLUDE} ${sysView_SRC}) add_executable(sysview ${common_INCLUDE} ${sysView_SRC})
target_link_libraries(sysview PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) target_link_libraries(sysview PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads)
if (NOT DISABLE_WEBSOCKETS) if (NOT DISABLE_WEBSOCKETS)
target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} ${websocketpp_SOURCE_DIR} src src/host src/sysview) target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} ${websocketpp_SOURCE_DIR} src src/host src/sysview)
else () else ()
target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/sysview) target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/host src/sysview)
endif (NOT DISABLE_WEBSOCKETS) endif (NOT DISABLE_WEBSOCKETS)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
@ -113,8 +113,8 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
include(src/tged/CMakeLists.txt) include(src/tged/CMakeLists.txt)
add_executable(tged ${common_INCLUDE} ${tged_SRC}) add_executable(tged ${common_INCLUDE} ${tged_SRC})
target_link_libraries(tged PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) target_link_libraries(tged PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads)
target_include_directories(tged PRIVATE ${OPENSSL_INCLUDE_DIR} websocketpp src src/host src/tged) target_include_directories(tged PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} websocketpp src src/host src/tged)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
# #
@ -123,8 +123,8 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
include(src/peered/CMakeLists.txt) include(src/peered/CMakeLists.txt)
add_executable(peered ${common_INCLUDE} ${peered_SRC}) add_executable(peered ${common_INCLUDE} ${peered_SRC})
target_link_libraries(peered PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) target_link_libraries(peered PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads)
target_include_directories(peered PRIVATE ${OPENSSL_INCLUDE_DIR} websocketpp src src/host src/peered) target_include_directories(peered PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} websocketpp src src/host src/peered)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
# #
@ -135,8 +135,8 @@ add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmcmd PRIVATE ${dvmcmd_RC}) target_sources(dvmcmd PRIVATE ${dvmcmd_RC})
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads)
target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/remote)
# #
## dvmbridge ## dvmbridge
@ -145,15 +145,15 @@ include(src/bridge/CMakeLists.txt)
add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC}) add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmbridge PRIVATE ${bridge_RC}) target_sources(dvmbridge PRIVATE ${bridge_RC})
target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads)
else () else ()
if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf")
target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} dl atomic asio::asio Threads::Threads) target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} dl atomic asio::asio Threads::Threads)
else () else ()
target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} dl asio::asio Threads::Threads)
endif (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") endif (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf")
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} src src/bridge) target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/bridge)
# #
## dvmpatch ## dvmpatch
@ -162,8 +162,8 @@ include(src/patch/CMakeLists.txt)
add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC}) add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmpatch PRIVATE ${patch_RC}) target_sources(dvmpatch PRIVATE ${patch_RC})
target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads)
else () else ()
target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} dl asio::asio Threads::Threads)
endif (COMPILE_WIN32) endif (COMPILE_WIN32)
target_include_directories(dvmpatch PRIVATE ${OPENSSL_INCLUDE_DIR} src src/patch) target_include_directories(dvmpatch PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/patch)

@ -48,6 +48,7 @@ option(DEBUG_RINGBUFFER "" off)
option(DEBUG_HTTP_PAYLOAD "" off) option(DEBUG_HTTP_PAYLOAD "" off)
option(DEBUG_TRELLIS "" off) option(DEBUG_TRELLIS "" off)
option(DEBUG_COMPRESS "" off) option(DEBUG_COMPRESS "" off)
option(DEBUG_RTP_MUX "" off)
if (DEBUG_DMR_PDU_DATA) if (DEBUG_DMR_PDU_DATA)
message(CHECK_START "DMR PDU Data Debug") message(CHECK_START "DMR PDU Data Debug")
@ -149,6 +150,10 @@ if (DEBUG_COMPRESS)
message(CHECK_START "zlib Compression Debug") message(CHECK_START "zlib Compression Debug")
add_definitions(-DDEBUG_COMPRESS) add_definitions(-DDEBUG_COMPRESS)
endif (DEBUG_COMPRESS) endif (DEBUG_COMPRESS)
if (DEBUG_RTP_MUX)
message(CHECK_START "RTP Mux Debug")
add_definitions(-DDEBUG_RTP_MUX)
endif (DEBUG_RTP_MUX)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
@ -179,6 +184,7 @@ endif (HAVE_SENDMMSG)
# are we enabling SSL support? # are we enabling SSL support?
if (NOT COMPILE_WIN32) if (NOT COMPILE_WIN32)
message(STATUS "OpenSSL root dir: ${OPENSSL_ROOT_DIR}")
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
if (OpenSSL_FOUND) if (OpenSSL_FOUND)
message(STATUS "OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") message(STATUS "OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")

@ -4,19 +4,13 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "ActivityLog.h" #include "ActivityLog.h"
#include "common/network/BaseNetwork.h" #include "common/network/BaseNetwork.h"
#include "common/Log.h" // for CurrentLogFileLevel() and LogGetNetwork() #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) #if defined(CATCH2_TEST_COMPILATION)
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#endif #endif
@ -39,12 +33,12 @@ const uint32_t ACT_LOG_BUFFER_LEN = 501U;
// Global Variables // Global Variables
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static std::string m_actFilePath; static std::string g_actFilePath;
static std::string m_actFileRoot; static std::string g_actFileRoot;
static FILE* m_actFpLog = nullptr; static FILE* g_actFpLog = nullptr;
static struct tm m_actTm; static struct tm g_actTm;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Global Functions // Global Functions
@ -62,22 +56,22 @@ static bool ActivityLogOpen()
struct tm* tm = ::gmtime(&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 (tm->tm_mday == g_actTm.tm_mday && tm->tm_mon == g_actTm.tm_mon && tm->tm_year == g_actTm.tm_year) {
if (m_actFpLog != nullptr) if (g_actFpLog != nullptr)
return true; return true;
} }
else { else {
if (m_actFpLog != nullptr) if (g_actFpLog != nullptr)
::fclose(m_actFpLog); ::fclose(g_actFpLog);
} }
char filename[200U]; 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); ::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"); g_actFpLog = ::fopen(filename, "a+t");
m_actTm = *tm; g_actTm = *tm;
return m_actFpLog != nullptr; return g_actFpLog != nullptr;
} }
/* Initializes the activity log. */ /* Initializes the activity log. */
@ -87,8 +81,8 @@ bool ActivityLogInitialise(const std::string& filePath, const std::string& fileR
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
return true; return true;
#endif #endif
m_actFilePath = filePath; g_actFilePath = filePath;
m_actFileRoot = fileRoot; g_actFileRoot = fileRoot;
return ::ActivityLogOpen(); return ::ActivityLogOpen();
} }
@ -100,56 +94,34 @@ void ActivityLogFinalise()
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
return; return;
#endif #endif
if (m_actFpLog != nullptr) if (g_actFpLog != nullptr)
::fclose(m_actFpLog); ::fclose(g_actFpLog);
} }
/* Writes a new entry to the activity log. */ /* Writes a new entry to the activity log. */
void ActivityLog(const char* msg, ...) void log_internal::ActivityLogInternal(const std::string& log)
{ {
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
return; return;
#endif #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(); bool ret = ::ActivityLogOpen();
if (!ret) if (!ret)
return; return;
if (LogGetNetwork() != nullptr) {
network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork();;
network->writeActLog(buffer);
}
if (CurrentLogFileLevel() == 0U) if (CurrentLogFileLevel() == 0U)
return; return;
::fprintf(m_actFpLog, "%s\n", buffer); if (LogGetNetwork() != nullptr) {
::fflush(m_actFpLog); network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork();
network->writeActLog(log.c_str());
}
::fprintf(g_actFpLog, "%s\n", log.c_str());
::fflush(g_actFpLog);
if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) {
::fprintf(stdout, "%s" EOL, buffer); ::fprintf(stdout, "%s" EOL, log.c_str());
::fflush(stdout); ::fflush(stdout);
} }
} }

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -18,12 +18,28 @@
#include "Defines.h" #include "Defines.h"
#if defined(_WIN32)
#include "common/Clock.h"
#else
#include <sys/time.h>
#endif // defined(_WIN32)
#include <string> #include <string>
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Global Functions // Global Functions
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
namespace log_internal
{
/**
* @brief Writes a new entry to the diagnostics log.
* @param level Log level for entry.
* @param log Fully formatted log message.
*/
extern HOST_SW_API void ActivityLogInternal(const std::string& log);
} // namespace log_internal
/** /**
* @brief Initializes the activity log. * @brief Initializes the activity log.
* @param filePath File path for the log file. * @param filePath File path for the log file.
@ -34,12 +50,45 @@ extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const
* @brief Finalizes the activity log. * @brief Finalizes the activity log.
*/ */
extern HOST_SW_API void ActivityLogFinalise(); extern HOST_SW_API void ActivityLogFinalise();
/** /**
* @brief Writes a new entry to the activity log. * @brief Writes a new entry to the diagnostics log.
* @param msg String format. * @param fmt String format.
* *
* This is a variable argument function. * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead.
*/ */
extern HOST_SW_API void ActivityLog(const char* msg, ...); template<typename ... Args>
HOST_SW_API void ActivityLog(const std::string& fmt, Args... args)
{
using namespace log_internal;
int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0'
if (size_s <= 0) {
throw std::runtime_error("Error during formatting.");
}
int prefixLen = 0;
char prefixBuf[256];
time_t now;
::time(&now);
struct tm* tm = ::localtime(&now);
struct timeval nowMillis;
::gettimeofday(&nowMillis, NULL);
prefixLen = ::sprintf(prefixBuf, "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);
auto size = static_cast<size_t>(size_s);
auto buf = std::make_unique<char[]>(size);
std::snprintf(buf.get(), size, fmt.c_str(), args ...);
std::string prefix = std::string(prefixBuf, prefixBuf + prefixLen);
std::string msg = std::string(buf.get(), buf.get() + size - 1);
ActivityLogInternal(std::string(prefix + msg));
}
#endif // __ACTIVITY_LOG_H__ #endif // __ACTIVITY_LOG_H__

@ -118,7 +118,7 @@ void usage(const char* message, const char* arg)
" -o output audio device\n" " -o output audio device\n"
#ifdef _WIN32 #ifdef _WIN32
"\n" "\n"
" -wasapi use WASAPI on Windows\n" " -winmm use WinMM audio on Windows\n"
#endif #endif
"\n" "\n"
" -c <file> specifies the configuration file to use\n" " -c <file> specifies the configuration file to use\n"
@ -204,10 +204,10 @@ int checkArgs(int argc, char* argv[])
p += 2; p += 2;
} }
#ifdef _WIN32 #ifdef _WIN32
else if (IS("-wasapi")) { else if (IS("-winmm")) {
// Windows // Windows
g_backends[0] = ma_backend_wasapi; g_backends[0] = ma_backend_winmm;
g_backends[1] = ma_backend_winmm; g_backends[1] = ma_backend_wasapi;
g_backends[2] = ma_backend_null; g_backends[2] = ma_backend_null;
} }
#endif #endif
@ -246,8 +246,8 @@ int main(int argc, char** argv)
#ifdef _WIN32 #ifdef _WIN32
// Windows // Windows
g_backends[0] = ma_backend_winmm; g_backends[0] = ma_backend_wasapi;
g_backends[1] = ma_backend_wasapi; g_backends[1] = ma_backend_winmm;
g_backends[2] = ma_backend_null; g_backends[2] = ma_backend_null;
#else #else
// Linux // Linux
@ -281,6 +281,8 @@ int main(int argc, char** argv)
} }
} }
log_stacktrace::SignalHandling sh(g_foreground);
::signal(SIGINT, sigHandler); ::signal(SIGINT, sigHandler);
::signal(SIGTERM, sigHandler); ::signal(SIGTERM, sigHandler);
#if !defined(_WIN32) #if !defined(_WIN32)

@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Bridge
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Lorenzo L. Romero, K2LLR
*/
/**
* @file CtsCorController.cpp
* @ingroup bridge
*/
#include "Defines.h"
#include "CtsCorController.h"
#if !defined(_WIN32)
#include <errno.h>
#endif
CtsCorController::CtsCorController(const std::string& port)
: m_port(port), m_isOpen(false), m_ownsFd(true)
#if defined(_WIN32)
, m_fd(INVALID_HANDLE_VALUE)
#else
, m_fd(-1)
#endif // defined(_WIN32)
{
}
CtsCorController::~CtsCorController()
{
close();
}
bool CtsCorController::open(int reuseFd)
{
if (m_isOpen)
return true;
#if defined(_WIN32)
std::string deviceName = m_port;
if (deviceName.find("\\\\.\\") == std::string::npos) {
deviceName = "\\\\." + m_port;
}
m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_fd == INVALID_HANDLE_VALUE) {
::LogError(LOG_HOST, "Cannot open CTS COR device - %s, err=%04lx", m_port.c_str(), ::GetLastError());
return false;
}
DCB dcb;
if (::GetCommState(m_fd, &dcb) == 0) {
::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError());
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
return false;
}
dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.fParity = FALSE;
dcb.StopBits = ONESTOPBIT;
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDsrSensitivity = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
if (::SetCommState(m_fd, &dcb) == 0) {
::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError());
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
return false;
}
#else
// If reusing an existing file descriptor from RTS PTT, don't open a new one
if (reuseFd >= 0) {
m_fd = reuseFd;
m_ownsFd = false; // Only COR can close file descriptor
::LogInfo(LOG_HOST, "CTS COR Controller reusing file descriptor from RTS PTT on %s", m_port.c_str());
m_isOpen = true;
return true;
}
m_ownsFd = true; // COR owns the file descriptor
// Open port if not available
m_fd = ::open(m_port.c_str(), O_RDONLY | O_NOCTTY | O_NDELAY, 0);
if (m_fd < 0) {
// Try rw if ro fails
m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0);
if (m_fd < 0) {
::LogError(LOG_HOST, "Cannot open CTS COR device - %s", m_port.c_str());
return false;
}
}
if (::isatty(m_fd) == 0) {
::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str());
::close(m_fd);
m_fd = -1;
return false;
}
// Save current RTS state before configuring termios
int savedModemState = 0;
if (::ioctl(m_fd, TIOCMGET, &savedModemState) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
::close(m_fd);
m_fd = -1;
return false;
}
bool savedRtsState = (savedModemState & TIOCM_RTS) != 0;
if (!setTermios()) {
::close(m_fd);
m_fd = -1;
return false;
}
// Restore RTS to its original state
int currentModemState = 0;
if (::ioctl(m_fd, TIOCMGET, &currentModemState) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s after termios", m_port.c_str());
::close(m_fd);
m_fd = -1;
return false;
}
bool currentRtsState = (currentModemState & TIOCM_RTS) != 0;
if (currentRtsState != savedRtsState) {
// Restore RTS to original state
if (savedRtsState) {
currentModemState |= TIOCM_RTS;
} else {
currentModemState &= ~TIOCM_RTS;
}
if (::ioctl(m_fd, TIOCMSET, &currentModemState) < 0) {
::LogError(LOG_HOST, "Cannot restore RTS state for %s", m_port.c_str());
::close(m_fd);
m_fd = -1;
return false;
}
::LogDebug(LOG_HOST, "CTS COR: Restored RTS to %s on %s", savedRtsState ? "HIGH" : "LOW", m_port.c_str());
}
#endif // defined(_WIN32)
::LogInfo(LOG_HOST, "CTS COR Controller opened on %s (RTS preserved)", m_port.c_str());
m_isOpen = true;
return true;
}
void CtsCorController::close()
{
if (!m_isOpen)
return;
#if defined(_WIN32)
if (m_fd != INVALID_HANDLE_VALUE) {
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
}
#else
// Only close the file descriptor if we opened it ourselves
// If we're reusing a descriptor from RTS PTT, don't close it
if (m_fd != -1 && m_ownsFd) {
::close(m_fd);
m_fd = -1;
} else if (m_fd != -1 && !m_ownsFd) {
m_fd = -1;
}
#endif // defined(_WIN32)
m_isOpen = false;
::LogInfo(LOG_HOST, "CTS COR Controller closed");
}
bool CtsCorController::isCtsAsserted()
{
if (!m_isOpen)
return false;
#if defined(_WIN32)
DWORD modemStat = 0;
if (::GetCommModemStatus(m_fd, &modemStat) == 0) {
::LogError(LOG_HOST, "Cannot read modem status for %s, err=%04lx", m_port.c_str(), ::GetLastError());
return false;
}
return (modemStat & MS_CTS_ON) != 0;
#else
int modemState = 0;
if (::ioctl(m_fd, TIOCMGET, &modemState) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
return false;
}
return (modemState & TIOCM_CTS) != 0;
#endif // defined(_WIN32)
}
bool CtsCorController::setTermios()
{
#if !defined(_WIN32)
termios termios;
if (::tcgetattr(m_fd, &termios) < 0) {
::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str());
return false;
}
termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK);
termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL);
termios.c_iflag &= ~(IXON | IXOFF | IXANY);
termios.c_oflag &= ~(OPOST);
// Important: Disable hardware flow control (CRTSCTS) to avoid affecting RTS
// We only want to read CTS, not control RTS
termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS);
termios.c_cflag |= (CS8 | CLOCAL | CREAD);
termios.c_lflag &= ~(ISIG | ICANON | IEXTEN);
termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
termios.c_cc[VMIN] = 0;
termios.c_cc[VTIME] = 10;
::cfsetospeed(&termios, B9600);
::cfsetispeed(&termios, B9600);
if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) {
::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str());
return false;
}
#endif // !defined(_WIN32)
return true;
}

@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Bridge
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Lorenzo L. Romero, K2LLR
*/
/**
* @file CtsCorController.h
* @ingroup bridge
*/
#if !defined(__CTS_COR_CONTROLLER_H__)
#define __CTS_COR_CONTROLLER_H__
#include "Defines.h"
#include "common/Log.h"
#include <string>
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#endif // defined(_WIN32)
/**
* @brief This class implements CTS-based COR detection for the bridge.
* @ingroup bridge
*/
class HOST_SW_API CtsCorController {
public:
/**
* @brief Initializes a new instance of the CtsCorController class.
* @param port Serial port device (e.g., /dev/ttyUSB0).
*/
CtsCorController(const std::string& port);
/**
* @brief Finalizes a instance of the CtsCorController class.
*/
~CtsCorController();
/**
* @brief Opens the serial port for CTS readback.
* @param reuseFd Optional file descriptor to reuse (when sharing port with RTS PTT).
* @returns bool True, if port was opened successfully, otherwise false.
*/
bool open(int reuseFd = -1);
/**
* @brief Closes the serial port.
*/
void close();
/**
* @brief Reads the current CTS signal state.
* @returns bool True if CTS is asserted (active), otherwise false.
*/
bool isCtsAsserted();
private:
std::string m_port;
bool m_isOpen;
bool m_ownsFd; // true if we opened the fd, false if reusing from RTS PTT
#if defined(_WIN32)
HANDLE m_fd;
#else
int m_fd;
#endif // defined(_WIN32)
/**
* @brief Sets the termios settings on the serial port.
* @returns bool True, if settings are set, otherwise false.
*/
bool setTermios();
};
#endif // __CTS_COR_CONTROLLER_H__

@ -20,6 +20,7 @@
#define __DEFINES_H__ #define __DEFINES_H__
#include "common/Defines.h" #include "common/Defines.h"
#include "common/GitHash.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Constants // Constants

File diff suppressed because it is too large Load Diff

@ -26,6 +26,7 @@
#include "common/yaml/Yaml.h" #include "common/yaml/Yaml.h"
#include "common/RingBuffer.h" #include "common/RingBuffer.h"
#include "common/Timer.h" #include "common/Timer.h"
#include "common/Clock.h"
#include "vocoder/MBEDecoder.h" #include "vocoder/MBEDecoder.h"
#include "vocoder/MBEEncoder.h" #include "vocoder/MBEEncoder.h"
#define MINIAUDIO_IMPLEMENTATION #define MINIAUDIO_IMPLEMENTATION
@ -33,6 +34,7 @@
#include "mdc/mdc_decode.h" #include "mdc/mdc_decode.h"
#include "network/PeerNetwork.h" #include "network/PeerNetwork.h"
#include "RtsPttController.h" #include "RtsPttController.h"
#include "CtsCorController.h"
#include <string> #include <string>
#include <mutex> #include <mutex>
@ -103,13 +105,11 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit
* @ingroup bridge * @ingroup bridge
*/ */
struct NetPacketRequest { struct NetPacketRequest {
uint32_t srcId; uint32_t srcId; //!< Source Address
uint32_t dstId; uint32_t dstId; //!< Destination Address
int pcmLength = 0U; //! Length of PCM data buffer int pcmLength = 0U; //!< Length of PCM data buffer
uint8_t* pcm = nullptr; //! Raw PCM buffer uint8_t* pcm = nullptr; //!< Raw PCM buffer
uint64_t pktRxTime; //! Packet receive time
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -159,10 +159,8 @@ private:
bool m_udpUseULaw; bool m_udpUseULaw;
bool m_udpRTPFrames; bool m_udpRTPFrames;
bool m_udpUsrp; bool m_udpUsrp;
uint8_t m_udpInterFrameDelay; bool m_udpFrameTiming;
uint16_t m_udpJitter; uint32_t m_udpFrameCnt;
bool m_udpSilenceDuringHang;
uint64_t m_lastUdpFrameTime;
uint8_t m_tekAlgoId; uint8_t m_tekAlgoId;
uint16_t m_tekKeyId; uint16_t m_tekKeyId;
@ -190,8 +188,6 @@ private:
float m_voxSampleLevel; float m_voxSampleLevel;
uint16_t m_dropTimeMS; uint16_t m_dropTimeMS;
Timer m_localDropTime; Timer m_localDropTime;
Timer m_udpCallClock;
Timer m_udpHangTime;
Timer m_udpDropTime; Timer m_udpDropTime;
bool m_detectAnalogMDC1200; bool m_detectAnalogMDC1200;
@ -255,6 +251,8 @@ private:
uint8_t m_detectedSampleCnt; uint8_t m_detectedSampleCnt;
bool m_dumpSampleLevel; bool m_dumpSampleLevel;
bool m_mtNoSleep;
bool m_running; bool m_running;
bool m_trace; bool m_trace;
bool m_debug; bool m_debug;
@ -264,14 +262,26 @@ private:
std::string m_rtsPttPort; std::string m_rtsPttPort;
RtsPttController* m_rtsPttController; RtsPttController* m_rtsPttController;
bool m_rtsPttActive; bool m_rtsPttActive;
// Timestamp of last audio written to output and hold-off before clearing PTT
system_clock::hrc::hrc_t m_lastAudioOut;
uint32_t m_rtsPttHoldoffMs;
// CTS COR Detection
bool m_ctsCorEnable;
std::string m_ctsCorPort;
CtsCorController* m_ctsCorController;
bool m_ctsCorActive;
bool m_ctsCorInvert; // if true, COR LOW triggers (instead of HIGH)
Timer m_ctsPadTimeout; // drives silence padding while CTS is active
uint32_t m_ctsCorHoldoffMs; // hold-off time before clearing COR after it deasserts
uint16_t m_rtpSeqNo; uint16_t m_rtpSeqNo;
uint32_t m_rtpTimestamp; uint32_t m_rtpTimestamp;
uint32_t m_usrpSeqNo; uint32_t m_usrpSeqNo;
static std::mutex m_audioMutex; static std::mutex s_audioMutex;
static std::mutex m_networkMutex; static std::mutex s_networkMutex;
#if defined(_WIN32) #if defined(_WIN32)
void* m_decoderState; void* m_decoderState;
@ -523,6 +533,11 @@ private:
* @returns bool True, if RTS PTT was initialized successfully, otherwise false. * @returns bool True, if RTS PTT was initialized successfully, otherwise false.
*/ */
bool initializeRtsPtt(); bool initializeRtsPtt();
/**
* @brief Helper to initialize CTS COR detection.
* @returns bool True, if CTS COR was initialized successfully, otherwise false.
*/
bool initializeCtsCor();
/** /**
* @brief Helper to assert RTS PTT (start transmission). * @brief Helper to assert RTS PTT (start transmission).
*/ */
@ -565,7 +580,7 @@ private:
* @param srcId * @param srcId
* @param dstId * @param dstId
*/ */
void callEndSilence(uint32_t srcId, uint32_t dstId); void padSilenceAudio(uint32_t srcId, uint32_t dstId);
/** /**
* @brief Entry point to call watchdog handler thread. * @brief Entry point to call watchdog handler thread.
@ -573,6 +588,12 @@ private:
* @returns void* (Ignore) * @returns void* (Ignore)
*/ */
static void* threadCallWatchdog(void* arg); static void* threadCallWatchdog(void* arg);
/**
* @brief Entry point to CTS COR monitor thread.
* @param arg Instance of the thread_t structure.
* @returns void* (Ignore)
*/
static void* threadCtsCorMonitor(void* arg);
}; };
#endif // __HOST_BRIDGE_H__ #endif // __HOST_BRIDGE_H__

@ -211,6 +211,17 @@ bool RtsPttController::clearPTT()
return true; return true;
} }
/* Gets the file descriptor for sharing with CTS COR controller. */
int RtsPttController::getFd() const
{
#if defined(_WIN32)
return (int)(intptr_t)m_fd;
#else
return m_fd;
#endif // defined(_WIN32)
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Private Class Members // Private Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

@ -72,6 +72,12 @@ public:
*/ */
bool clearPTT(); bool clearPTT();
/**
* @brief Gets the file descriptor for sharing with CTS COR controller.
* @returns int File descriptor, or -1 if not open.
*/
int getFd() const;
private: private:
std::string m_port; std::string m_port;
bool m_isOpen; bool m_isOpen;

@ -11,9 +11,9 @@
#include "common/dmr/data/EMB.h" #include "common/dmr/data/EMB.h"
#include "common/dmr/lc/FullLC.h" #include "common/dmr/lc/FullLC.h"
#include "common/dmr/SlotType.h" #include "common/dmr/SlotType.h"
#include "common/network/json/json.h"
#include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/DFSIDefines.h"
#include "common/p25/dfsi/LC.h" #include "common/p25/dfsi/LC.h"
#include "common/json/json.h"
#include "common/Utils.h" #include "common/Utils.h"
#include "network/PeerNetwork.h" #include "network/PeerNetwork.h"

Binary file not shown.

@ -47,12 +47,12 @@ file(GLOB common_SRC
"src/common/edac/*.cpp" "src/common/edac/*.cpp"
"src/common/lookups/*.cpp" "src/common/lookups/*.cpp"
"src/common/network/*.cpp" "src/common/network/*.cpp"
"src/common/network/rest/*.cpp"
"src/common/network/rest/http/*.cpp"
"src/common/network/sip/*.cpp" "src/common/network/sip/*.cpp"
"src/common/network/tcp/*.cpp" "src/common/network/tcp/*.cpp"
"src/common/network/udp/*.cpp" "src/common/network/udp/*.cpp"
"src/common/network/viface/*.cpp" "src/common/network/viface/*.cpp"
"src/common/restapi/*.cpp"
"src/common/restapi/http/*.cpp"
"src/common/yaml/*.cpp" "src/common/yaml/*.cpp"
"src/common/zlib/*.cpp" "src/common/zlib/*.cpp"
"src/common/zlib/*.c" "src/common/zlib/*.c"
@ -99,15 +99,17 @@ file(GLOB common_INCLUDE
"src/common/concurrent/*.h" "src/common/concurrent/*.h"
"src/common/edac/*.h" "src/common/edac/*.h"
"src/common/edac/rs/*.h" "src/common/edac/rs/*.h"
"src/common/json/*.h"
"src/common/lookups/*.h" "src/common/lookups/*.h"
"src/common/network/*.h" "src/common/network/*.h"
"src/common/network/json/*.h" "src/common/network/json/*.h"
"src/common/network/rest/*.h"
"src/common/network/rest/http/*.h"
"src/common/network/sip/*.h" "src/common/network/sip/*.h"
"src/common/network/tcp/*.h" "src/common/network/tcp/*.h"
"src/common/network/udp/*.h" "src/common/network/udp/*.h"
"src/common/network/viface/*.h" "src/common/network/viface/*.h"
"src/common/restapi/*.h"
"src/common/restapi/http/*.h"
"src/common/backtrace/*.h"
"src/common/yaml/*.h" "src/common/yaml/*.h"
"src/common/zlib/*.h" "src/common/zlib/*.h"
"src/common/*.h" "src/common/*.h"

@ -24,8 +24,21 @@
#if defined(_WIN32) #if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h> #include <Windows.h>
#include <WinSock2.h> #include <WinSock2.h>
/*
** bryanb: because we've included Windows.h in a header -- take the nuclear option
** make sure Windows.h and any of its includes *DO NOT* define min/max macros
*/
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#else #else
#include <sys/time.h> #include <sys/time.h>
#endif // defined(_WIN32) #endif // defined(_WIN32)

@ -0,0 +1,362 @@
// SPDX-License-Identifier: MIT
/*
* Digital Voice Modem - Common Library
* MIT 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 "DESCrypto.h"
#include "Log.h"
#include "Utils.h"
using namespace crypto;
#include <cassert>
#include <cstring>
#include <string>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define LB32_MASK 0x00000001
#define LB64_MASK 0x0000000000000001
#define L64_MASK 0x00000000ffffffff
#define H64_MASK 0xffffffff00000000
// Permuted Choice 1 Table [7*8]
static const char PC1_TABLE[] = {
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4
};
// Permuted Choice 2 Table [6*8]
static const char PC2_TABLE[] = {
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32
};
// Iteration Shift Array
static const char ITERATION_SHIFT[] = {
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};
// Initial Permutation Table [8*8]
static const char IP[] = {
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7
};
// Inverse Initial Permutation Table [8*8]
static const char FP[] = {
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25
};
// Expansion Table [6*8]
static const char EXPANSION[] = {
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1
};
// Post S-Box Permutation [4*8]
static const char PBOX[] = {
16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25
};
// The S-Box Tables [8*16*4]
static const char SBOX[8][64] = {
{ // S1
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 },
{ // S2
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 },
{ // S3
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 },
{ // S4
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 },
{ // S5
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 },
{ // S6
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 },
{ // S7
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 },
{ // S8
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 }
};
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the DES class. */
DES::DES() = default;
/* Encrypt input block with given key. */
uint8_t* DES::encryptBlock(const uint8_t block[], const uint8_t key[])
{
ulong64_t keyValue = toValue(key);
ulong64_t blockValue = toValue(block);
generateSubkeys(keyValue);
ulong64_t out = des(blockValue, false);
return fromValue(out);
}
/* Decrypt input block with given key. */
uint8_t* DES::decryptBlock(const uint8_t block[], const uint8_t key[])
{
ulong64_t keyValue = toValue(key);
ulong64_t blockValue = toValue(block);
generateSubkeys(keyValue);
ulong64_t out = des(blockValue, true);
return fromValue(out);
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Internal helper to convert payload bytes to a 64-bit long value. */
ulong64_t DES::toValue(const uint8_t* payload)
{
assert(payload != nullptr);
ulong64_t value = 0U;
// combine bytes into ulong64_t (8 byte) value
value = payload[0U];
value = (value << 8) + payload[1U];
value = (value << 8) + payload[2U];
value = (value << 8) + payload[3U];
value = (value << 8) + payload[4U];
value = (value << 8) + payload[5U];
value = (value << 8) + payload[6U];
value = (value << 8) + payload[7U];
return value;
}
/* Internal helper to convert a 64-bit long value to payload bytes. */
uint8_t* DES::fromValue(const ulong64_t value)
{
uint8_t* payload = new uint8_t[8U];
::memset(payload, 0x00U, 8U);
// split ulong64_t (8 byte) value into bytes
payload[0U] = (uint8_t)((value >> 56) & 0xFFU);
payload[1U] = (uint8_t)((value >> 48) & 0xFFU);
payload[2U] = (uint8_t)((value >> 40) & 0xFFU);
payload[3U] = (uint8_t)((value >> 32) & 0xFFU);
payload[4U] = (uint8_t)((value >> 24) & 0xFFU);
payload[5U] = (uint8_t)((value >> 16) & 0xFFU);
payload[6U] = (uint8_t)((value >> 8) & 0xFFU);
payload[7U] = (uint8_t)((value >> 0) & 0xFFU);
return payload;
}
/* */
void DES::generateSubkeys(uint64_t key)
{
// initial key schedule calculation
uint64_t PC1 = 0; // 56 bits
for (uint8_t i = 0; i < 56; i++) {
PC1 <<= 1;
PC1 |= (key >> (64 - PC1_TABLE[i])) & LB64_MASK;
}
// 28 bits
uint32_t C = (uint32_t)((PC1 >> 28) & 0x000000000fffffff);
uint32_t D = (uint32_t)(PC1 & 0x000000000fffffff);
// calculation of the 16 keys
for (uint8_t i = 0; i < 16; i++) {
// key schedule, shifting Ci and Di
for (uint8_t j = 0; j < ITERATION_SHIFT[i]; j++) {
C = (0x0fffffff & (C << 1)) | (0x00000001 & (C >> 27));
D = (0x0fffffff & (D << 1)) | (0x00000001 & (D >> 27));
}
uint64_t PC2 = (((uint64_t)C) << 28) | (uint64_t)D;
sub_key[i] = 0; // 48 bits (2*24)
for (uint8_t j = 0; j < 48; j++) {
sub_key[i] <<= 1;
sub_key[i] |= (PC2 >> (56 - PC2_TABLE[j])) & LB64_MASK;
}
}
}
/* */
ulong64_t DES::des(ulong64_t block, bool decrypt)
{
// applying initial permutation
block = intialPermutation(block);
// dividing T' into two 32-bit parts
uint32_t L = (uint32_t)(block >> 32) & L64_MASK;
uint32_t R = (uint32_t)(block & L64_MASK);
// 16 rounds
for (uint8_t i = 0; i < 16; i++) {
uint32_t F = decrypt ? f(R, sub_key[15 - i]) : f(R, sub_key[i]);
feistel(L, R, F);
}
// swapping the two parts
block = (((uint64_t)R) << 32) | (uint64_t)L;
// applying final permutation
return finalPermutation(block);
}
/* */
ulong64_t DES::intialPermutation(ulong64_t block)
{
// initial permutation
uint64_t result = 0;
for (uint8_t i = 0; i < 64; i++) {
result <<= 1;
result |= (block >> (64 - IP[i])) & LB64_MASK;
}
return result;
}
/* */
ulong64_t DES::finalPermutation(ulong64_t block)
{
// inverse initial permutation
uint64_t result = 0;
for (uint8_t i = 0; i < 64; i++) {
result <<= 1;
result |= (block >> (64 - FP[i])) & LB64_MASK;
}
return result;
}
/* */
void DES::feistel(uint32_t& L, uint32_t& R, uint32_t F)
{
uint32_t temp = R;
R = L ^ F;
L = temp;
}
/* */
uint32_t DES::f(uint32_t R, ulong64_t k)
{
// applying expansion permutation and returning 48-bit data
ulong64_t input = 0;
for (uint8_t i = 0; i < 48; i++) {
input <<= 1;
input |= (ulong64_t)((R >> (32 - EXPANSION[i])) & LB32_MASK);
}
// XORing expanded Ri with Ki, the round key
input = input ^ k;
// applying S-Boxes function and returning 32-bit data
uint32_t output = 0;
for (uint8_t i = 0; i < 8; i++) {
// Outer bits
char row = (char)((input & (0x0000840000000000 >> 6 * i)) >> (42 - 6 * i));
row = (row >> 4) | (row & 0x01);
// Middle 4 bits of input
char column = (char)((input & (0x0000780000000000 >> 6 * i)) >> (43 - 6 * i));
output <<= 4;
output |= (uint32_t)(SBOX[i][16 * row + column] & 0x0f);
}
// applying the round permutation
uint32_t result = 0;
for (uint8_t i = 0; i < 32; i++) {
result <<= 1;
result |= (output >> (32 - PBOX[i])) & LB32_MASK;
}
return result;
}

@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
/*
* Digital Voice Modem - Common Library
* MIT 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 DESCrypto.h
* @ingroup crypto
* @file DESCrypto.cpp
* @ingroup crypto
*/
#if !defined(__DES_CRYPTO_H__)
#define __DES_CRYPTO_H__
#include "common/Defines.h"
namespace crypto
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Data Encryption Standard Algorithm.
* @ingroup crypto
*/
class HOST_SW_API DES {
public:
/**
* @brief Initializes a new instance of the DES class.
*/
explicit DES();
/**
* @brief Encrypt input block with given key.
* @param in Input buffer with block to encrypt.
* @param key Encryption key.
* @returns uint8_t* Encrypted input buffer.
*/
uint8_t* encryptBlock(const uint8_t block[], const uint8_t key[]);
/**
* @brief Decrypt input block with given key.
* @param block Input buffer with block to encrypt.
* @param key Encryption key.
* @returns uint8_t* Encrypted input buffer.
*/
uint8_t* decryptBlock(const uint8_t block[], const uint8_t key[]);
private:
uint64_t sub_key[16]; // 48 bits each
static ulong64_t toValue(const uint8_t* payload);
static uint8_t* fromValue(const ulong64_t value);
void generateSubkeys(uint64_t key);
ulong64_t des(ulong64_t block, bool decrypt);
ulong64_t intialPermutation(ulong64_t block);
ulong64_t finalPermutation(ulong64_t block);
void feistel(uint32_t& L, uint32_t& R, uint32_t F);
uint32_t f(uint32_t R, ulong64_t k);
};
} // namespace crypto
#endif // __DES_CRYPTO_H__

@ -106,22 +106,15 @@ typedef unsigned long long ulong64_t;
// Constants // Constants
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#ifndef __GIT_VER__
#define __GIT_VER__ "00000000"
#endif
#ifndef __GIT_VER_HASH__
#define __GIT_VER_HASH__ "00000000"
#endif
#define __PROG_NAME__ "" #define __PROG_NAME__ ""
#define __EXE_NAME__ "" #define __EXE_NAME__ ""
#define VERSION_MAJOR "04" #define VERSION_MAJOR "05"
#define VERSION_MINOR "32" #define VERSION_MINOR "02"
#define VERSION_REV "J" #define VERSION_REV "A"
#define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR
#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" #define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR ")"
#define __BUILD__ __DATE__ " " __TIME__ #define __BUILD__ __DATE__ " " __TIME__
#if !defined(NDEBUG) #if !defined(NDEBUG)
@ -168,32 +161,32 @@ const uint32_t RPC_DEFAULT_PORT = 9890;
* @brief Operational Host States * @brief Operational Host States
*/ */
enum HOST_STATE { enum HOST_STATE {
FNE_STATE = 240U, //! FNE (only used by dvmfne) FNE_STATE = 240U, //!< FNE (only used by dvmfne)
HOST_STATE_LOCKOUT = 250U, //! Lockout (dvmhost traffic lockout state) HOST_STATE_LOCKOUT = 250U, //!< Lockout (dvmhost traffic lockout state)
HOST_STATE_ERROR = 254U, //! Error (dvmhost error state) HOST_STATE_ERROR = 254U, //!< Error (dvmhost error state)
HOST_STATE_QUIT = 255U, //! Quit (dvmhost quit state) HOST_STATE_QUIT = 255U, //!< Quit (dvmhost quit state)
}; };
/** /**
* @brief Operational RF States * @brief Operational RF States
*/ */
enum RPT_RF_STATE { enum RPT_RF_STATE {
RS_RF_LISTENING, //! Modem Listening RS_RF_LISTENING, //!< Modem Listening
RS_RF_LATE_ENTRY, //! Traffic Late Entry RS_RF_LATE_ENTRY, //!< Traffic Late Entry
RS_RF_AUDIO, //! Audio RS_RF_AUDIO, //!< Audio
RS_RF_DATA, //! Data RS_RF_DATA, //!< Data
RS_RF_REJECTED, //! Traffic Rejected RS_RF_REJECTED, //!< Traffic Rejected
RS_RF_INVALID //! Traffic Invalid RS_RF_INVALID //!< Traffic Invalid
}; };
/** /**
* @brief Operational Network States * @brief Operational Network States
*/ */
enum RPT_NET_STATE { enum RPT_NET_STATE {
RS_NET_IDLE, //! Idle RS_NET_IDLE, //!< Idle
RS_NET_AUDIO, //! Audio RS_NET_AUDIO, //!< Audio
RS_NET_DATA //! Data RS_NET_DATA //!< Data
}; };
const uint8_t UDP_COMPRESS_NONE = 0x00U; const uint8_t UDP_COMPRESS_NONE = 0x00U;

@ -0,0 +1,36 @@
// 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 GitHash.h
* @ingroup common
*/
#pragma once
#if !defined(__GIT_HASH_H__)
#define __GIT_HASH_H__
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#ifndef __GIT_VER__
#define __GIT_VER__ "00000000"
#endif
#ifndef __GIT_VER_HASH__
#define __GIT_VER_HASH__ "00000000"
#endif
#ifdef __VER__
#undef __VER__
#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")"
#endif
/** @} */
#endif // __GIT_HASH_H__

@ -11,21 +11,17 @@
#include "Log.h" #include "Log.h"
#include "network/BaseNetwork.h" #include "network/BaseNetwork.h"
#if defined(_WIN32)
#include "Clock.h"
#else
#include <sys/time.h>
#include <syslog.h>
#endif // defined(_WIN32)
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#endif #endif
#if !defined(_WIN32)
#include <syslog.h>
#endif // defined(_WIN32)
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstdarg> #include <cstdarg>
#include <ctime>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
@ -42,13 +38,13 @@ const uint32_t LOG_BUFFER_LEN = 4096U;
// Global Variables // Global Variables
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static uint32_t m_fileLevel = 0U; static uint32_t g_fileLevel = 0U;
static std::string m_filePath; static std::string g_filePath;
static std::string m_fileRoot; static std::string g_fileRoot;
static network::BaseNetwork* m_network; static network::BaseNetwork* g_network;
static FILE* m_fpLog = nullptr; static FILE* g_fpLog = nullptr;
uint32_t g_logDisplayLevel = 2U; uint32_t g_logDisplayLevel = 2U;
bool g_disableTimeDisplay = false; bool g_disableTimeDisplay = false;
@ -56,11 +52,11 @@ bool g_disableTimeDisplay = false;
bool g_useSyslog = false; bool g_useSyslog = false;
bool g_disableNetworkLog = false; bool g_disableNetworkLog = false;
static struct tm m_tm; static struct tm g_tm;
static std::ostream m_outStream { std::cerr.rdbuf() }; static std::ostream g_outStream { std::cerr.rdbuf() };
static char LEVELS[] = " DMIWEF"; bool log_stacktrace::SignalHandling::s_foreground = false;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Global Functions // Global Functions
@ -68,15 +64,15 @@ static char LEVELS[] = " DMIWEF";
/* Helper to get the current log file level. */ /* Helper to get the current log file level. */
uint32_t CurrentLogFileLevel() { return m_fileLevel; } uint32_t CurrentLogFileLevel() { return g_fileLevel; }
/* Helper to get the current log file path. */ /* Helper to get the current log file path. */
std::string LogGetFilePath() { return m_filePath; } std::string LogGetFilePath() { return g_filePath; }
/* Helper to get the current log file root. */ /* Helper to get the current log file root. */
std::string LogGetFileRoot() { return m_fileRoot; } std::string LogGetFileRoot() { return g_fileRoot; }
/* Helper to open the detailed log file, file handle. */ /* Helper to open the detailed log file, file handle. */
@ -85,7 +81,7 @@ static bool LogOpen()
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
return true; return true;
#endif #endif
if (m_fileLevel == 0U) if (g_fileLevel == 0U)
return true; return true;
if (!g_useSyslog) { if (!g_useSyslog) {
@ -94,26 +90,26 @@ static bool LogOpen()
struct tm* tm = ::localtime(&now); struct tm* tm = ::localtime(&now);
if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) { if (tm->tm_mday == g_tm.tm_mday && tm->tm_mon == g_tm.tm_mon && tm->tm_year == g_tm.tm_year) {
if (m_fpLog != nullptr) if (g_fpLog != nullptr)
return true; return true;
} }
else { else {
if (m_fpLog != nullptr) if (g_fpLog != nullptr)
::fclose(m_fpLog); ::fclose(g_fpLog);
} }
char filename[200U]; char filename[200U];
::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); ::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", g_filePath.c_str(), g_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
m_fpLog = ::fopen(filename, "a+t"); g_fpLog = ::fopen(filename, "a+t");
m_tm = *tm; g_tm = *tm;
return m_fpLog != nullptr; return g_fpLog != nullptr;
} }
else { else {
#if !defined(_WIN32) #if !defined(_WIN32)
switch (m_fileLevel) { switch (g_fileLevel) {
case 1U: case 1U:
setlogmask(LOG_UPTO(LOG_DEBUG)); setlogmask(LOG_UPTO(LOG_DEBUG));
break; break;
@ -132,7 +128,7 @@ static bool LogOpen()
break; break;
} }
openlog(m_fileRoot.c_str(), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON); openlog(g_fileRoot.c_str(), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON);
return true; return true;
#else #else
return false; return false;
@ -140,19 +136,12 @@ static bool LogOpen()
} }
} }
/* Internal helper to set an output stream to direct logging to. */
void __InternalOutputStream(std::ostream& stream)
{
m_outStream.rdbuf(stream.rdbuf());
}
/* Gets the instance of the Network class to transfer the activity log with. */ /* Gets the instance of the Network class to transfer the activity log with. */
void* LogGetNetwork() void* LogGetNetwork()
{ {
// NO GOOD, VERY BAD, TERRIBLE HACK // NO GOOD, VERY BAD, TERRIBLE HACK
return (void*)m_network; return (void*)g_network;
} }
/* Sets the instance of the Network class to transfer the activity log with. */ /* Sets the instance of the Network class to transfer the activity log with. */
@ -164,16 +153,16 @@ void LogSetNetwork(void* network)
#endif #endif
// note: The Network class is passed here as a void so we can avoid including the Network.h // note: The Network class is passed here as a void so we can avoid including the Network.h
// header in Log.h. This is dirty and probably terrible... // header in Log.h. This is dirty and probably terrible...
m_network = (network::BaseNetwork*)network; g_network = (network::BaseNetwork*)network;
} }
/* Initializes the diagnostics log. */ /* Initializes the diagnostics log. */
bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay, bool useSyslog) bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay, bool useSyslog)
{ {
m_filePath = filePath; g_filePath = filePath;
m_fileRoot = fileRoot; g_fileRoot = fileRoot;
m_fileLevel = fileLevel; g_fileLevel = fileLevel;
g_logDisplayLevel = displayLevel; g_logDisplayLevel = displayLevel;
g_disableTimeDisplay = disableTimeDisplay; g_disableTimeDisplay = disableTimeDisplay;
#if defined(_WIN32) #if defined(_WIN32)
@ -192,9 +181,9 @@ void LogFinalise()
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
return; return;
#endif #endif
if (m_fpLog != nullptr) { if (g_fpLog != nullptr) {
::fclose(m_fpLog); ::fclose(g_fpLog);
m_fpLog = nullptr; g_fpLog = nullptr;
} }
#if !defined(_WIN32) #if !defined(_WIN32)
if (g_useSyslog) if (g_useSyslog)
@ -202,140 +191,42 @@ void LogFinalise()
#endif // !defined(_WIN32) #endif // !defined(_WIN32)
} }
/* Writes a new entry to the diagnostics log. */ /* Internal helper to set an output stream to direct logging to. */
void Log(uint32_t level, const char *module, const char* file, const int lineNo, const char* func, const char* fmt, ...) void log_internal::SetInternalOutputStream(std::ostream& stream)
{ {
assert(fmt != nullptr); g_outStream.rdbuf(stream.rdbuf());
#if defined(CATCH2_TEST_COMPILATION) }
g_disableTimeDisplay = true;
#endif
char buffer[LOG_BUFFER_LEN];
if (!g_disableTimeDisplay && !g_useSyslog) {
time_t now;
::time(&now);
struct tm* tm = ::localtime(&now);
struct timeval nowMillis;
::gettimeofday(&nowMillis, NULL);
if (module != nullptr) {
// level 1 is DEBUG
if (level == 1U) {
// if we have a file and line number -- add that to the log entry
if (file != nullptr && lineNo > 0) {
// if we have a function name add that to the log entry
if (func != nullptr) {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u][%s] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module, file, lineNo, func);
}
else {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module, file, lineNo);
}
} else {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module);
}
} else {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module);
}
}
else {
// level 1 is DEBUG
if (level == 1U) {
// if we have a file and line number -- add that to the log entry
if (file != nullptr && lineNo > 0) {
// if we have a function name add that to the log entry
if (func != nullptr) {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u][%s] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, file, lineNo, func);
}
else {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, file, lineNo);
}
} else {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U);
}
} else {
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U);
}
}
}
else {
if (module != nullptr) {
// level 1 is DEBUG
if (level == 1U) {
// if we have a file and line number -- add that to the log entry
if (file != nullptr && lineNo > 0) {
// if we have a function name add that to the log entry
if (func != nullptr) {
::sprintf(buffer, "%c: (%s)[%s:%u][%s] ", LEVELS[level], module, file, lineNo, func);
}
else {
::sprintf(buffer, "%c: (%s)[%s:%u] ", LEVELS[level], module, file, lineNo);
}
}
else {
::sprintf(buffer, "%c: (%s) ", LEVELS[level], module);
}
} else {
::sprintf(buffer, "%c: (%s) ", LEVELS[level], module);
}
}
else {
if (level >= 9999U) {
::sprintf(buffer, "U: ");
}
else {
// if we have a file and line number -- add that to the log entry
if (file != nullptr && lineNo > 0) {
// if we have a function name add that to the log entry
if (func != nullptr) {
::sprintf(buffer, "%c: [%s:%u][%s] ", LEVELS[level], file, lineNo, func);
}
else {
::sprintf(buffer, "%c: [%s:%u] ", LEVELS[level], file, lineNo);
}
}
else {
::sprintf(buffer, "%c: ", LEVELS[level]);
}
}
}
}
va_list vl, vl_len;
va_start(vl, fmt);
va_copy(vl_len, vl);
size_t len = ::vsnprintf(nullptr, 0U, fmt, vl_len);
::vsnprintf(buffer + ::strlen(buffer), len + 1U, fmt, vl);
va_end(vl_len); /* Writes a new entry to the diagnostics log. */
va_end(vl);
if (m_outStream && g_logDisplayLevel == 0U) { void log_internal::LogInternal(uint32_t level, const std::string& log)
m_outStream << buffer << std::endl; {
if (g_outStream && g_logDisplayLevel == 0U) {
g_outStream << log << std::endl;
} }
if (m_network != nullptr && !g_disableNetworkLog) { if (g_network != nullptr && !g_disableNetworkLog) {
// don't transfer debug data... // don't transfer debug data...
if (level > 1U) { if (level > 1U) {
m_network->writeDiagLog(buffer); g_network->writeDiagLog(log.c_str());
} }
} }
#if defined(CATCH2_TEST_COMPILATION) #if defined(CATCH2_TEST_COMPILATION)
UNSCOPED_INFO(buffer); UNSCOPED_INFO(log.c_str());
return; return;
#endif #endif
if (level >= m_fileLevel && m_fileLevel != 0U) { if (level >= g_fileLevel && g_fileLevel != 0U) {
if (!g_useSyslog) { if (!g_useSyslog) {
bool ret = ::LogOpen(); bool ret = ::LogOpen();
if (!ret) if (!ret)
return; return;
if (m_fpLog != nullptr) { if (g_fpLog != nullptr) {
::fprintf(m_fpLog, "%s\n", buffer); ::fprintf(g_fpLog, "%s\n", log.c_str());
::fflush(m_fpLog); ::fflush(g_fpLog);
} }
} else { } else {
#if !defined(_WIN32) #if !defined(_WIN32)
@ -346,16 +237,13 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo,
syslogLevel = LOG_DEBUG; syslogLevel = LOG_DEBUG;
break; break;
case 2U: case 2U:
syslogLevel = LOG_NOTICE;
break;
case 3U:
case 9999U: // in-band U: messages should also be info level case 9999U: // in-band U: messages should also be info level
syslogLevel = LOG_INFO; syslogLevel = LOG_INFO;
break; break;
case 4U: case 3U:
syslogLevel = LOG_WARNING; syslogLevel = LOG_WARNING;
break; break;
case 5U: case 4U:
syslogLevel = LOG_ERR; syslogLevel = LOG_ERR;
break; break;
default: default:
@ -363,20 +251,20 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo,
break; break;
} }
syslog(syslogLevel, "%s", buffer); syslog(syslogLevel, "%s", log.c_str());
#endif // !defined(_WIN32) #endif // !defined(_WIN32)
} }
} }
if (!g_useSyslog && level >= g_logDisplayLevel && g_logDisplayLevel != 0U) { if (!g_useSyslog && level >= g_logDisplayLevel && g_logDisplayLevel != 0U) {
::fprintf(stdout, "%s" EOL, buffer); ::fprintf(stdout, "%s" EOL, log.c_str());
::fflush(stdout); ::fflush(stdout);
} }
// fatal error (specially allow any log levels above 9999) // fatal error (specially allow any log levels above 9999)
if (level >= 6U && level < 9999U) { if (level >= 5U && level < 9999U) {
if (m_fpLog != nullptr) if (g_fpLog != nullptr)
::fclose(m_fpLog); ::fclose(g_fpLog);
#if !defined(_WIN32) #if !defined(_WIN32)
if (g_useSyslog) if (g_useSyslog)
::closelog(); ::closelog();
@ -384,3 +272,24 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo,
exit(1); exit(1);
} }
} }
/* Internal helper to get the log file path. */
std::string log_internal::GetLogFilePath()
{
return g_filePath;
}
/* Internal helper to get the log file root name. */
std::string log_internal::GetLogFileRoot()
{
return g_fileRoot;
}
/* Internal helper to get the log file handle pointer. */
FILE* log_internal::GetLogFile()
{
return g_fpLog;
}

@ -22,7 +22,17 @@
#define __LOG_H__ #define __LOG_H__
#include "common/Defines.h" #include "common/Defines.h"
#if defined(_WIN32)
#include "common/Clock.h"
#else
#include <sys/time.h>
#endif // defined(_WIN32)
#if !defined(CATCH2_TEST_COMPILATION)
#include "common/backtrace/backward.h"
#endif
#include <ctime>
#include <string> #include <string>
/** /**
@ -38,13 +48,13 @@
#define LOG_HOST "HOST" #define LOG_HOST "HOST"
#define LOG_REST "RESTAPI" #define LOG_REST "RESTAPI"
#define LOG_SIP "SIP"
#define LOG_MODEM "MODEM" #define LOG_MODEM "MODEM"
#define LOG_RF "RF" #define LOG_RF "RF"
#define LOG_NET "NET" #define LOG_NET "NET"
#define LOG_P25 "P25" #define LOG_P25 "P25"
#define LOG_NXDN "NXDN" #define LOG_NXDN "NXDN"
#define LOG_DMR "DMR" #define LOG_DMR "DMR"
#define LOG_ANALOG "ANALOG"
#define LOG_CAL "CAL" #define LOG_CAL "CAL"
#define LOG_SETUP "SETUP" #define LOG_SETUP "SETUP"
#define LOG_SERIAL "SERIAL" #define LOG_SERIAL "SERIAL"
@ -63,7 +73,7 @@
* *
* This is a variable argument function. * This is a variable argument function.
*/ */
#define LogDebug(_module, fmt, ...) Log(1U, _module, __FILE__, __LINE__, nullptr, fmt, ##__VA_ARGS__) #define LogDebug(_module, fmt, ...) Log(1U, {_module, __FILE__, __LINE__, nullptr}, fmt, ##__VA_ARGS__)
/** /**
* @brief Macro helper to create a debug log entry. * @brief Macro helper to create a debug log entry.
* @param _module Name of module generating log entry. * @param _module Name of module generating log entry.
@ -72,15 +82,7 @@
* *
* This is a variable argument function. * This is a variable argument function.
*/ */
#define LogDebugEx(_module, _func, fmt, ...) Log(1U, _module, __FILE__, __LINE__, _func, fmt, ##__VA_ARGS__) #define LogDebugEx(_module, _func, fmt, ...) Log(1U, {_module, __FILE__, __LINE__, _func}, fmt, ##__VA_ARGS__)
/**
* @brief Macro helper to create a message log entry.
* @param _module Name of module generating log entry.
* @param fmt String format.
*
* This is a variable argument function.
*/
#define LogMessage(_module, fmt, ...) Log(2U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__)
/** /**
* @brief Macro helper to create a informational log entry. * @brief Macro helper to create a informational log entry.
* @param _module Name of module generating log entry. * @param _module Name of module generating log entry.
@ -89,7 +91,7 @@
* This is a variable argument function. LogInfo() does not use a module * This is a variable argument function. LogInfo() does not use a module
* name when creating a log entry. * name when creating a log entry.
*/ */
#define LogInfo(fmt, ...) Log(3U, nullptr, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) #define LogInfo(fmt, ...) Log(2U, {nullptr, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__)
/** /**
* @brief Macro helper to create a informational log entry with module name. * @brief Macro helper to create a informational log entry with module name.
* @param _module Name of module generating log entry. * @param _module Name of module generating log entry.
@ -97,7 +99,7 @@
* *
* This is a variable argument function. * This is a variable argument function.
*/ */
#define LogInfoEx(_module, fmt, ...) Log(3U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) #define LogInfoEx(_module, fmt, ...) Log(2U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__)
/** /**
* @brief Macro helper to create a warning log entry. * @brief Macro helper to create a warning log entry.
* @param _module Name of module generating log entry. * @param _module Name of module generating log entry.
@ -105,7 +107,7 @@
* *
* This is a variable argument function. * This is a variable argument function.
*/ */
#define LogWarning(_module, fmt, ...) Log(4U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) #define LogWarning(_module, fmt, ...) Log(3U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__)
/** /**
* @brief Macro helper to create a error log entry. * @brief Macro helper to create a error log entry.
* @param _module Name of module generating log entry. * @param _module Name of module generating log entry.
@ -113,7 +115,7 @@
* *
* This is a variable argument function. * This is a variable argument function.
*/ */
#define LogError(_module, fmt, ...) Log(5U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) #define LogError(_module, fmt, ...) Log(4U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__)
/** /**
* @brief Macro helper to create a fatal log entry. * @brief Macro helper to create a fatal log entry.
* @param _module Name of module generating log entry. * @param _module Name of module generating log entry.
@ -121,7 +123,7 @@
* *
* This is a variable argument function. * This is a variable argument function.
*/ */
#define LogFatal(_module, fmt, ...) Log(6U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) #define LogFatal(_module, fmt, ...) Log(5U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Externs // Externs
@ -144,14 +146,611 @@ extern bool g_useSyslog;
*/ */
extern bool g_disableNetworkLog; extern bool g_disableNetworkLog;
// --------------------------------------------------------------------------- namespace log_internal
// Global Functions {
// --------------------------------------------------------------------------- constexpr static char LOG_LEVELS[] = " DIWEF";
/**
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents a source code location.
* @ingroup logger
*/
struct SourceLocation {
public:
/**
* @brief Initializes a new instance of the SourceLocation class.
*/
constexpr SourceLocation() = default;
/**
* @brief Initializes a new instance of the SourceLocation class.
* @param module Application module.
* @param filename Source code filename.
* @param line Line number in source code file.
* @param func Function name within source code.
*/
constexpr SourceLocation(const char* module, const char* filename, int line, const char* func) :
module(module),
filename(filename),
line(line),
funcname(func)
{
/* stub */
}
public:
const char* module = nullptr;
const char* filename = nullptr;
int line = 0;
const char* funcname = nullptr;
};
/**
* @brief Internal helper to set an output stream to direct logging to. * @brief Internal helper to set an output stream to direct logging to.
* @param stream * @param stream
*/ */
extern HOST_SW_API void __InternalOutputStream(std::ostream& stream); extern HOST_SW_API void SetInternalOutputStream(std::ostream& stream);
/**
* @brief Writes a new entry to the diagnostics log.
* @param level Log level for entry.
* @param log Fully formatted log message.
*/
extern HOST_SW_API void LogInternal(uint32_t level, const std::string& log);
/**
* @brief Internal helper to get the log file path.
* @returns std::string Configured log file path.
*/
extern HOST_SW_API std::string GetLogFilePath();
/**
* @brief Internal helper to get the log file root name.
* @returns std::string Configured log file root name.
*/
extern HOST_SW_API std::string GetLogFileRoot();
/**
* @brief Internal helper to get the log file handle pointer.
* @returns FILE* Pointer to the open log file.
*/
extern HOST_SW_API FILE* GetLogFile();
} // namespace log_internal
namespace log_stacktrace
{
#if !defined(CATCH2_TEST_COMPILATION)
#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN)
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Backward backtrace signal handling class.
* @ingroup logger
*/
class HOST_SW_API SignalHandling {
public:
/**
* @brief Helper to generate a default list of POSIX signals to handle.
* @return std::vector<int> List of POSIX signals.
*/
static std::vector<int> makeDefaultSignals() {
const int posixSignals[] = {
// Signals for which the default action is "Core".
SIGABRT, // Abort signal from abort(3)
SIGBUS, // Bus error (bad memory access)
SIGFPE, // Floating point exception
SIGILL, // Illegal Instruction
SIGIOT, // IOT trap. A synonym for SIGABRT
SIGQUIT, // Quit from keyboard
SIGSEGV, // Invalid memory reference
SIGSYS, // Bad argument to routine (SVr4)
SIGTRAP, // Trace/breakpoint trap
SIGXCPU, // CPU time limit exceeded (4.2BSD)
SIGXFSZ, // File size limit exceeded (4.2BSD)
#if defined(BACKWARD_SYSTEM_DARWIN)
SIGEMT, // emulation instruction executed
#endif
};
return std::vector<int>(posixSignals, posixSignals + sizeof posixSignals / sizeof posixSignals[0]);
}
/**
* @brief Initializes a new instance of the SignalHandling class
* @param foreground Log stacktrace to stderr.
* @param posixSignals List of signals to handle.
*/
SignalHandling(bool foreground, const std::vector<int>& posixSignals = makeDefaultSignals()) :
m_loaded(false)
{
bool success = true;
s_foreground = foreground;
const size_t stackSize = 1024 * 1024 * 8;
m_stackContent.reset(static_cast<char *>(malloc(stackSize)));
if (m_stackContent) {
stack_t ss;
ss.ss_sp = m_stackContent.get();
ss.ss_size = stackSize;
ss.ss_flags = 0;
if (sigaltstack(&ss, nullptr) < 0) {
success = false;
}
} else {
success = false;
}
for (size_t i = 0; i < posixSignals.size(); ++i) {
struct sigaction action;
memset(&action, 0, sizeof action);
action.sa_flags = static_cast<int>(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND);
sigfillset(&action.sa_mask);
sigdelset(&action.sa_mask, posixSignals[i]);
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
#endif
action.sa_sigaction = &sig_handler;
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
int r = sigaction(posixSignals[i], &action, nullptr);
if (r < 0)
success = false;
}
m_loaded = success;
}
/**
* @brief Helper to return whether or not the SignalHandling class is loaded.
* @return bool True if signal handler is loaded, otherwise false.
*/
bool loaded() const { return m_loaded; }
/**
* @brief Helper to handle a signal.
* @param signo Signal number.
* @param info Signal informational structure.
* @param _ctx Signal user context data.
*/
static void handleSignal(int signo, siginfo_t* info, void* _ctx)
{
ucontext_t *uctx = static_cast<ucontext_t *>(_ctx);
backward::StackTrace st;
void* errorAddr = nullptr;
#ifdef REG_RIP // x86_64
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.gregs[REG_RIP]);
#elif defined(REG_EIP) // x86_32
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.gregs[REG_EIP]);
#elif defined(__arm__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.arm_pc);
#elif defined(__aarch64__)
#if defined(__APPLE__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__pc);
#else
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.pc);
#endif
#elif defined(__mips__)
errorAddr = reinterpret_cast<void *>(reinterpret_cast<struct sigcontext *>(&uctx->uc_mcontext)->sc_pc);
#elif defined(__APPLE__) && defined(__POWERPC__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__srr0);
#elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \
defined(__POWERPC__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.regs->nip);
#elif defined(__riscv)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.__gregs[REG_PC]);
#elif defined(__s390x__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.psw.addr);
#elif defined(__APPLE__) && defined(__x86_64__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__rip);
#elif defined(__APPLE__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__eip);
#elif defined(__loongarch__)
errorAddr = reinterpret_cast<void *>(uctx->uc_mcontext.__pc);
#else
#warning ":/ sorry, ain't know no nothing none not of your architecture!"
#endif
if (errorAddr) {
st.load_from(errorAddr, 32, reinterpret_cast<void *>(uctx), info->si_addr);
} else {
st.load_here(32, reinterpret_cast<void *>(uctx), info->si_addr);
}
backward::Printer p;
p.address = true;
p.snippet = false;
p.color_mode = backward::ColorMode::never;
log_internal::LogInternal(2U, "UNRECOVERABLE FATAL ERROR!");
if (s_foreground > 0) {
p.print(st, stderr);
}
std::string filePath = log_internal::GetLogFilePath();
std::string fileRoot = log_internal::GetLogFileRoot();
time_t now;
::time(&now);
struct tm* tm = ::localtime(&now);
char filename[200U];
::sprintf(filename, "%s/%s-%04d-%02d-%02d.stacktrace.log", filePath.c_str(), fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
// log stack trace to a file if we're using syslog
if (g_useSyslog) {
FILE* stacktraceFp = ::fopen(filename, "a+t");
::fprintf(stacktraceFp, "UNRECOVERABLE FATAL ERROR!\r\n");
p.print(st, stacktraceFp);
::fflush(stacktraceFp);
::fclose(stacktraceFp);
} else {
FILE *stacktraceFp = log_internal::GetLogFile();
if (stacktraceFp == nullptr) {
stacktraceFp = ::fopen(filename, "a+t");
::fprintf(stacktraceFp, "UNRECOVERABLE FATAL ERROR!\r\n");
}
p.print(st, stacktraceFp);
::fflush(stacktraceFp);
::fclose(stacktraceFp);
}
(void)info;
}
private:
backward::details::handle<char*> m_stackContent;
bool m_loaded;
static bool s_foreground;
/**
* @brief Internal helper to handle a signal.
* @param signo Signal number.
* @param info Signal informational structure.
* @param _ctx Signal user context data.
*/
#ifdef __GNUC__
__attribute__((noreturn))
#endif
static void sig_handler(int signo, siginfo_t* info, void* _ctx)
{
handleSignal(signo, info, _ctx);
// try to forward the signal.
raise(info->si_signo);
// terminate the process immediately.
puts("Abnormal termination.");
_exit(EXIT_FAILURE);
}
};
#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN
#ifdef BACKWARD_SYSTEM_WINDOWS
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Backward backtrace signal handling class.
* @ingroup logger
*/
class HOST_SW_API SignalHandling {
public:
/**
* @brief Initializes a new instance of the SignalHandling class
* @param foreground Log stacktrace to stderr. (Windows always logs to the foreground, this is ignored.)
* @param posixSignals List of signals to handle.
*/
SignalHandling(bool foreground, const std::vector<int>& = std::vector<int>()) :
m_reporterThread([]() {
/* We handle crashes in a utility thread:
** backward structures and some Windows functions called here
** need stack space, which we do not have when we encounter a
** stack overflow.
** To support reporting stack traces during a stack overflow,
** we create a utility thread at startup, which waits until a
** crash happens or the program exits normally.
*/
{
std::unique_lock<std::mutex> lk(mtx());
cv().wait(lk, [] { return crashed() != CRASH_STATUS::RUNNING; });
}
if (crashed() == CRASH_STATUS::CRASHED) {
handleStackTrace(skipRecs());
}
{
std::unique_lock<std::mutex> lk(mtx());
crashed() = CRASH_STATUS::ENDING;
}
cv().notify_one();
})
{
SetUnhandledExceptionFilter(crashHandler);
signal(SIGABRT, signalHandler);
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
std::set_terminate(&terminator);
#ifndef BACKWARD_ATLEAST_CXX17
std::set_unexpected(&terminator);
#endif
_set_purecall_handler(&terminator);
_set_invalid_parameter_handler(&invalidParameterHandler);
}
/**
* @brief Finalizes a instance of the SignalHandling class
*/
~SignalHandling()
{
{
std::unique_lock<std::mutex> lk(mtx());
crashed() = CRASH_STATUS::NORMAL_EXIT;
}
cv().notify_one();
m_reporterThread.join();
}
/**
* @brief Helper to return whether or not the SignalHandling class is loaded.
* @return bool True if signal handler is loaded, otherwise false.
*/
bool loaded() const { return true; }
private:
enum class CRASH_STATUS {
RUNNING,
CRASHED,
NORMAL_EXIT,
ENDING
};
/**
* @brief
* @return CONTEXT*
*/
static CONTEXT* ctx()
{
static CONTEXT data;
return &data;
}
/**
* @brief
* @return crash_status&
*/
static CRASH_STATUS& crashed()
{
static CRASH_STATUS data;
return data;
}
/**
* @brief
* @return std::mutex&
*/
static std::mutex& mtx()
{
static std::mutex data;
return data;
}
/**
* @brief
* @return std::condition_variable&
*/
static std::condition_variable& cv()
{
static std::condition_variable data;
return data;
}
/**
* @brief
* @return HANDLE&
*/
static HANDLE& threadHandle()
{
static HANDLE handle;
return handle;
}
std::thread m_reporterThread;
static bool s_foreground;
// TODO: how not to hardcode these?
static const constexpr int signalSkipRecs =
#ifdef __clang__
// With clang, RtlCaptureContext also captures the stack frame of the
// current function Below that, there are 3 internal Windows functions
4
#else
// With MSVC cl, RtlCaptureContext misses the stack frame of the current
// function The first entries during StackWalk are the 3 internal Windows
// functions
3
#endif
;
/**
* @brief
* @return int&
*/
static int& skipRecs()
{
static int data;
return data;
}
/**
* @brief
*/
static inline void terminator()
{
crashHandler(signalSkipRecs);
abort();
}
/**
* @brief
*/
static inline void signalHandler(int)
{
crashHandler(signalSkipRecs);
abort();
}
/**
* @brief
* @param int
*/
static inline void __cdecl invalidParameterHandler(const wchar_t *, const wchar_t *, const wchar_t *,
unsigned int, uintptr_t)
{
crashHandler(signalSkipRecs);
abort();
}
/**
* @brief
* @param info
* @return NOINLINE
*/
NOINLINE static LONG WINAPI crashHandler(EXCEPTION_POINTERS* info)
{
// the exception info supplies a trace from exactly where the issue was,
// no need to skip records
crashHandler(0, info->ContextRecord);
return EXCEPTION_CONTINUE_SEARCH;
}
/**
* @brief
* @param skip
* @param ct
* @return NOINLINE
*/
NOINLINE static void crashHandler(int skip, CONTEXT* ct = nullptr)
{
if (ct == nullptr) {
RtlCaptureContext(ctx());
} else {
memcpy(ctx(), ct, sizeof(CONTEXT));
}
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &threadHandle(), 0, FALSE, DUPLICATE_SAME_ACCESS);
skipRecs() = skip;
{
std::unique_lock<std::mutex> lk(mtx());
crashed() = CRASH_STATUS::CRASHED;
}
cv().notify_one();
{
std::unique_lock<std::mutex> lk(mtx());
cv().wait(lk, [] { return crashed() != CRASH_STATUS::CRASHED; });
}
}
/**
* @brief
* @param skipFrames
*/
static void handleStackTrace(int skipFrames = 0)
{
// printer creates the TraceResolver, which can supply us a machine type
// for stack walking. Without this, StackTrace can only guess using some
// macros.
// StackTrace also requires that the PDBs are already loaded, which is done
// in the constructor of TraceResolver
backward::Printer p;
backward::StackTrace st;
st.set_machine_type(p.resolver().machine_type());
st.set_thread_handle(threadHandle());
st.load_here(32 + skipFrames, ctx());
st.skip_n_firsts(skipFrames);
p.address = true;
p.snippet = false;
p.color_mode = backward::ColorMode::never;
log_internal::LogInternal(2U, "UNRECOVERABLE FATAL ERROR!");
p.print(st, std::cerr);
std::string filePath = log_internal::GetLogFilePath();
std::string fileRoot = log_internal::GetLogFileRoot();
time_t now;
::time(&now);
struct tm* tm = ::localtime(&now);
char filename[200U];
::sprintf(filename, "%s/%s-%04d-%02d-%02d.stacktrace.log", filePath.c_str(), fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
// log stack trace to a file if we're using syslog
FILE *stacktraceFp = log_internal::GetLogFile();
if (stacktraceFp == nullptr) {
stacktraceFp = ::fopen(filename, "a+t");
::fprintf(stacktraceFp, "UNRECOVERABLE FATAL ERROR!\r\n");
}
p.print(st, stacktraceFp);
::fflush(stacktraceFp);
::fclose(stacktraceFp);
}
};
#endif // BACKWARD_SYSTEM_WINDOWS
#ifdef BACKWARD_SYSTEM_UNKNOWN
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Backward backtrace signal handling class.
* @ingroup logger
*/
class HOST_SW_API SignalHandling {
public:
/**
* @brief Initializes a new instance of the SignalHandling class
* @param foreground Log stacktrace to stderr.
*/
SignalHandling(bool forground, const std::vector<int>& = std::vector<int>())
{
/* stub */
}
/**
* @brief Helper to return whether or not the SignalHandling class is loaded.
* @return bool True if signal handler is loaded, otherwise false.
*/
bool loaded() { return false; }
private:
static bool s_foreground;
};
#endif // BACKWARD_SYSTEM_UNKNOWN
#endif // !defined(CATCH2_TEST_COMPILATION)
}
// ---------------------------------------------------------------------------
// Global Function Externs
// ---------------------------------------------------------------------------
/** /**
* @brief Helper to get the current log file level. * @brief Helper to get the current log file level.
@ -191,23 +790,166 @@ extern HOST_SW_API void LogSetNetwork(void* network);
* @param syslog Flag indicating whether or not logs will be sent to syslog. * @param syslog Flag indicating whether or not logs will be sent to syslog.
* @returns * @returns
*/ */
extern HOST_SW_API bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay = false, bool useSyslog = false); extern HOST_SW_API bool LogInitialise(const std::string& filePath, const std::string& fileRoot,
uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay = false, bool useSyslog = false);
/** /**
* @brief Finalizes the diagnostics log. * @brief Finalizes the diagnostics log.
*/ */
extern HOST_SW_API void LogFinalise(); extern HOST_SW_API void LogFinalise();
/** /**
* @brief Writes a new entry to the diagnostics log. * @brief Writes a new entry to the diagnostics log.
* @param level Log level for entry. * @param level Log level for entry.
* @param module Name of module generating log entry. * @param sourceLog Source code location information.
* @param file Name of source code file generating log entry.
* @param line Line number in source code file generating log entry.
* @param func Name of function generating log entry.
* @param fmt String format. * @param fmt String format.
* *
* This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead. * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead.
*/ */
extern HOST_SW_API void Log(uint32_t level, const char* module, const char* file, const int lineNo, const char* func, const char* fmt, ...); template<typename ... Args>
HOST_SW_API void Log(uint32_t level, log_internal::SourceLocation sourceLoc, const std::string& fmt, Args... args)
{
using namespace log_internal;
int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0'
if (size_s <= 0) {
throw std::runtime_error("Error during formatting.");
}
#if defined(CATCH2_TEST_COMPILATION)
g_disableTimeDisplay = true;
#endif
int prefixLen = 0;
char prefixBuf[256];
if (!g_disableTimeDisplay && !g_useSyslog) {
time_t now;
::time(&now);
struct tm* tm = ::localtime(&now);
struct timeval nowMillis;
::gettimeofday(&nowMillis, NULL);
if (level > 6U)
level = 2U; // default this sort of log message to INFO
if (sourceLoc.module != nullptr) {
// level 1 is DEBUG
if (level == 1U) {
// if we have a file and line number -- add that to the log entry
if (sourceLoc.filename != nullptr && sourceLoc.line > 0) {
// if we have a function name add that to the log entry
if (sourceLoc.funcname != nullptr) {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u][%s] ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U,
sourceLoc.module, sourceLoc.filename, sourceLoc.line, sourceLoc.funcname);
}
else {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u] ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U,
sourceLoc.module, sourceLoc.filename, sourceLoc.line);
}
} else {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U,
sourceLoc.module);
}
} else {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U,
sourceLoc.module);
}
}
else {
// level 1 is DEBUG
if (level == 1U) {
// if we have a file and line number -- add that to the log entry
if (sourceLoc.filename != nullptr && sourceLoc.line > 0) {
// if we have a function name add that to the log entry
if (sourceLoc.funcname != nullptr) {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u][%s] ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U,
sourceLoc.filename, sourceLoc.line, sourceLoc.funcname);
}
else {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u] ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U,
sourceLoc.filename, sourceLoc.line);
}
} else {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LOG_LEVELS[level], tm->tm_year + 1900,
tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U);
}
} else {
prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LOG_LEVELS[level],
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U);
}
}
}
else {
if (sourceLoc.module != nullptr) {
if (level > 6U)
level = 2U; // default this sort of log message to INFO
// level 1 is DEBUG
if (level == 1U) {
// if we have a file and line number -- add that to the log entry
if (sourceLoc.filename != nullptr && sourceLoc.line > 0) {
// if we have a function name add that to the log entry
if (sourceLoc.funcname != nullptr) {
prefixLen = ::sprintf(prefixBuf, "%c: (%s)[%s:%u][%s] ", LOG_LEVELS[level],
sourceLoc.module, sourceLoc.filename, sourceLoc.line, sourceLoc.funcname);
}
else {
prefixLen = ::sprintf(prefixBuf, "%c: (%s)[%s:%u] ", LOG_LEVELS[level],
sourceLoc.module, sourceLoc.filename, sourceLoc.line);
}
}
else {
prefixLen = ::sprintf(prefixBuf, "%c: (%s) ", LOG_LEVELS[level],
sourceLoc.module);
}
} else {
prefixLen = ::sprintf(prefixBuf, "%c: (%s) ", LOG_LEVELS[level],
sourceLoc.module);
}
}
else {
if (level >= 9999U) {
prefixLen = ::sprintf(prefixBuf, "U: ");
}
else {
if (level > 6U)
level = 2U; // default this sort of log message to INFO
// if we have a file and line number -- add that to the log entry
if (sourceLoc.filename != nullptr && sourceLoc.line > 0) {
// if we have a function name add that to the log entry
if (sourceLoc.funcname != nullptr) {
prefixLen = ::sprintf(prefixBuf, "%c: [%s:%u][%s] ", LOG_LEVELS[level],
sourceLoc.filename, sourceLoc.line, sourceLoc.funcname);
}
else {
prefixLen = ::sprintf(prefixBuf, "%c: [%s:%u] ", LOG_LEVELS[level],
sourceLoc.filename, sourceLoc.line);
}
}
else {
prefixLen = ::sprintf(prefixBuf, "%c: ", LOG_LEVELS[level]);
}
}
}
}
auto size = static_cast<size_t>(size_s);
auto buf = std::make_unique<char[]>(size);
std::snprintf(buf.get(), size, fmt.c_str(), args ...);
std::string prefix = std::string(prefixBuf, prefixBuf + prefixLen);
std::string msg = std::string(buf.get(), buf.get() + size - 1);
LogInternal(level, std::string(prefix + msg));
}
/** @} */ /** @} */
#endif // __LOG_H__ #endif // __LOG_H__

@ -47,8 +47,8 @@ typedef HANDLE pthread_t;
* @ingroup common * @ingroup common
*/ */
struct thread_t { struct thread_t {
void* obj; //! Object that created this thread. void* obj; //!< Object that created this thread.
pthread_t thread; //! Thread Handle. pthread_t thread; //!< Thread Handle.
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

@ -72,7 +72,7 @@ void Utils::dump(int level, const std::string& title, const uint8_t* data, uint3
{ {
assert(data != nullptr); assert(data != nullptr);
::Log(level, "DUMP", nullptr, 0, nullptr, "%s (len %u)", title.c_str(), length); ::Log(level, {"DUMP", nullptr, 0, nullptr}, "%s (len %u)", title.c_str(), length);
uint32_t offset = 0U; uint32_t offset = 0U;
@ -103,7 +103,7 @@ void Utils::dump(int level, const std::string& title, const uint8_t* data, uint3
output += '*'; output += '*';
#endif #endif
::Log(level, "DUMP", nullptr, 0, nullptr, "%04X: %s", offset, output.c_str()); ::Log(level, {"DUMP", nullptr, 0, nullptr}, "%04X: %s", offset, output.c_str());
offset += 16U; offset += 16U;
@ -143,7 +143,7 @@ void Utils::symbols(const std::string& title, const uint8_t* data, uint32_t leng
{ {
assert(data != nullptr); assert(data != nullptr);
::Log(2U, "SYMBOLS", nullptr, 0, nullptr, "%s (len %u)", title.c_str(), length); ::Log(2U, {"SYMBOLS", nullptr, 0, nullptr}, "%s (len %u)", title.c_str(), length);
uint32_t offset = 0U; uint32_t offset = 0U;
uint32_t count = 0U; uint32_t count = 0U;
@ -158,7 +158,7 @@ void Utils::symbols(const std::string& title, const uint8_t* data, uint32_t leng
microslotHeader += temp; microslotHeader += temp;
} }
::Log(2U, "SYMBOLS", nullptr, 0, nullptr, "MCR: %s", microslotHeader.c_str()); ::Log(2U, {"SYMBOLS", nullptr, 0, nullptr}, "MCR: %s", microslotHeader.c_str());
uint32_t bufLen = length; uint32_t bufLen = length;
while (bufLen > 0U) { while (bufLen > 0U) {
@ -188,7 +188,7 @@ void Utils::symbols(const std::string& title, const uint8_t* data, uint32_t leng
symOffset += 9; symOffset += 9;
} }
::Log(2U, "SYMBOLS", nullptr, 0, nullptr, "%03u: %s", count, output.c_str()); ::Log(2U, {"SYMBOLS", nullptr, 0, nullptr}, "%03u: %s", count, output.c_str());
offset += 18U; offset += 18U;
count += 2U; count += 2U;

@ -37,17 +37,17 @@ namespace analog
* @{ * @{
*/ */
const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //! Sample size for 20ms of 16-bit audio at 8kHz. const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //!< Sample size for 20ms of 16-bit audio at 8kHz.
const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //! Sample size for 20ms of 16-bit audio at 8kHz in bytes. const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //!< Sample size for 20ms of 16-bit audio at 8kHz in bytes.
/** @} */ /** @} */
/** @brief Audio Frame Type(s) */ /** @brief Audio Frame Type(s) */
namespace AudioFrameType { namespace AudioFrameType {
/** @brief Audio Frame Type(s) */ /** @brief Audio Frame Type(s) */
enum E : uint8_t { enum E : uint8_t {
VOICE_START = 0x00U, //! Voice Start Frame VOICE_START = 0x00U, //!< Voice Start Frame
VOICE = 0x01U, //! Voice Continuation Frame VOICE = 0x01U, //!< Voice Continuation Frame
TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator TERMINATOR = 0x02U, //!< Voice End Frame / Call Terminator
}; };
} }

File diff suppressed because it is too large Load Diff

@ -62,8 +62,8 @@ namespace concurrent
void spinlock() const { __spinlock(); } void spinlock() const { __spinlock(); }
protected: protected:
mutable std::mutex m_mutex; //! Mutex used for change locking. 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. 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. * @brief Lock the object.

@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file concurrent_shared_lock.h
* @ingroup concurrency
*/
#if !defined(__CONCURRENCY_CONCURRENT_SHARED_LOCK_H__)
#define __CONCURRENCY_CONCURRENT_SHARED_LOCK_H__
#include "common/Thread.h"
#include <shared_mutex>
#include <condition_variable>
namespace concurrent
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Base class for a concurrently shared locked container.
* @ingroup concurrency
*/
class concurrent_shared_lock
{
public:
/**
* @brief Initializes a new instance of the concurrent_shared_lock class.
*/
concurrent_shared_lock() :
m_mutex()
{
/* stub */
}
/**
* @brief Locks the object.
*/
void lock() const { __lock(); }
/**
* @brief Unlocks the object.
*/
void unlock() const { __unlock(); }
/**
* @brief Share locks the object.
*/
void shared_lock() const { __shared_lock(); }
/**
* @brief Share unlocks the object.
*/
void shared_unlock() const { __shared_unlock(); }
protected:
mutable std::shared_timed_mutex m_mutex; //!< Mutex used for locking.
/**
* @brief Lock the object.
*/
inline void __lock() const { m_mutex.lock(); }
/**
* @brief Lock the object.
*/
inline void __shared_lock() const { m_mutex.lock_shared(); }
/**
* @brief Unlock the object.
*/
inline void __unlock() const { m_mutex.unlock(); }
/**
* @brief Unlock the object.
*/
inline void __shared_unlock() const { m_mutex.unlock_shared(); }
};
} // namespace concurrent
#endif // __CONCURRENCY_CONCURRENT_SHARED_LOCK_H__

@ -0,0 +1,369 @@
// 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 shared_unordered_map.h
* @ingroup concurrency
*/
#if !defined(__CONCURRENCY_SHARED_UNORDERED_MAP_H__)
#define __CONCURRENCY_SHARED_UNORDERED_MAP_H__
#include "common/concurrent/concurrent_shared_lock.h"
#include "common/Thread.h"
#include <unordered_map>
#include <mutex>
namespace concurrent
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Thread-safe share-locked std::unordered_map. Read operations
* must use shared_lock()/shared_unlock() to ensure thread-safety. (This includes iterators.)
* @ingroup concurrency
*/
template <typename Key, typename T>
class shared_unordered_map : public concurrent_shared_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 shared_unordered_map class.
*/
shared_unordered_map() : concurrent_shared_lock(),
m_map()
{
/* stub */
}
/**
* @brief Initializes a new instance of the shared_unordered_map class.
* @param size Initial size of the shared_unordered_map.
*/
shared_unordered_map(size_t size) : concurrent_shared_lock(),
m_map(size)
{
/* stub */
}
/**
* @brief Finalizes a instance of the shared_unordered_map class.
*/
virtual ~shared_unordered_map()
{
m_map.clear();
}
/**
* @brief Unordered map assignment operator.
* @param other A map of identical element and allocator types.
*/
shared_unordered_map& operator=(const shared_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.
*/
shared_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.
*/
shared_unordered_map& operator=(shared_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.
*/
shared_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()
{
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
{
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()
{
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
{
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
{
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
{
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)
{
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
{
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)
{
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
{
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
{
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
{
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
{
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)
{
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
{
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
{
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()
{
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
{
return m_map;
}
/**
* @brief Prepare the underlying unordered_map for a specified number of
* elements.
* @param n Number of elements required.
*/
void reserve(size_t n)
{
m_map.reserve(n);
}
private:
std::unordered_map<Key, T> m_map;
};
} // namespace concurrent
#endif // __CONCURRENCY_SHARED_UNORDERED_MAP_H__

@ -0,0 +1,426 @@
// 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 shared_vector.h
* @ingroup concurrency
*/
#if !defined(__CONCURRENCY_SHARED_VECTOR_H__)
#define __CONCURRENCY_SHARED_VECTOR_H__
#include "common/concurrent/concurrent_shared_lock.h"
#include "common/Thread.h"
#include <vector>
#include <mutex>
namespace concurrent
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Thread-safe share-locked std::vector. Read operations
* must use shared_lock()/shared_unlock() to ensure thread-safety. (This includes iterators.)
* @ingroup concurrency
*/
template <typename T>
class shared_vector : public concurrent_shared_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 shared_vector class.
*/
shared_vector() : concurrent_shared_lock(),
m_vector()
{
/* stub */
}
/**
* @brief Initializes a new instance of the shared_vector class.
* @param size Initial size of the vector.
*/
shared_vector(size_t size) : concurrent_shared_lock(),
m_vector(size)
{
/* stub */
}
/**
* @brief Finalizes a instance of the shared_vector class.
*/
virtual ~shared_vector()
{
m_vector.clear();
}
/**
* @brief Vector assignment operator.
* @param other A vector of identical element and allocator types.
*/
shared_vector& operator=(const shared_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.
*/
shared_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.
*/
shared_vector& operator=(shared_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.
*/
shared_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()
{
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
{
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()
{
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
{
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
{
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
{
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
{
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
{
return m_vector.capacity();
}
/**
* @brief Checks if the vector is empty.
* @returns bool True if the vector is empty, false otherwise.
*/
bool empty() const
{
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)
{
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
{
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)
{
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
{
return m_vector.at(index);
}
/**
* @brief Gets the first element of the vector.
* @returns T& First element of the vector.
*/
T& front()
{
return m_vector.front();
}
/**
* @brief Gets the first element of the vector.
* @returns const T& First element of the vector.
*/
const T& front() const
{
return m_vector.front();
}
/**
* @brief Gets the last element of the vector.
* @returns T& Last element of the vector.
*/
T& back()
{
return m_vector.back();
}
/**
* @brief Gets the last element of the vector.
* @returns const T& Last element of the vector.
*/
const T& back() const
{
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(shared_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()
{
return m_vector;
}
/**
* @brief Gets the underlying vector.
* @returns const std::vector<T>& Underlying vector.
*/
const std::vector<T>& get() const
{
return m_vector;
}
/**
* @brief Prepare the underlying vector for a specified number of
* elements.
* @param n Number of elements required.
*/
void reserve(size_t n)
{
m_vector.reserve(n);
}
private:
std::vector<T> m_vector;
};
} // namespace concurrent
#endif // __CONCURRENCY_SHARED_VECTOR_H__

@ -368,6 +368,16 @@ namespace concurrent
return m_map; return m_map;
} }
/**
* @brief Prepare the underlying unordered_map for a specified number of
* elements.
* @param n Number of elements required.
*/
void reserve(size_t n)
{
m_map.reserve(n);
}
private: private:
std::unordered_map<Key, T> m_map; std::unordered_map<Key, T> m_map;
}; };

@ -430,6 +430,16 @@ namespace concurrent
return m_vector; return m_vector;
} }
/**
* @brief Prepare the underlying vector for a specified number of
* elements.
* @param n Number of elements required.
*/
void reserve(size_t n)
{
m_vector.reserve(n);
}
private: private:
std::vector<T> m_vector; std::vector<T> m_vector;
}; };

@ -112,12 +112,14 @@ namespace dmr
const uint32_t MAX_PDU_COUNT = 32U; const uint32_t MAX_PDU_COUNT = 32U;
const uint32_t DMR_PDU_UNCONFIRMED_LENGTH_BYTES = 12U; const uint32_t DMR_PDU_HALFRATE_LENGTH_BYTES = 12U;
const uint32_t DMR_PDU_CONFIRMED_LENGTH_BYTES = 18U; const uint32_t DMR_PDU_THREEQUARTER_LENGTH_BYTES = 18U;
const uint32_t DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES = 16U;
const uint32_t DMR_PDU_CONFIRMED_HALFRATE_DATA_LENGTH_BYTES = 10U;
const uint32_t DMR_PDU_UNCODED_LENGTH_BYTES = 24U; const uint32_t DMR_PDU_UNCODED_LENGTH_BYTES = 24U;
const uint32_t DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES = 16U;
const uint32_t DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES = 10U;
const uint32_t DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES = 22U;
const uint32_t MI_LENGTH_BYTES = 4U; // This was guessed based on OTA data captures -- the message indicator seems to be the same length as a source/destination address const uint32_t MI_LENGTH_BYTES = 4U; // This was guessed based on OTA data captures -- the message indicator seems to be the same length as a source/destination address
const uint32_t RAW_AMBE_LENGTH_BYTES = 9U; const uint32_t RAW_AMBE_LENGTH_BYTES = 9U;
/** @} */ /** @} */
@ -147,16 +149,16 @@ namespace dmr
const uint16_t DMR_LOGICAL_CH_ABSOLUTE = 0xFFFU; const uint16_t DMR_LOGICAL_CH_ABSOLUTE = 0xFFFU;
const uint32_t WUID_SUPLI = 0xFFFEC4U; //! Supplementary Data Service Working Unit ID const uint32_t WUID_SUPLI = 0xFFFEC4U; //!< Supplementary Data Service Working Unit ID
const uint32_t WUID_SDMI = 0xFFFEC5U; //! UDT Short Data Service Working Unit ID const uint32_t WUID_SDMI = 0xFFFEC5U; //!< UDT Short Data Service Working Unit ID
const uint32_t WUID_REGI = 0xFFFEC6U; //! Registration Working Unit ID const uint32_t WUID_REGI = 0xFFFEC6U; //!< Registration Working Unit ID
const uint32_t WUID_STUNI = 0xFFFECCU; //! MS Stun/Revive Identifier const uint32_t WUID_STUNI = 0xFFFECCU; //!< MS Stun/Revive Identifier
const uint32_t WUID_AUTHI = 0xFFFECDU; //! Authentication Working Unit ID const uint32_t WUID_AUTHI = 0xFFFECDU; //!< Authentication Working Unit ID
const uint32_t WUID_KILLI = 0xFFFECFU; //! MS Kill Identifier const uint32_t WUID_KILLI = 0xFFFECFU; //!< MS Kill Identifier
const uint32_t WUID_TATTSI = 0xFFFED7U; //! Talkgroup Subscription/Attachement Service Working Unit ID const uint32_t WUID_TATTSI = 0xFFFED7U; //!< Talkgroup Subscription/Attachement Service Working Unit ID
const uint32_t WUID_ALLL = 0xFFFFFDU; //! All-call Site-wide Working Unit ID const uint32_t WUID_ALLL = 0xFFFFFDU; //!< All-call Site-wide Working Unit ID
const uint32_t WUID_ALLZ = 0xFFFFFEU; //! All-call System-wide Working Unit ID const uint32_t WUID_ALLZ = 0xFFFFFEU; //!< All-call System-wide Working Unit ID
const uint32_t WUID_ALL = 0xFFFFFFU; //! All-call Network-wide Working Unit ID const uint32_t WUID_ALL = 0xFFFFFFU; //!< All-call Network-wide Working Unit ID
const uint32_t NO_HEADERS_SIMPLEX = 8U; const uint32_t NO_HEADERS_SIMPLEX = 8U;
const uint32_t NO_HEADERS_DUPLEX = 3U; const uint32_t NO_HEADERS_DUPLEX = 3U;
@ -167,13 +169,13 @@ namespace dmr
namespace DPF { namespace DPF {
/** @brief Data Packet Format */ /** @brief Data Packet Format */
enum E : uint8_t { enum E : uint8_t {
UDT = 0x00U, //! Unified Data Transport Header UDT = 0x00U, //!< Unified Data Transport Header
RESPONSE = 0x01U, //! Response Data Header RESPONSE = 0x01U, //!< Response Data Header
UNCONFIRMED_DATA = 0x02U, //! Unconfirmed Data Header UNCONFIRMED_DATA = 0x02U, //!< Unconfirmed Data Header
CONFIRMED_DATA = 0x03U, //! Confirmed Data Header CONFIRMED_DATA = 0x03U, //!< Confirmed Data Header
DEFINED_SHORT = 0x0DU, //! Defined Short Data Header DEFINED_SHORT = 0x0DU, //!< Defined Short Data Header
DEFINED_RAW = 0x0EU, //! Defined Raw Data Header DEFINED_RAW = 0x0EU, //!< Defined Raw Data Header
PROPRIETARY = 0x0FU, //! Proprietary PROPRIETARY = 0x0FU, //!< Proprietary
}; };
}; };
@ -181,9 +183,9 @@ namespace dmr
namespace PDUResponseClass { namespace PDUResponseClass {
/** @brief Data Response Class */ /** @brief Data Response Class */
enum : uint8_t { enum : uint8_t {
ACK = 0x00U, //! Acknowledge ACK = 0x00U, //!< Acknowledge
NACK = 0x01U, //! Negative Acknowledge NACK = 0x01U, //!< Negative Acknowledge
ACK_RETRY = 0x02U //! Acknowlege Retry ACK_RETRY = 0x02U //!< Acknowlege Retry
}; };
}; };
@ -191,15 +193,20 @@ namespace dmr
namespace PDUResponseType { namespace PDUResponseType {
/** @brief Data Response Type */ /** @brief Data Response Type */
enum : uint8_t { enum : uint8_t {
ACK = 0x01U, //! Acknowledge ACK = 0x01U, //!< Acknowledge
NACK_ILLEGAL = 0x00U, //! Illegal Format NACK_ILLEGAL = 0x00U, //!< Illegal Format
NACK_PACKET_CRC = 0x01U, //! Packet CRC NACK_PACKET_CRC = 0x01U, //!< Packet CRC
NACK_MEMORY_FULL = 0x02U, //! Memory Full NACK_MEMORY_FULL = 0x02U, //!< Memory Full
NACK_UNDELIVERABLE = 0x04U //! Undeliverable NACK_UNDELIVERABLE = 0x04U //!< Undeliverable
}; };
}; };
/** @brief ARP Request */
const uint8_t DMR_PDU_ARP_REQUEST = 0x01U;
/** @brief ARP Reply */
const uint8_t DMR_PDU_ARP_REPLY = 0x02U;
/** @name Feature Set IDs */ /** @name Feature Set IDs */
/** @brief ETSI Standard Feature Set */ /** @brief ETSI Standard Feature Set */
const uint8_t FID_ETSI = 0x00U; const uint8_t FID_ETSI = 0x00U;
@ -239,10 +246,10 @@ namespace dmr
namespace SLCO { namespace SLCO {
/** @brief Short-Link Control Opcode(s) */ /** @brief Short-Link Control Opcode(s) */
enum E : uint8_t { enum E : uint8_t {
NONE = 0x00U, //! NULL NONE = 0x00U, //!< NULL
ACT = 0x01U, //! ACT = 0x01U, //!
TSCC = 0x02U, //! TSCC TSCC = 0x02U, //!< TSCC
PAYLOAD = 0x03U //! Payload PAYLOAD = 0x03U //!< Payload
}; };
} }
@ -250,8 +257,8 @@ namespace dmr
namespace FLCO { namespace FLCO {
/** @brief Full-Link Control Opcode(s) */ /** @brief Full-Link Control Opcode(s) */
enum E : uint8_t { enum E : uint8_t {
GROUP = 0x00U, //! GRP VCH USER - Group Voice Channel User GROUP = 0x00U, //!< GRP VCH USER - Group Voice Channel User
PRIVATE = 0x03U, //! UU VCH USER - Unit-to-Unit Voice Channel User PRIVATE = 0x03U, //!< UU VCH USER - Unit-to-Unit Voice Channel User
TALKER_ALIAS_HEADER = 0x04U, //! TALKER_ALIAS_HEADER = 0x04U, //!
TALKER_ALIAS_BLOCK1 = 0x05U, //! TALKER_ALIAS_BLOCK1 = 0x05U, //!
@ -266,12 +273,12 @@ namespace dmr
namespace ExtendedFunctions { namespace ExtendedFunctions {
/** @brief FID_MOT Extended Functions. */ /** @brief FID_MOT Extended Functions. */
enum : uint16_t { enum : uint16_t {
CHECK = 0x0000U, //! Radio Check CHECK = 0x0000U, //!< Radio Check
UNINHIBIT = 0x007EU, //! Radio Uninhibit UNINHIBIT = 0x007EU, //!< Radio Uninhibit
INHIBIT = 0x007FU, //! Radio Inhibit INHIBIT = 0x007FU, //!< Radio Inhibit
CHECK_ACK = 0x0080U, //! Radio Check Ack CHECK_ACK = 0x0080U, //!< Radio Check Ack
UNINHIBIT_ACK = 0x00FEU, //! Radio Uninhibit Ack UNINHIBIT_ACK = 0x00FEU, //!< Radio Uninhibit Ack
INHIBIT_ACK = 0x00FFU //! Radio Inhibit Ack INHIBIT_ACK = 0x00FFU //!< Radio Inhibit Ack
}; };
}; };
@ -279,30 +286,31 @@ namespace dmr
namespace DataType { namespace DataType {
/** @brief Data Type(s) */ /** @brief Data Type(s) */
enum E : uint8_t { enum E : uint8_t {
VOICE_PI_HEADER = 0x00U, //! Voice with Privacy Indicator Header VOICE_PI_HEADER = 0x00U, //!< Voice with Privacy Indicator Header
VOICE_LC_HEADER = 0x01U, //! Voice with Link Control Header VOICE_LC_HEADER = 0x01U, //!< Voice with Link Control Header
TERMINATOR_WITH_LC = 0x02U, //! Terminator with Link Control TERMINATOR_WITH_LC = 0x02U, //!< Terminator with Link Control
CSBK = 0x03U, //! CSBK CSBK = 0x03U, //!< CSBK
MBC_HEADER = 0x04U, //! Multi-Block Control Header MBC_HEADER = 0x04U, //!< Multi-Block Control Header
MBC_DATA = 0x05U, //! Multi-Block Control Data MBC_DATA = 0x05U, //!< Multi-Block Control Data
DATA_HEADER = 0x06U, //! Data Header DATA_HEADER = 0x06U, //!< Data Header
RATE_12_DATA = 0x07U, //! 1/2 Rate Data RATE_12_DATA = 0x07U, //!< 1/2 Rate Data
RATE_34_DATA = 0x08U, //! 3/4 Rate Data RATE_34_DATA = 0x08U, //!< 3/4 Rate Data
IDLE = 0x09U, //! Idle IDLE = 0x09U, //!< Idle
RATE_1_DATA = 0x0AU, //! Rate 1 Data RATE_1_DATA = 0x0AU, //!< Rate 1 Data
/* /*
** Internal Data Type(s) ** Internal Data Type(s)
*/ */
VOICE_SYNC = 0xF0U, //! Internal - Voice Sync VOICE_SYNC = 0xF0U, //!< Internal - Voice Sync
VOICE = 0xF1U //! Internal - Voice VOICE = 0xF1U, //!< Internal - Voice
GENERIC_DATA = 0xF2U //!< Internal - Data
}; };
} }
@ -321,10 +329,10 @@ namespace dmr
namespace SiteModel { namespace SiteModel {
/** @brief Site Models */ /** @brief Site Models */
enum E : uint8_t { enum E : uint8_t {
SM_TINY = 0x00U, //! Tiny SM_TINY = 0x00U, //!< Tiny
SM_SMALL = 0x01U, //! Small SM_SMALL = 0x01U, //!< Small
SM_LARGE = 0x02U, //! Large SM_LARGE = 0x02U, //!< Large
SM_HUGE = 0x03U //! Huge SM_HUGE = 0x03U //!< Huge
}; };
} }
@ -337,13 +345,13 @@ namespace dmr
namespace TalkerID { namespace TalkerID {
/** @brief Talker ID */ /** @brief Talker ID */
enum : uint8_t { enum : uint8_t {
NONE = 0x00U, //! No Talker ID NONE = 0x00U, //!< No Talker ID
HEADER = 0x01U, //! Talker ID Header HEADER = 0x01U, //!< Talker ID Header
BLOCK1 = 0x02U, //! Talker ID Block 1 BLOCK1 = 0x02U, //!< Talker ID Block 1
BLOCK2 = 0x04U, //! Talker ID Block 2 BLOCK2 = 0x04U, //!< Talker ID Block 2
BLOCK3 = 0x08U //! Talker ID Block 3 BLOCK3 = 0x08U //!< Talker ID Block 3
}; };
} }
@ -351,34 +359,34 @@ namespace dmr
namespace ReasonCode { namespace ReasonCode {
/** @brief Reason Code(s) */ /** @brief Reason Code(s) */
enum : uint8_t { enum : uint8_t {
TS_ACK_RSN_MSG = 0x60U, //! TS - Message Accepted TS_ACK_RSN_MSG = 0x60U, //!< TS - Message Accepted
TS_ACK_RSN_REG = 0x62U, //! TS - Registration Accepted TS_ACK_RSN_REG = 0x62U, //!< TS - Registration Accepted
TS_ACK_RSN_AUTH_RESP = 0x64U, //! TS - Authentication Challenge Response TS_ACK_RSN_AUTH_RESP = 0x64U, //!< TS - Authentication Challenge Response
TS_ACK_RSN_REG_SUB_ATTACH = 0x65U, //! TS - Registration Response with subscription TS_ACK_RSN_REG_SUB_ATTACH = 0x65U, //!< TS - Registration Response with subscription
MS_ACK_RSN_MSG = 0x44U, //! MS - Message Accepted MS_ACK_RSN_MSG = 0x44U, //!< MS - Message Accepted
MS_ACK_RSN_AUTH_RESP = 0x48U, //! MS - Authentication Challenge Response MS_ACK_RSN_AUTH_RESP = 0x48U, //!< MS - Authentication Challenge Response
TS_DENY_RSN_SYS_UNSUPPORTED_SVC = 0x20U,//! System Unsupported Service TS_DENY_RSN_SYS_UNSUPPORTED_SVC = 0x20U,//!< System Unsupported Service
TS_DENY_RSN_PERM_USER_REFUSED = 0x21U, //! User Permenantly Refused TS_DENY_RSN_PERM_USER_REFUSED = 0x21U, //!< User Permenantly Refused
TS_DENY_RSN_TEMP_USER_REFUSED = 0x22U, //! User Temporarily Refused TS_DENY_RSN_TEMP_USER_REFUSED = 0x22U, //!< User Temporarily Refused
TS_DENY_RSN_TRSN_SYS_REFUSED = 0x23U, //! System Refused TS_DENY_RSN_TRSN_SYS_REFUSED = 0x23U, //!< System Refused
TS_DENY_RSN_TGT_NOT_REG = 0x24U, //! Target Not Registered TS_DENY_RSN_TGT_NOT_REG = 0x24U, //!< Target Not Registered
TS_DENY_RSN_TGT_UNAVAILABLE = 0x25U, //! Target Unavailable TS_DENY_RSN_TGT_UNAVAILABLE = 0x25U, //!< Target Unavailable
TS_DENY_RSN_SYS_BUSY = 0x27U, //! System Busy TS_DENY_RSN_SYS_BUSY = 0x27U, //!< System Busy
TS_DENY_RSN_SYS_NOT_READY = 0x28U, //! System Not Ready TS_DENY_RSN_SYS_NOT_READY = 0x28U, //!< System Not Ready
TS_DENY_RSN_CALL_CNCL_REFUSED = 0x29U, //! Call Cancel Refused TS_DENY_RSN_CALL_CNCL_REFUSED = 0x29U, //!< Call Cancel Refused
TS_DENY_RSN_REG_REFUSED = 0x2AU, //! Registration Refused TS_DENY_RSN_REG_REFUSED = 0x2AU, //!< Registration Refused
TS_DENY_RSN_REG_DENIED = 0x2BU, //! Registration Denied TS_DENY_RSN_REG_DENIED = 0x2BU, //!< Registration Denied
TS_DENY_RSN_MS_NOT_REG = 0x2DU, //! MS Not Registered TS_DENY_RSN_MS_NOT_REG = 0x2DU, //!< MS Not Registered
TS_DENY_RSN_TGT_BUSY = 0x2EU, //! Target Busy TS_DENY_RSN_TGT_BUSY = 0x2EU, //!< Target Busy
TS_DENY_RSN_TGT_GROUP_NOT_VALID = 0x2FU,//! Group Not Valid TS_DENY_RSN_TGT_GROUP_NOT_VALID = 0x2FU,//!< Group Not Valid
TS_QUEUED_RSN_NO_RESOURCE = 0xA0U, //! No Resources Available TS_QUEUED_RSN_NO_RESOURCE = 0xA0U, //!< No Resources Available
TS_QUEUED_RSN_SYS_BUSY = 0xA1U, //! System Busy TS_QUEUED_RSN_SYS_BUSY = 0xA1U, //!< System Busy
TS_WAIT_RSN = 0xE0U, //! Wait TS_WAIT_RSN = 0xE0U, //!< Wait
MS_DENY_RSN_UNSUPPORTED_SVC = 0x00U, //! Service Unsupported MS_DENY_RSN_UNSUPPORTED_SVC = 0x00U, //!< Service Unsupported
}; };
} }
@ -386,19 +394,19 @@ namespace dmr
namespace ServiceKind { namespace ServiceKind {
/** @brief Random Access Service Kind */ /** @brief Random Access Service Kind */
enum : uint8_t { enum : uint8_t {
IND_VOICE_CALL = 0x00U, //! Individual Voice Call IND_VOICE_CALL = 0x00U, //!< Individual Voice Call
GRP_VOICE_CALL = 0x01U, //! Group Voice Call GRP_VOICE_CALL = 0x01U, //!< Group Voice Call
IND_DATA_CALL = 0x02U, //! Individual Data Call IND_DATA_CALL = 0x02U, //!< Individual Data Call
GRP_DATA_CALL = 0x03U, //! Group Data Call GRP_DATA_CALL = 0x03U, //!< Group Data Call
IND_UDT_DATA_CALL = 0x04U, //! Individual UDT Short Data Call IND_UDT_DATA_CALL = 0x04U, //!< Individual UDT Short Data Call
GRP_UDT_DATA_CALL = 0x05U, //! Group UDT Short Data Call GRP_UDT_DATA_CALL = 0x05U, //!< Group UDT Short Data Call
UDT_SHORT_POLL = 0x06U, //! UDT Short Data Polling Service UDT_SHORT_POLL = 0x06U, //!< UDT Short Data Polling Service
STATUS_TRANSPORT = 0x07U, //! Status Transport Service STATUS_TRANSPORT = 0x07U, //!< Status Transport Service
CALL_DIVERSION = 0x08U, //! Call Diversion Service CALL_DIVERSION = 0x08U, //!< Call Diversion Service
CALL_ANSWER = 0x09U, //! Call Answer Service CALL_ANSWER = 0x09U, //!< Call Answer Service
SUPPLEMENTARY_SVC = 0x0DU, //! Supplementary Service SUPPLEMENTARY_SVC = 0x0DU, //!< Supplementary Service
REG_SVC = 0x0EU, //! Registration Service REG_SVC = 0x0EU, //!< Registration Service
CANCEL_CALL = 0x0FU //! Cancel Call Service CANCEL_CALL = 0x0FU //!< Cancel Call Service
}; };
} }
@ -406,14 +414,14 @@ namespace dmr
namespace BroadcastAnncType { namespace BroadcastAnncType {
/** @brief Broadcast Announcement Type(s) */ /** @brief Broadcast Announcement Type(s) */
enum : uint8_t { enum : uint8_t {
ANN_WD_TSCC = 0x00U, //! Announce-Withdraw TSCC Channel ANN_WD_TSCC = 0x00U, //!< Announce-Withdraw TSCC Channel
CALL_TIMER_PARMS = 0x01U, //! Specify Call Timer Parameters CALL_TIMER_PARMS = 0x01U, //!< Specify Call Timer Parameters
VOTE_NOW = 0x02U, //! Vote Now Advice VOTE_NOW = 0x02U, //!< Vote Now Advice
LOCAL_TIME = 0x03U, //! Broadcast Local Time LOCAL_TIME = 0x03U, //!< Broadcast Local Time
MASS_REG = 0x04U, //! Mass Registration MASS_REG = 0x04U, //!< Mass Registration
CHAN_FREQ = 0x05U, //! Logical Channel/Frequency CHAN_FREQ = 0x05U, //!< Logical Channel/Frequency
ADJ_SITE = 0x06U, //! Adjacent Site Information ADJ_SITE = 0x06U, //!< Adjacent Site Information
SITE_PARMS = 0x07U //! General Site Parameters SITE_PARMS = 0x07U //!< General Site Parameters
}; };
} }
@ -423,28 +431,28 @@ namespace dmr
enum : uint8_t { enum : uint8_t {
// CSBK ISP/OSP Shared Opcode(s) // CSBK ISP/OSP Shared Opcode(s)
NONE = 0x00U, //! NONE = 0x00U, //!
UU_V_REQ = 0x04U, //! UU VCH REQ - Unit-to-Unit Voice Channel Request UU_V_REQ = 0x04U, //!< UU VCH REQ - Unit-to-Unit Voice Channel Request
UU_ANS_RSP = 0x05U, //! UU ANS RSP - Unit-to-Unit Answer Response UU_ANS_RSP = 0x05U, //!< UU ANS RSP - Unit-to-Unit Answer Response
CTCSBK = 0x07U, //! CT CSBK - Channel Timing CSBK CTCSBK = 0x07U, //!< CT CSBK - Channel Timing CSBK
ALOHA = 0x19U, //! ALOHA - Aloha PDU for Random Access ALOHA = 0x19U, //!< ALOHA - Aloha PDU for Random Access
AHOY = 0x1CU, //! AHOY - Enquiry from TSCC AHOY = 0x1CU, //!< AHOY - Enquiry from TSCC
RAND = 0x1FU, //! (ETSI) RAND - Random Access / (DMRA) CALL ALRT - Call Alert RAND = 0x1FU, //!< (ETSI) RAND - Random Access / (DMRA) CALL ALRT - Call Alert
ACK_RSP = 0x20U, //! ACK RSP - Acknowledge Response ACK_RSP = 0x20U, //!< ACK RSP - Acknowledge Response
EXT_FNCT = 0x24U, //! (DMRA) EXT FNCT - Extended Function EXT_FNCT = 0x24U, //!< (DMRA) EXT FNCT - Extended Function
NACK_RSP = 0x26U, //! NACK RSP - Negative Acknowledgement Response NACK_RSP = 0x26U, //!< NACK RSP - Negative Acknowledgement Response
BROADCAST = 0x28U, //! BCAST - Announcement PDU BROADCAST = 0x28U, //!< BCAST - Announcement PDU
MAINT = 0x2AU, //! MAINT - Call Maintainence PDU MAINT = 0x2AU, //!< MAINT - Call Maintainence PDU
P_CLEAR = 0x2EU, //! P_CLEAR - Payload Channel Clear P_CLEAR = 0x2EU, //!< P_CLEAR - Payload Channel Clear
PV_GRANT = 0x30U, //! PV_GRANT - Private Voice Channel Grant PV_GRANT = 0x30U, //!< PV_GRANT - Private Voice Channel Grant
TV_GRANT = 0x31U, //! TV_GRANT - Talkgroup Voice Channel Grant TV_GRANT = 0x31U, //!< TV_GRANT - Talkgroup Voice Channel Grant
BTV_GRANT = 0x32U, //! BTV_GRANT - Broadcast Talkgroup Voice Channel Grant BTV_GRANT = 0x32U, //!< BTV_GRANT - Broadcast Talkgroup Voice Channel Grant
PD_GRANT = 0x33U, //! PD_GRANT - Private Data Channel Grant PD_GRANT = 0x33U, //!< PD_GRANT - Private Data Channel Grant
TD_GRANT = 0x34U, //! TD_GRANT - Talkgroup Data Channel Grant TD_GRANT = 0x34U, //!< TD_GRANT - Talkgroup Data Channel Grant
BSDWNACT = 0x38U, //! BS DWN ACT - BS Outbound Activation BSDWNACT = 0x38U, //!< BS DWN ACT - BS Outbound Activation
PRECCSBK = 0x3DU, //! PRE CSBK - Preamble CSBK PRECCSBK = 0x3DU, //!< PRE CSBK - Preamble CSBK
// CSBK DVM Outbound Signalling Packet (OSP) Opcode(s) // CSBK DVM Outbound Signalling Packet (OSP) Opcode(s)
DVM_GIT_HASH = 0x3FU //! DVM_GIT_HASH = 0x3FU //!<
}; };
} }

@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "dmr/DMRDefines.h"
#include "dmr/data/Assembler.h"
#include "edac/CRC.h"
#include "Log.h"
#include "Utils.h"
using namespace dmr;
using namespace dmr::defines;
using namespace dmr::data;
#include <cassert>
#include <cstring>
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
bool Assembler::s_dumpPDUData = false;
bool Assembler::s_verbose = false;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the Assembler class. */
Assembler::Assembler() :
m_blockWriter(nullptr)
{
/* stub */
}
/* Finalizes a instance of the Assembler class. */
Assembler::~Assembler() = default;
/* Helper to assemble user data as a DMR PDU packet. */
void Assembler::assemble(data::DataHeader& dataHeader, DataType::E dataType, const uint8_t* pduUserData,
uint32_t* assembledBitLength, void* userContext)
{
assert(pduUserData != nullptr);
assert(m_blockWriter != nullptr);
if (assembledBitLength != nullptr)
*assembledBitLength = 0U;
uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * DMR_FRAME_LENGTH_BITS);
if (dataHeader.getPadLength() > 0U)
bitLength += (dataHeader.getPadLength() * 8U);
UInt8Array dataArray = std::make_unique<uint8_t[]>((bitLength / 8U) + 1U);
uint8_t* data = dataArray.get();
::memset(data, 0x00U, (bitLength / 8U) + 1U);
uint8_t block[DMR_FRAME_LENGTH_BYTES];
::memset(block, 0x00U, DMR_FRAME_LENGTH_BYTES);
uint32_t blocksToFollow = dataHeader.getBlocksToFollow();
if (s_verbose) {
LogInfoEx(LOG_DMR, DMR_DT_DATA_HEADER ", dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u",
dataHeader.getDPF(), dataHeader.getA(), dataHeader.getSAP(), dataHeader.getFullMesage(), dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(dataType),
dataHeader.getFSN(), dataHeader.getDstId(), dataHeader.getSrcId(), dataHeader.getGI());
}
// generate the PDU header
dataHeader.encode(block);
#if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, PDU Assembler Block", block, DMR_FRAME_LENGTH_BYTES);
#endif
m_blockWriter(userContext, 0U, block, DMR_FRAME_LENGTH_BYTES, false);
if (pduUserData != nullptr && blocksToFollow > 0U) {
uint32_t dataOffset = 0U;
uint32_t pduLength = dataHeader.getPDULength(dataType) + dataHeader.getPadLength();
uint32_t dataBlockCnt = 1U;
uint32_t secondHeaderOffset = 0U;
// we pad 20 bytes of extra space -- confirmed data will use various extra space in the PDU
DECLARE_UINT8_ARRAY(packetData, pduLength + 20U);
uint32_t packetLength = dataHeader.getPacketLength(dataType);
uint32_t padLength = dataHeader.getPadLength();
#if DEBUG_DMR_PDU_DATA
LogDebugEx(LOG_DMR, "Assembler::assemble()", "packetLength = %u, secondHeaderOffset = %u, padLength = %u, pduLength = %u", packetLength, secondHeaderOffset, padLength, pduLength);
#endif
::memcpy(packetData + secondHeaderOffset, pduUserData, packetLength);
edac::CRC::addCRC32(packetData, packetLength + 4U);
if (padLength > 0U) {
// move the CRC-32 to the end of the packet data after the padding
uint8_t crcBytes[4U];
::memcpy(crcBytes, packetData + packetLength, 4U);
::memset(packetData + packetLength, 0x00U, 4U);
::memcpy(packetData + (packetLength + padLength), crcBytes, 4U);
}
#if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, Assembled PDU User Data", packetData, packetLength + padLength + 4U);
#endif
// generate the PDU data
for (uint32_t i = 0U; i < blocksToFollow; i++) {
DataBlock dataBlock = DataBlock();
dataBlock.setFormat(dataHeader);
dataBlock.setSerialNo(i);
dataBlock.setData(packetData + dataOffset);
dataBlock.setLastBlock((i + 1U) == blocksToFollow);
if (s_verbose) {
if (dataType == DataType::RATE_34_DATA) {
LogInfoEx(LOG_DMR, DMR_DT_RATE_34_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X",
(dataHeader.getDPF() == DPF::CONFIRMED_DATA) ? dataBlock.getSerialNo() : i, dataBlock.getDataType(), dataBlock.getFormat());
} else if (dataType == DataType::RATE_12_DATA) {
LogInfoEx(LOG_DMR, DMR_DT_RATE_12_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X",
(dataHeader.getDPF() == DPF::CONFIRMED_DATA) ? dataBlock.getSerialNo() : i, dataBlock.getDataType(), dataBlock.getFormat());
}
else {
LogInfoEx(LOG_DMR, DMR_DT_RATE_1_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X",
(dataHeader.getDPF() == DPF::CONFIRMED_DATA) ? dataBlock.getSerialNo() : i, dataBlock.getDataType(), dataBlock.getFormat());
}
}
::memset(block, 0x00U, DMR_FRAME_LENGTH_BYTES);
dataBlock.encode(block);
#if DEBUG_P25_PDU_DATA
Utils::dump(1U, "DMR, PDU Assembler Block", block, DMR_FRAME_LENGTH_BYTES);
#endif
m_blockWriter(userContext, dataBlockCnt, block, DMR_FRAME_LENGTH_BYTES, dataBlock.getLastBlock());
if (dataHeader.getDPF() == DPF::CONFIRMED_DATA) {
if (dataType == DataType::RATE_34_DATA) {
dataOffset += DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES;
} else if (dataType == DataType::RATE_12_DATA) {
dataOffset += DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES;
}
else {
dataOffset += DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES;
}
} else {
if (dataType == DataType::RATE_34_DATA) {
dataOffset += DMR_PDU_THREEQUARTER_LENGTH_BYTES;
} else if (dataType == DataType::RATE_12_DATA) {
dataOffset += DMR_PDU_HALFRATE_LENGTH_BYTES;
}
else {
dataOffset += DMR_PDU_UNCODED_LENGTH_BYTES;
}
}
dataBlockCnt++;
}
}
if (assembledBitLength != nullptr)
*assembledBitLength = bitLength;
}

@ -0,0 +1,94 @@
// 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 Assembler.h
* @ingroup dmr_pdu
* @file Assembler.cpp
* @ingroup dmr_pdu
*/
#if !defined(__DMR_DATA__ASSEMBLER_H__)
#define __DMR_DATA__ASSEMBLER_H__
#include "common/Defines.h"
#include "common/dmr/data/DataBlock.h"
#include "common/dmr/data/DataHeader.h"
#include <string>
#include <functional>
namespace dmr
{
namespace data
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements a packet assembler for DMR PDU packet streams.
* @ingroup dmr_pdu
*/
class HOST_SW_API Assembler {
public:
/**
* @brief Initializes a new instance of the Assembler class.
*/
Assembler();
/**
* @brief Finalizes a instance of the Assembler class.
*/
~Assembler();
/**
* @brief Helper to assemble user data as a DMR PDU packet.
* @note When using a custom block writer, this will return null.
* @param dataHeader Instance of a PDU data header.
* @param dataType DMR packet data type.
* @param[in] pduUserData Buffer containing user data to assemble.
* @param[out] assembledBitLength Length of assembled packet in bits.
* @param[in] userContext User supplied context data to pass to custom block writer.
* @returns UInt8Array Assembled PDU buffer.
*/
void assemble(data::DataHeader& dataHeader, DMRDEF::DataType::E dataType, const uint8_t* pduUserData,
uint32_t* assembledBitLength, void* userContext = nullptr);
/**
* @brief Helper to set the custom block writer callback.
* @param callback
*/
void setBlockWriter(std::function<void(const void*, const uint8_t, const uint8_t*, uint32_t, bool)>&& callback)
{
m_blockWriter = callback;
}
/**
* @brief Sets the flag indicating whether or not the assembler will dump PDU data.
* @param dumpPDUData Flag indicating PDU log dumping.
*/
static void setDumpPDUData(bool dumpPDUData) { s_dumpPDUData = dumpPDUData; }
/**
* @brief Sets the flag indicating verbose log output.
* @param verbose Flag indicating verbose log output.
*/
static void setVerbose(bool verbose) { s_verbose = verbose; }
private:
static bool s_dumpPDUData;
static bool s_verbose;
/**
* @brief Custom block writing callback.
*/
std::function<void(const void* userContext, const uint8_t currentBlock, const uint8_t* data, uint32_t len, bool lastBlock)> m_blockWriter;
};
} // namespace data
} // namespace dmr
#endif // __DMR_DATA__ASSEMBLER_H__

@ -25,6 +25,13 @@ using namespace dmr::data;
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Initializes a copy instance of the DataBlock class. */
DataBlock::DataBlock(const DataBlock& data) : DataBlock()
{
copy(data);
}
/* Initializes a new instance of the DataBlock class. */ /* Initializes a new instance of the DataBlock class. */
DataBlock::DataBlock() : DataBlock::DataBlock() :
@ -43,7 +50,10 @@ DataBlock::DataBlock() :
DataBlock::~DataBlock() DataBlock::~DataBlock()
{ {
if (m_data != nullptr) {
delete[] m_data; delete[] m_data;
m_data = nullptr;
}
} }
/* Decodes DMR PDU data block. */ /* Decodes DMR PDU data block. */
@ -88,9 +98,11 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header)
::memset(m_data, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); ::memset(m_data, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
if (m_dataType == DataType::RATE_34_DATA) if (m_dataType == DataType::RATE_34_DATA)
::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); // Payload Data ::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); // Payload Data
else if (m_dataType == DataType::RATE_12_DATA) else if (m_dataType == DataType::RATE_12_DATA)
::memcpy(m_data, buffer + 2U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data ::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); // Payload Data
else if (m_dataType == DataType::RATE_1_DATA)
::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); // Payload Data
else { else {
LogError(LOG_DMR, "DataBlock::decode(), failed to decode block, invalid dataType = $%02X", m_dataType); LogError(LOG_DMR, "DataBlock::decode(), failed to decode block, invalid dataType = $%02X", m_dataType);
return false; return false;
@ -100,22 +112,40 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header)
::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
// generate CRC buffer // generate CRC buffer
uint32_t crcBitLength = 144U; if (m_dataType == DataType::RATE_34_DATA) {
if (m_dataType == DataType::RATE_12_DATA) ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES);
crcBitLength = 96U; for (uint32_t i = 0U; i < 7U; i++) {
for (uint32_t i = 16U; i < crcBitLength; i++) {
bool b = READ_BIT(buffer, i); bool b = READ_BIT(buffer, i);
WRITE_BIT(crcBuffer, i - 16U, b); WRITE_BIT(crcBuffer, i + 128U, b);
} }
}
for (uint32_t i = 0U; i < 6U; i++) { else if (m_dataType == DataType::RATE_12_DATA) {
::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES);
for (uint32_t i = 0U; i < 7U; i++) {
bool b = READ_BIT(buffer, i); bool b = READ_BIT(buffer, i);
WRITE_BIT(crcBuffer, i + (crcBitLength - 16U), b); WRITE_BIT(crcBuffer, i + 80U, b);
}
}
else if (m_dataType == DataType::RATE_1_DATA) {
::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES);
for (uint32_t i = 0U; i < 7U; i++) {
bool b = READ_BIT(buffer, i);
WRITE_BIT(crcBuffer, i + 176U, b);
}
} }
#if DEBUG_DMR_PDU_DATA
Utils::dump(2U, "DMR, CRC Bit Buffer", crcBuffer, DMR_PDU_UNCODED_LENGTH_BYTES);
#endif
uint32_t crcBitLength = 135U; // 3/4 Rate
if (m_dataType == DataType::RATE_12_DATA)
crcBitLength = 87U;
if (m_dataType == DataType::RATE_1_DATA)
crcBitLength = 183U;
// compute CRC-9 for the packet // compute CRC-9 for the packet
uint16_t calculated = edac::CRC::createCRC9(crcBuffer, crcBitLength - 9U); uint16_t calculated = edac::CRC::createCRC9(crcBuffer, crcBitLength);
calculated = ~calculated & 0x1FFU; calculated = ~calculated & 0x1FFU;
if ((crc ^ calculated) != 0) { if ((crc ^ calculated) != 0) {
@ -151,9 +181,9 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header)
::memset(m_data, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); ::memset(m_data, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
if (m_dataType == DataType::RATE_34_DATA) if (m_dataType == DataType::RATE_34_DATA)
::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); // Payload Data ::memcpy(m_data, buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES); // Payload Data
else if (m_dataType == DataType::RATE_12_DATA) else if (m_dataType == DataType::RATE_12_DATA)
::memcpy(m_data, buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data ::memcpy(m_data, buffer, DMR_PDU_HALFRATE_LENGTH_BYTES); // Payload Data
else { else {
LogError(LOG_DMR, "DataBlock::decode(), failed to decode block, invalid dataType = $%02X", m_dataType); LogError(LOG_DMR, "DataBlock::decode(), failed to decode block, invalid dataType = $%02X", m_dataType);
return false; return false;
@ -180,98 +210,122 @@ void DataBlock::encode(uint8_t* data)
if (m_DPF == DPF::CONFIRMED_DATA) { if (m_DPF == DPF::CONFIRMED_DATA) {
if (m_dataType == DataType::RATE_34_DATA) { if (m_dataType == DataType::RATE_34_DATA) {
uint8_t buffer[DMR_PDU_CONFIRMED_LENGTH_BYTES]; uint8_t buffer[DMR_PDU_THREEQUARTER_LENGTH_BYTES];
::memset(buffer, 0x00U, DMR_PDU_CONFIRMED_LENGTH_BYTES); ::memset(buffer, 0x00U, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No. buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No.
::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); // Payload Data ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); // Payload Data
uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES]; uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES];
::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
// generate CRC buffer // generate CRC buffer
uint32_t bufferLen = DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES; ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES);
uint32_t crcBitLength = 144U; for (uint32_t i = 0U; i < 7U; i++) {
for (uint32_t i = 2U; i < bufferLen; i++)
crcBuffer[i - 2U] = buffer[2U];
for (uint32_t i = 0; i < 6U; i++) {
bool b = READ_BIT(buffer, i); bool b = READ_BIT(buffer, i);
WRITE_BIT(crcBuffer, i + (crcBitLength - 15U), b); WRITE_BIT(crcBuffer, i + 128U, b);
} }
uint16_t crc = edac::CRC::createCRC9(crcBuffer, 135U); uint16_t crc = edac::CRC::createCRC9(crcBuffer, 135U);
crc = ~crc & 0x1FFU;
buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8) buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8)
buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7)
#if DEBUG_DMR_PDU_DATA #if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed PDU Data Block", buffer, DMR_PDU_CONFIRMED_LENGTH_BYTES); Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed 3/4 Rate PDU Data Block", buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
#endif #endif
m_trellis.encode34(buffer, data, true); m_trellis.encode34(buffer, data, true);
} }
else if (m_dataType == DataType::RATE_12_DATA) { else if (m_dataType == DataType::RATE_12_DATA) {
uint8_t buffer[DMR_PDU_UNCONFIRMED_LENGTH_BYTES]; uint8_t buffer[DMR_PDU_HALFRATE_LENGTH_BYTES];
::memset(buffer, 0x00U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); ::memset(buffer, 0x00U, DMR_PDU_HALFRATE_LENGTH_BYTES);
buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No. buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No.
::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_HALFRATE_DATA_LENGTH_BYTES); // Payload Data ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); // Payload Data
uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES]; uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES];
::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
// generate CRC buffer // generate CRC buffer
uint32_t bufferLen = DMR_PDU_CONFIRMED_HALFRATE_DATA_LENGTH_BYTES; ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES);
uint32_t crcBitLength = 96U; for (uint32_t i = 0U; i < 7U; i++) {
for (uint32_t i = 2U; i < bufferLen; i++)
crcBuffer[i - 2U] = buffer[2U];
for (uint32_t i = 0; i < 6U; i++) {
bool b = READ_BIT(buffer, i); bool b = READ_BIT(buffer, i);
WRITE_BIT(crcBuffer, i + (crcBitLength - 15U), b); WRITE_BIT(crcBuffer, i + 80U, b);
} }
uint16_t crc = edac::CRC::createCRC9(crcBuffer, 87U); uint16_t crc = edac::CRC::createCRC9(crcBuffer, 87U);
crc = ~crc & 0x1FFU;
buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8) buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8)
buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7)
::memcpy(buffer, m_data, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_HALFRATE_LENGTH_BYTES);
#if DEBUG_DMR_PDU_DATA #if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed 1/2 Rate PDU Data Block", buffer, DMR_PDU_HALFRATE_LENGTH_BYTES);
#endif #endif
m_bptc.encode(buffer, data); m_bptc.encode(buffer, data);
} }
else { else if (m_dataType == DataType::RATE_1_DATA) {
LogError(LOG_DMR, "DataBlock::encode(), cowardly refusing to encode confirmed full-rate (rate 1) data"); uint8_t buffer[DMR_PDU_UNCODED_LENGTH_BYTES];
return; ::memset(buffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No.
::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); // Payload Data
uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES];
::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES);
// generate CRC buffer
::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES);
for (uint32_t i = 0U; i < 7U; i++) {
bool b = READ_BIT(buffer, i);
WRITE_BIT(crcBuffer, i + 176U, b);
}
uint16_t crc = edac::CRC::createCRC9(crcBuffer, 183U);
crc = ~crc & 0x1FFU;
buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8)
buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7)
::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES);
#if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed 1 Rate PDU Data Block", buffer, DMR_PDU_UNCODED_LENGTH_BYTES);
#endif
::memcpy(data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES);
} }
} }
else if (m_DPF == DPF::UNCONFIRMED_DATA || m_DPF == DPF::RESPONSE || m_DPF == DPF::DEFINED_RAW || else if (m_DPF == DPF::UNCONFIRMED_DATA || m_DPF == DPF::RESPONSE || m_DPF == DPF::DEFINED_RAW ||
m_DPF == DPF::DEFINED_SHORT || m_DPF == DPF::UDT) { m_DPF == DPF::DEFINED_SHORT || m_DPF == DPF::UDT) {
if (m_dataType == DataType::RATE_34_DATA) { if (m_dataType == DataType::RATE_34_DATA) {
uint8_t buffer[DMR_PDU_CONFIRMED_LENGTH_BYTES]; uint8_t buffer[DMR_PDU_THREEQUARTER_LENGTH_BYTES];
::memset(buffer, 0x00U, DMR_PDU_CONFIRMED_LENGTH_BYTES); ::memset(buffer, 0x00U, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
#if DEBUG_DMR_PDU_DATA #if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, DMR_PDU_CONFIRMED_LENGTH_BYTES); Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed 3/4 Rate PDU Data Block", buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
#endif #endif
m_trellis.encode34(buffer, data, true); m_trellis.encode34(buffer, data, true);
} }
else if (m_dataType == DataType::RATE_12_DATA) { else if (m_dataType == DataType::RATE_12_DATA) {
uint8_t buffer[DMR_PDU_UNCONFIRMED_LENGTH_BYTES]; uint8_t buffer[DMR_PDU_HALFRATE_LENGTH_BYTES];
::memset(buffer, 0x00U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); ::memset(buffer, 0x00U, DMR_PDU_HALFRATE_LENGTH_BYTES);
::memcpy(buffer, m_data, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_HALFRATE_LENGTH_BYTES);
#if DEBUG_DMR_PDU_DATA #if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed 1/2 Rate PDU Data Block", buffer, DMR_PDU_HALFRATE_LENGTH_BYTES);
#endif #endif
m_bptc.encode(buffer, data); m_bptc.encode(buffer, data);
@ -283,6 +337,10 @@ void DataBlock::encode(uint8_t* data)
::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES);
::memcpy(data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES); ::memcpy(data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES);
#if DEBUG_DMR_PDU_DATA
Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed 1 Rate PDU Data Block", buffer, DMR_PDU_UNCODED_LENGTH_BYTES);
#endif
} }
} }
else { else {
@ -333,14 +391,25 @@ void DataBlock::setData(const uint8_t* buffer)
assert(buffer != nullptr); assert(buffer != nullptr);
assert(m_data != nullptr); assert(m_data != nullptr);
if (m_DPF == DPF::CONFIRMED_DATA) {
if (m_dataType == DataType::RATE_34_DATA)
::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES);
else if (m_dataType == DataType::RATE_12_DATA)
::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES);
else if (m_dataType == DataType::RATE_1_DATA)
::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES);
else
LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType);
} else {
if (m_dataType == DataType::RATE_34_DATA) if (m_dataType == DataType::RATE_34_DATA)
::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); ::memcpy(m_data, buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
else if (m_dataType == DataType::RATE_12_DATA) else if (m_dataType == DataType::RATE_12_DATA)
::memcpy(m_data, buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(m_data, buffer, DMR_PDU_HALFRATE_LENGTH_BYTES);
else if (m_dataType == DataType::RATE_1_DATA) else if (m_dataType == DataType::RATE_1_DATA)
::memcpy(m_data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES); ::memcpy(m_data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES);
else else
LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType);
}
} }
/* Gets the raw data stored in the data block. */ /* Gets the raw data stored in the data block. */
@ -350,12 +419,27 @@ uint32_t DataBlock::getData(uint8_t* buffer) const
assert(buffer != nullptr); assert(buffer != nullptr);
assert(m_data != nullptr); assert(m_data != nullptr);
if (m_DPF == DPF::CONFIRMED_DATA) {
if (m_dataType == DataType::RATE_34_DATA) {
::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES);
return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES;
} else if (m_dataType == DataType::RATE_12_DATA) {
::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES);
return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES;
} else if (m_dataType == DataType::RATE_1_DATA) {
::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES);
return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES;
} else {
LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType);
return 0U;
}
} else {
if (m_dataType == DataType::RATE_34_DATA) { if (m_dataType == DataType::RATE_34_DATA) {
::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_THREEQUARTER_LENGTH_BYTES);
return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES; return DMR_PDU_THREEQUARTER_LENGTH_BYTES;
} else if (m_dataType == DataType::RATE_12_DATA) { } else if (m_dataType == DataType::RATE_12_DATA) {
::memcpy(buffer, m_data, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_HALFRATE_LENGTH_BYTES);
return DMR_PDU_UNCONFIRMED_LENGTH_BYTES; return DMR_PDU_HALFRATE_LENGTH_BYTES;
} else if (m_dataType == DataType::RATE_1_DATA) { } else if (m_dataType == DataType::RATE_1_DATA) {
::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES); ::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES);
return DMR_PDU_UNCODED_LENGTH_BYTES; return DMR_PDU_UNCODED_LENGTH_BYTES;
@ -363,4 +447,24 @@ uint32_t DataBlock::getData(uint8_t* buffer) const
LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType);
return 0U; return 0U;
} }
}
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Internal helper to copy the the class. */
void DataBlock::copy(const DataBlock& data)
{
m_serialNo = data.m_serialNo;
m_lastBlock = data.m_lastBlock;
m_dataType = data.m_dataType;
m_DPF = data.m_DPF;
if (m_data != nullptr && data.m_data != nullptr) {
::memcpy(m_data, data.m_data, DMR_PDU_UNCODED_LENGTH_BYTES);
}
} }

@ -38,6 +38,11 @@ namespace dmr
*/ */
class HOST_SW_API DataBlock { class HOST_SW_API DataBlock {
public: public:
/**
* @brief Initializes a copy instance of the DataBlock class.
* @param data Instance of DataBlock class to copy from.
*/
DataBlock(const DataBlock& data);
/** /**
* @brief Initializes a new instance of the DataBlock class. * @brief Initializes a new instance of the DataBlock class.
*/ */
@ -117,6 +122,11 @@ namespace dmr
defines::DPF::E m_DPF; defines::DPF::E m_DPF;
uint8_t* m_data; uint8_t* m_data;
/**
* @brief Internal helper to copy the class.
*/
void copy(const DataBlock& data);
}; };
} // namespace data } // namespace data
} // namespace dmr } // namespace dmr

@ -33,6 +33,13 @@ const uint8_t UDTF_NMEA = 0x05U;
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Initializes a copy instance of the DataHeader class. */
DataHeader::DataHeader(const DataHeader& data) : DataHeader()
{
copy(data);
}
/* Initializes a new instance of the DataHeader class. */ /* Initializes a new instance of the DataHeader class. */
DataHeader::DataHeader() : DataHeader::DataHeader() :
@ -68,7 +75,10 @@ DataHeader::DataHeader() :
DataHeader::~DataHeader() DataHeader::~DataHeader()
{ {
if (m_data != nullptr) {
delete[] m_data; delete[] m_data;
m_data = nullptr;
}
} }
/* Equals operator. */ /* Equals operator. */
@ -404,33 +414,65 @@ void DataHeader::reset()
/* Gets the total length in bytes of enclosed packet data. */ /* Gets the total length in bytes of enclosed packet data. */
uint32_t DataHeader::getPacketLength() const uint32_t DataHeader::getPacketLength(DataType::E dataType) const
{ {
if (m_DPF == DPF::RESPONSE) { if (m_DPF == DPF::RESPONSE) {
return 0U; // responses have no packet length as they are header only return 0U; // responses have no packet length as they are header only
} }
if (m_DPF == DPF::CONFIRMED_DATA) { if (m_DPF == DPF::CONFIRMED_DATA) {
return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; if (dataType == DataType::RATE_34_DATA) {
return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength;
}
else if (dataType == DataType::RATE_12_DATA) {
return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength;
} }
else { else {
return DMR_PDU_UNCONFIRMED_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength;
}
}
else {
if (dataType == DataType::RATE_34_DATA) {
return DMR_PDU_THREEQUARTER_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength;
}
else if (dataType == DataType::RATE_12_DATA) {
return DMR_PDU_HALFRATE_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength;
}
else {
return DMR_PDU_UNCODED_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength;
}
} }
} }
/* Gets the total length in bytes of entire PDU. */ /* Gets the total length in bytes of entire PDU. */
uint32_t DataHeader::getPDULength() const uint32_t DataHeader::getPDULength(DataType::E dataType) const
{ {
if (m_DPF == DPF::RESPONSE) { if (m_DPF == DPF::RESPONSE) {
return 0U; // responses have no packet length as they are header only return 0U; // responses have no packet length as they are header only
} }
if (m_DPF == DPF::CONFIRMED_DATA) { if (m_DPF == DPF::CONFIRMED_DATA) {
return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES * m_blocksToFollow; if (dataType == DataType::RATE_34_DATA) {
return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES * m_blocksToFollow;
}
else if (dataType == DataType::RATE_12_DATA) {
return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES * m_blocksToFollow;
}
else {
return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES * m_blocksToFollow;
}
}
else {
if (dataType == DataType::RATE_34_DATA) {
return DMR_PDU_THREEQUARTER_LENGTH_BYTES * m_blocksToFollow;
}
else if (dataType == DataType::RATE_12_DATA) {
return DMR_PDU_HALFRATE_LENGTH_BYTES * m_blocksToFollow;
} }
else { else {
return DMR_PDU_UNCONFIRMED_LENGTH_BYTES * m_blocksToFollow; return DMR_PDU_UNCODED_LENGTH_BYTES * m_blocksToFollow;
}
} }
} }
@ -447,10 +489,32 @@ uint32_t DataHeader::getData(uint8_t* buffer) const
/* Helper to calculate the number of blocks to follow and padding length for a PDU. */ /* Helper to calculate the number of blocks to follow and padding length for a PDU. */
void DataHeader::calculateLength(uint32_t packetLength) void DataHeader::calculateLength(DataType::E dataType, uint32_t packetLength)
{ {
uint32_t len = packetLength + 4U; // packet length + CRC32 uint32_t len = packetLength + 4U; // packet length + CRC32
uint32_t blockLen = (m_DPF == DPF::CONFIRMED_DATA) ? DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES : DMR_PDU_UNCONFIRMED_LENGTH_BYTES; uint32_t blockLen = DMR_PDU_UNCODED_LENGTH_BYTES;
if (m_DPF == DPF::CONFIRMED_DATA) {
if (dataType == DataType::RATE_34_DATA) {
blockLen = DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES;
}
else if (dataType == DataType::RATE_12_DATA) {
blockLen = DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES;
}
else {
blockLen = DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES;
}
}
else {
if (dataType == DataType::RATE_34_DATA) {
blockLen = DMR_PDU_THREEQUARTER_LENGTH_BYTES;
}
else if (dataType == DataType::RATE_12_DATA) {
blockLen = DMR_PDU_HALFRATE_LENGTH_BYTES;
}
else {
blockLen = DMR_PDU_UNCODED_LENGTH_BYTES;
}
}
if (len > blockLen) { if (len > blockLen) {
m_padLength = blockLen - (len % blockLen); m_padLength = blockLen - (len % blockLen);
@ -463,13 +527,74 @@ void DataHeader::calculateLength(uint32_t packetLength)
/* Helper to determine the pad length for a given packet length. */ /* Helper to determine the pad length for a given packet length. */
uint32_t DataHeader::calculatePadLength(DPF::E dpf, uint32_t packetLength) uint32_t DataHeader::calculatePadLength(DPF::E dpf, DataType::E dataType, uint32_t packetLength)
{ {
uint32_t len = packetLength + 4U; // packet length + CRC32 uint32_t len = packetLength + 4U; // packet length + CRC32
if (dpf == DPF::CONFIRMED_DATA) { if (dpf == DPF::CONFIRMED_DATA) {
return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); if (dataType == DataType::RATE_34_DATA) {
return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES);
}
else if (dataType == DataType::RATE_12_DATA) {
return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES);
} }
else { else {
return DMR_PDU_UNCONFIRMED_LENGTH_BYTES - (len % DMR_PDU_UNCONFIRMED_LENGTH_BYTES); return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES);
}
}
else {
if (dataType == DataType::RATE_34_DATA) {
return DMR_PDU_THREEQUARTER_LENGTH_BYTES - (len % DMR_PDU_THREEQUARTER_LENGTH_BYTES);
}
else if (dataType == DataType::RATE_12_DATA) {
return DMR_PDU_HALFRATE_LENGTH_BYTES - (len % DMR_PDU_HALFRATE_LENGTH_BYTES);
}
else {
return DMR_PDU_UNCODED_LENGTH_BYTES - (len % DMR_PDU_UNCODED_LENGTH_BYTES);
}
}
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Internal helper to copy the the class. */
void DataHeader::copy(const DataHeader& data)
{
m_GI = data.m_GI;
m_A = data.m_A;
m_DPF = data.m_DPF;
m_sap = data.m_sap;
m_fsn = data.m_fsn;
m_Ns = data.m_Ns;
m_blocksToFollow = data.m_blocksToFollow;
m_padLength = data.m_padLength;
m_F = data.m_F;
m_S = data.m_S;
m_dataFormat = data.m_dataFormat;
m_srcId = data.m_srcId;
m_dstId = data.m_dstId;
m_rspClass = data.m_rspClass;
m_rspType = data.m_rspType;
m_rspStatus = data.m_rspStatus;
m_srcPort = data.m_srcPort;
m_dstPort = data.m_dstPort;
m_SF = data.m_SF;
m_PF = data.m_PF;
m_UDTO = data.m_UDTO;
if (m_data != nullptr && data.m_data != nullptr) {
::memcpy(m_data, data.m_data, DMR_LC_HEADER_LENGTH_BYTES);
} }
} }

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX
* Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -38,6 +38,11 @@ namespace dmr
*/ */
class HOST_SW_API DataHeader { class HOST_SW_API DataHeader {
public: public:
/**
* @brief Initializes a copy instance of the DataHeader class.
* @param data Instance of DataHeader class to copy from.
*/
DataHeader(const DataHeader& data);
/** /**
* @brief Initializes a new instance of the DataHeader class. * @brief Initializes a new instance of the DataHeader class.
*/ */
@ -72,14 +77,16 @@ namespace dmr
/** /**
* @brief Gets the total length in bytes of enclosed packet data. * @brief Gets the total length in bytes of enclosed packet data.
* @param dataType DMR Data Type.
* @returns uint32_t Total length of packet in bytes. * @returns uint32_t Total length of packet in bytes.
*/ */
uint32_t getPacketLength() const; uint32_t getPacketLength(defines::DataType::E dataType) const;
/** /**
* @brief Gets the total length in bytes of entire PDU. * @brief Gets the total length in bytes of entire PDU.
* @param dataType DMR Data Type.
* @returns uint32_t Total length of PDU in bytes. * @returns uint32_t Total length of PDU in bytes.
*/ */
uint32_t getPDULength() const; uint32_t getPDULength(defines::DataType::E dataType) const;
/** /**
* @brief Gets the raw header data. * @brief Gets the raw header data.
@ -90,17 +97,19 @@ namespace dmr
/** /**
* @brief Helper to calculate the number of blocks to follow and padding length for a PDU. * @brief Helper to calculate the number of blocks to follow and padding length for a PDU.
* @param dataType DMR Data Type.
* @param packetLength Length of PDU. * @param packetLength Length of PDU.
*/ */
void calculateLength(uint32_t packetLength); void calculateLength(defines::DataType::E dataType, uint32_t packetLength);
/** /**
* @brief Helper to determine the pad length for a given packet length. * @brief Helper to determine the pad length for a given packet length.
* @param dpf PDU format type. * @param dpf PDU format type.
* @param dataType DMR Data Type.
* @param packetLength Length of PDU. * @param packetLength Length of PDU.
* @returns uint32_t Number of pad bytes. * @returns uint32_t Number of pad bytes.
*/ */
static uint32_t calculatePadLength(defines::DPF::E dpf, uint32_t packetLength); static uint32_t calculatePadLength(defines::DPF::E dpf, defines::DataType::E dataType, uint32_t packetLength);
public: public:
/** /**
@ -190,6 +199,11 @@ namespace dmr
bool m_SF; bool m_SF;
bool m_PF; bool m_PF;
uint8_t m_UDTO; uint8_t m_UDTO;
/**
* @brief Internal helper to copy the class.
*/
void copy(const DataHeader& data);
}; };
} // namespace data } // namespace data
} // namespace dmr } // namespace dmr

@ -44,8 +44,15 @@ EmbeddedData::EmbeddedData() :
EmbeddedData::~EmbeddedData() EmbeddedData::~EmbeddedData()
{ {
if (m_raw != nullptr) {
delete[] m_raw; delete[] m_raw;
m_raw = nullptr;
}
if (m_data != nullptr) {
delete[] m_data; delete[] m_data;
m_data = nullptr;
}
} }
/* Add LC data (which may consist of 4 blocks) to the data store. */ /* Add LC data (which may consist of 4 blocks) to the data store. */

@ -64,7 +64,10 @@ NetData::NetData() :
NetData::~NetData() NetData::~NetData()
{ {
if (m_data != nullptr) {
delete[] m_data; delete[] m_data;
m_data = nullptr;
}
} }
/* Equals operator. */ /* Equals operator. */

@ -25,9 +25,9 @@ using namespace dmr::lc;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
bool CSBK::m_verbose = false; bool CSBK::s_verbose = false;
SiteData CSBK::m_siteData = SiteData(); SiteData CSBK::s_siteData = SiteData();
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
@ -271,7 +271,7 @@ bool CSBK::decode(const uint8_t* data, uint8_t* payload)
break; break;
} }
if (m_verbose) { if (s_verbose) {
Utils::dump(2U, "CSBK::decode(), Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); Utils::dump(2U, "CSBK::decode(), Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES);
} }
@ -339,7 +339,7 @@ void CSBK::encode(uint8_t* data, const uint8_t* payload)
break; break;
} }
if (m_verbose) { if (s_verbose) {
Utils::dump(2U, "CSBK::encode(), Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); Utils::dump(2U, "CSBK::encode(), Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES);
} }

@ -4,10 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2015,2016 Jonathan Naylor, G4KLX * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX
* Copyright (C) 2019-2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2019-2024 Bryan Biedenkapp, N2PLL
* *
@ -92,24 +88,24 @@ namespace dmr
* @brief Gets the flag indicating verbose log output. * @brief Gets the flag indicating verbose log output.
* @returns bool True, if the CSBK is verbose logging, otherwise false. * @returns bool True, if the CSBK is verbose logging, otherwise false.
*/ */
static bool getVerbose() { return m_verbose; } static bool getVerbose() { return s_verbose; }
/** /**
* @brief Sets the flag indicating verbose log output. * @brief Sets the flag indicating verbose log output.
* @param verbose Flag indicating verbose log output. * @param verbose Flag indicating verbose log output.
*/ */
static void setVerbose(bool verbose) { m_verbose = verbose; } static void setVerbose(bool verbose) { s_verbose = verbose; }
/** @name Local Site data */ /** @name Local Site data */
/** /**
* @brief Gets the local site data. * @brief Gets the local site data.
* @returns SiteData Currently set site data for the CSBK class. * @returns SiteData Currently set site data for the CSBK class.
*/ */
static SiteData getSiteData() { return m_siteData; } static SiteData getSiteData() { return s_siteData; }
/** /**
* @brief Sets the local site data. * @brief Sets the local site data.
* @param siteData Site data to set for the CSBK class. * @param siteData Site data to set for the CSBK class.
*/ */
static void setSiteData(SiteData siteData) { m_siteData = siteData; } static void setSiteData(SiteData siteData) { s_siteData = siteData; }
/** @} */ /** @} */
public: public:
@ -230,10 +226,10 @@ namespace dmr
/** @} */ /** @} */
protected: protected:
static bool m_verbose; static bool s_verbose;
// Local Site data // Local Site data
static SiteData m_siteData; static SiteData s_siteData;
/** /**
* @brief Internal helper to convert payload bytes to a 64-bit long value. * @brief Internal helper to convert payload bytes to a 64-bit long value.

@ -4,10 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2015,2016 Jonathan Naylor, G4KLX * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX
* Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL
* *

@ -4,9 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL
* *
*/ */

@ -4,9 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2021,2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2021,2025 Bryan Biedenkapp, N2PLL
* *
*/ */

@ -4,9 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
* *
*/ */

@ -63,7 +63,7 @@ void CSBK_ACK_RSP::encode(uint8_t* data)
csbkValue = 0U; csbkValue = 0U;
} else { } else {
csbkValue = (m_GI ? 0x40U : 0x00U) + // Source Type csbkValue = (m_GI ? 0x40U : 0x00U) + // Source Type
(m_siteData.siteId() & 0x3FU); // Net + Site LSB (s_siteData.siteId() & 0x3FU); // Net + Site LSB
} }
csbkValue = (csbkValue << 7) + (m_response & 0x7FU); // Response Information csbkValue = (csbkValue << 7) + (m_response & 0x7FU); // Response Information
csbkValue = (csbkValue << 8) + (m_reason & 0xFFU); // Reason Code csbkValue = (csbkValue << 8) + (m_reason & 0xFFU); // Reason Code

@ -55,13 +55,13 @@ void CSBK_ALOHA::encode(uint8_t* data)
csbkValue = (csbkValue << 1) + ((m_siteTSSync) ? 1U : 0U); // Site Time Slot Synchronization csbkValue = (csbkValue << 1) + ((m_siteTSSync) ? 1U : 0U); // Site Time Slot Synchronization
csbkValue = (csbkValue << 3) + DMR_ALOHA_VER_151; // DMR Spec. Version (1.5.1) csbkValue = (csbkValue << 3) + DMR_ALOHA_VER_151; // DMR Spec. Version (1.5.1)
csbkValue = (csbkValue << 1) + ((m_siteOffsetTiming) ? 1U : 0U); // Site Timing: Aligned or Offset csbkValue = (csbkValue << 1) + ((m_siteOffsetTiming) ? 1U : 0U); // Site Timing: Aligned or Offset
csbkValue = (csbkValue << 1) + ((m_siteData.netActive()) ? 1U : 0U); // Site Networked csbkValue = (csbkValue << 1) + ((s_siteData.netActive()) ? 1U : 0U); // Site Networked
csbkValue = (csbkValue << 5) + (m_alohaMask & 0x1FU); // MS Mask csbkValue = (csbkValue << 5) + (m_alohaMask & 0x1FU); // MS Mask
csbkValue = (csbkValue << 2) + 0U; // Service Function csbkValue = (csbkValue << 2) + 0U; // Service Function
csbkValue = (csbkValue << 4) + (m_nRandWait & 0x0FU); // Random Access Wait csbkValue = (csbkValue << 4) + (m_nRandWait & 0x0FU); // Random Access Wait
csbkValue = (csbkValue << 1) + ((m_siteData.requireReg()) ? 1U : 0U); // Require Registration csbkValue = (csbkValue << 1) + ((s_siteData.requireReg()) ? 1U : 0U); // Require Registration
csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number
csbkValue = (csbkValue << 16) + m_siteData.systemIdentity(); // Site Identity csbkValue = (csbkValue << 16) + s_siteData.systemIdentity(); // Site Identity
csbkValue = (csbkValue << 24) + m_srcId; // Source Radio Address csbkValue = (csbkValue << 24) + m_srcId; // Source Radio Address
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue); std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);

@ -142,11 +142,11 @@ void CSBK_BROADCAST::encode(uint8_t* data)
break; break;
case BroadcastAnncType::SITE_PARMS: case BroadcastAnncType::SITE_PARMS:
// Broadcast Params 1 // Broadcast Params 1
csbkValue = (csbkValue << 14) + m_siteData.systemIdentity(true); // Site Identity (Broadcast Params 1) csbkValue = (csbkValue << 14) + s_siteData.systemIdentity(true); // Site Identity (Broadcast Params 1)
csbkValue = (csbkValue << 1) + ((m_siteData.requireReg()) ? 1U : 0U); // Require Registration csbkValue = (csbkValue << 1) + ((s_siteData.requireReg()) ? 1U : 0U); // Require Registration
csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number
csbkValue = (csbkValue << 16) + m_siteData.systemIdentity(); // Site Identity csbkValue = (csbkValue << 16) + s_siteData.systemIdentity(); // Site Identity
// Broadcast Params 2 // Broadcast Params 2
csbkValue = (csbkValue << 1) + 0U; // Roaming TG Subscription/Attach csbkValue = (csbkValue << 1) + 0U; // Roaming TG Subscription/Attach

@ -4,9 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL * Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* *
*/ */

@ -320,6 +320,36 @@ bool CRC::checkCRC32(const uint8_t *in, uint32_t length)
return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U] && crc8[2U] == in[length - 3U] && crc8[3U] == in[length - 4U]; return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U] && crc8[2U] == in[length - 3U] && crc8[3U] == in[length - 4U];
} }
/* Check 32-bit CRC (inverted). */
bool CRC::checkInvertedCRC32(const uint8_t *in, uint32_t length)
{
assert(in != nullptr);
assert(length > 4U);
union {
uint32_t crc32;
uint8_t crc8[4U];
};
uint32_t i = 0;
crc32 = 0x00000000U;
for (uint32_t j = (length - 4U); j-- > 0; i++) {
uint32_t idx = ((crc32 >> 24) ^ in[i]) & 0xFFU;
crc32 = (CRC32_TABLE[idx] ^ (crc32 << 8)) & 0xFFFFFFFFU;
}
crc32 &= 0xFFFFFFFFU;
#if DEBUG_CRC_CHECK
uint32_t inCrc = (in[length - 4U] << 24) | (in[length - 3U] << 16) | (in[length - 2U] << 8) | (in[length - 1U] << 0);
LogDebugEx(LOG_HOST, "CRC::checkInvertedCRC32()", "crc = $%08X, in = $%08X, len = %u", crc32, inCrc, length);
#endif
return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U] && crc8[2U] == in[length - 3U] && crc8[3U] == in[length - 4U];
}
/* Encode 32-bit CRC. */ /* Encode 32-bit CRC. */
void CRC::addCRC32(uint8_t* in, uint32_t length) void CRC::addCRC32(uint8_t* in, uint32_t length)
@ -353,6 +383,38 @@ void CRC::addCRC32(uint8_t* in, uint32_t length)
in[length - 4U] = crc8[3U]; in[length - 4U] = crc8[3U];
} }
/* Encode 32-bit CRC (inverted). */
void CRC::addInvertedCRC32(uint8_t* in, uint32_t length)
{
assert(in != nullptr);
assert(length > 4U);
union {
uint32_t crc32;
uint8_t crc8[4U];
};
uint32_t i = 0;
crc32 = 0x00000000U;
for (uint32_t j = (length - 4U); j-- > 0; i++) {
uint32_t idx = ((crc32 >> 24) ^ in[i]) & 0xFFU;
crc32 = (CRC32_TABLE[idx] ^ (crc32 << 8)) & 0xFFFFFFFFU;
}
crc32 &= 0xFFFFFFFFU;
#if DEBUG_CRC_ADD
LogDebugEx(LOG_HOST, "CRC::addInvertedCRC32()", "crc = $%08X, len = %u", crc32, length);
#endif
in[length - 1U] = crc8[0U];
in[length - 2U] = crc8[1U];
in[length - 3U] = crc8[2U];
in[length - 4U] = crc8[3U];
}
/* Generate 8-bit CRC. */ /* Generate 8-bit CRC. */
uint8_t CRC::crc8(const uint8_t *in, uint32_t length) uint8_t CRC::crc8(const uint8_t *in, uint32_t length)

@ -92,12 +92,25 @@ namespace edac
* @returns bool True, if CRC is valid, otherwise false. * @returns bool True, if CRC is valid, otherwise false.
*/ */
static bool checkCRC32(const uint8_t* in, uint32_t length); static bool checkCRC32(const uint8_t* in, uint32_t length);
/**
* @brief Check 32-bit CRC (inverted).
* @param[in] in Input byte array.
* @param length Length of byte array.
* @returns bool True, if CRC is valid, otherwise false.
*/
static bool checkInvertedCRC32(const uint8_t* in, uint32_t length);
/** /**
* @brief Encode 32-bit CRC. * @brief Encode 32-bit CRC.
* @param[out] in Input byte array. * @param[out] in Input byte array.
* @param length Length of byte array. * @param length Length of byte array.
*/ */
static void addCRC32(uint8_t* in, uint32_t length); static void addCRC32(uint8_t* in, uint32_t length);
/**
* @brief Encode 32-bit CRC (inverted).
* @param[out] in Input byte array.
* @param length Length of byte array.
*/
static void addInvertedCRC32(uint8_t* in, uint32_t length);
/** /**
* @brief Generate 8-bit CRC. * @brief Generate 8-bit CRC.

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2016 Jonathan Naylor, G4KLX * Copyright (C) 2016 Jonathan Naylor, G4KLX
* Copyright (C) 2017,2023 Bryan Biedenkapp, N2PLL * Copyright (C) 2017,2023,2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "Defines.h" #include "Defines.h"
@ -77,6 +77,149 @@ const uint8_t ENCODE_MATRIX_362017[20U][36U] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 002, 001, 053, 074, 002, 014, 052, 074, 012, 057, 024, 063, 015, 042, 052, 033 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 002, 001, 053, 074, 002, 014, 052, 074, 012, 057, 024, 063, 015, 042, 052, 033 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 034, 035, 002, 023, 021, 027, 022, 033, 064, 042, 005, 073, 051, 046, 073, 060 } }; { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 034, 035, 002, 023, 021, 027, 022, 033, 064, 042, 005, 073, 051, 046, 073, 060 } };
const uint8_t ENCODE_MATRIX_633529[35U][63U] = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 013, 014, 023, 076, 015, 077, 050, 062, 015, 014, 012, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 051, 061, 032, 006 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 072, 043, 045, 021, 020, 011, 012, 002, 034, 045, 060, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 043, 011, 056, 016 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 041, 061, 024, 036, 024, 045, 063, 072, 007, 027, 012, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 050, 001, 003, 012 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 015, 032, 011, 022, 057, 030, 071, 016, 013, 074, 020, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 016, 013, 040, 077 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 063, 005, 071, 034, 045, 005, 050, 044, 071, 003, 060, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 036, 021, 046, 044 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 003, 011, 061, 045, 002, 035, 037, 016, 072, 003, 044, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 043, 027, 055, 073 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 037, 073, 045, 031, 046, 021, 013, 017, 015, 002, 047, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 071, 075, 021, 061 },
{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 034, 057, 013, 053, 071, 033, 046, 075, 016, 041, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 056, 004, 032, 061 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 021, 010, 001, 071, 064, 063, 066, 024, 076, 055, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 037, 023, 043, 072 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 046, 056, 036, 017, 025, 012, 021, 070, 040, 020, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 076, 060, 017, 071 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 064, 054, 071, 007, 041, 020, 075, 010, 030, 020, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 001, 001, 002, 037 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 034, 013, 054, 030, 044, 054, 055, 046, 040, 012, 033, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 036, 026, 073, 003 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 035, 010, 076, 055, 017, 046, 027, 070, 061, 064, 024, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 044, 016, 070, 061 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 036, 034, 020, 037, 020, 054, 072, 012, 062, 027, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 015, 031, 051, 030 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 064, 074, 024, 056, 017, 001, 002, 004, 054, 007, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 030, 013, 046, 072 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 031, 013, 052, 032, 002, 061, 043, 014, 060, 002, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047, 045, 067, 027, 074 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 056, 010, 017, 037, 012, 062, 011, 071, 003, 020, 042, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057, 020, 052, 064, 031 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 046, 004, 071, 071, 054, 045, 013, 025, 012, 051, 057, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070, 017, 047, 017, 041 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 044, 040, 054, 046, 036, 022, 061, 022, 062, 014, 054, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066, 015, 066, 052, 014 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 067, 067, 061, 050, 071, 026, 073, 046, 015, 041, 067, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062, 023, 016, 010, 002 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 026, 057, 021, 016, 062, 004, 005, 034, 074, 025, 065, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051, 043, 062, 072, 004 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 046, 040, 054, 072, 013, 042, 010, 050, 014, 075, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077, 013, 042, 031, 042 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 076, 073, 076, 034, 006, 044, 056, 070, 072, 027, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027, 062, 042, 001, 020 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 066, 074, 002, 012, 053, 075, 030, 020, 073, 075, 034, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035, 031, 066, 010, 042 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 044, 041, 034, 072, 027, 022, 024, 040, 051, 046, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020, 020, 060, 025, 001 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 013, 065, 067, 037, 021, 005, 077, 040, 031, 054, 043, 041, 013, 030, 013, 037, 062, 045, 061, 053, 005, 063, 013, 063, 071, 041, 052, 023 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 053, 032, 074, 007, 035, 062, 040, 036, 042, 010, 024, 031, 067, 054, 021, 001, 072, 072, 073, 006, 061, 017, 020, 067, 005, 055, 045, 003 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 035, 077, 057, 075, 020, 037, 011, 065, 011, 066, 026, 035, 036, 033, 031, 031, 072, 045, 042, 051, 060, 071, 015, 040, 017, 025, 003, 057 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 005, 020, 040, 013, 037, 033, 061, 040, 027, 004, 034, 036, 044, 022, 004, 065, 067, 064, 022, 062, 031, 034, 062, 040, 041, 024, 022, 044 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 003, 077, 044, 074, 025, 047, 001, 027, 076, 055, 043, 045, 011, 040, 046, 041, 057, 014, 030, 043, 042, 074, 044, 051, 036, 050, 050, 017 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 052, 004, 033, 041, 064, 037, 065, 003, 037, 071, 010, 016, 076, 023, 004, 016, 063, 017, 067, 001, 010, 012, 066, 021, 064, 015, 070, 012 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 015, 021, 074, 035, 020, 070, 003, 010, 062, 044, 076, 076, 034, 023, 053, 064, 022, 062, 034, 030, 063, 070, 006, 020, 007, 027, 054, 004 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 054, 075, 036, 001, 051, 051, 036, 016, 074, 002, 014, 042, 013, 016, 034, 012, 022, 007, 022, 044, 023, 022, 001, 005, 062, 006, 074, 064 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 065, 023, 065, 063, 012, 060, 055, 014, 005, 003, 003, 006, 044, 004, 006, 073, 016, 076, 055, 006, 030, 064, 013, 026, 065, 077, 020, 002 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 026, 055, 065, 012, 051, 067, 043, 012, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007, 024, 013, 034 } };
const uint8_t ENCODE_MATRIX_523023[30U][52U] = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 023, 076, 015, 077, 050, 062, 015, 014, 012, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 045, 021, 020, 011, 012, 002, 034, 045, 060, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 036, 024, 045, 063, 072, 007, 027, 012, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 022, 057, 030, 071, 016, 013, 074, 020, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 034, 045, 005, 050, 044, 071, 003, 060, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 061, 045, 002, 035, 037, 016, 072, 003, 044, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 045, 031, 046, 021, 013, 017, 015, 002, 047, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025 },
{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 057, 013, 053, 071, 033, 046, 075, 016, 041, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 010, 001, 071, 064, 063, 066, 024, 076, 055, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 056, 036, 017, 025, 012, 021, 070, 040, 020, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 071, 007, 041, 020, 075, 010, 030, 020, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 030, 044, 054, 055, 046, 040, 012, 033, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 076, 055, 017, 046, 027, 070, 061, 064, 024, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 034, 020, 037, 020, 054, 072, 012, 062, 027, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 024, 056, 017, 001, 002, 004, 054, 007, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 013, 052, 032, 002, 061, 043, 014, 060, 002, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 017, 037, 012, 062, 011, 071, 003, 020, 042, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 071, 054, 045, 013, 025, 012, 051, 057, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 046, 036, 022, 061, 022, 062, 014, 054, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 061, 050, 071, 026, 073, 046, 015, 041, 067, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 021, 016, 062, 004, 005, 034, 074, 025, 065, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 040, 054, 072, 013, 042, 010, 050, 014, 075, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 073, 076, 034, 006, 044, 056, 070, 072, 027, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 002, 012, 053, 075, 030, 020, 073, 075, 034, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 041, 034, 072, 027, 022, 024, 040, 051, 046, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 067, 037, 021, 005, 077, 040, 031, 054, 043, 041, 013, 030, 013, 037, 062, 045, 061, 053, 005, 063, 013, 063 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 074, 007, 035, 062, 040, 036, 042, 010, 024, 031, 067, 054, 021, 001, 072, 072, 073, 006, 061, 017, 020, 067 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 057, 075, 020, 037, 011, 065, 011, 066, 026, 035, 036, 033, 031, 031, 072, 045, 042, 051, 060, 071, 015, 040 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 040, 013, 037, 033, 061, 040, 027, 004, 034, 036, 044, 022, 004, 065, 067, 064, 022, 062, 031, 034, 062, 040 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 044, 074, 025, 047, 001, 027, 076, 055, 043, 045, 011, 040, 046, 041, 057, 014, 030, 043, 042, 074, 044, 051 } };
const uint8_t ENCODE_MATRIX_462621[26U][46U] = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 043, 012, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 014, 012, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 021, 020, 011, 012, 002 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 060, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 036, 024, 045, 063, 072, 007 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 012, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 022, 057, 030, 071, 016, 013 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 034, 045, 005, 050, 044, 071 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 060, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 045, 002, 035, 037, 016, 072 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 044, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 031, 046, 021, 013, 017, 015 },
{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 047, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 013, 053, 071, 033, 046, 075 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 041, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 001, 071, 064, 063, 066, 024 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 036, 017, 025, 012, 021, 070 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 071, 007, 041, 020, 075, 010 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 030, 044, 054, 055, 046, 040 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 033, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 055, 017, 046, 027, 070, 061 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 020, 037, 020, 054, 072, 012 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 027, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 024, 056, 017, 001, 002, 004 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 007, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 052, 032, 002, 061, 043, 014 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 002, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047, 037, 012, 062, 011, 071, 003 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 042, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057, 071, 054, 045, 013, 025, 012 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 057, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070, 046, 036, 022, 061, 022, 062 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 054, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066, 050, 071, 026, 073, 046, 015 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 067, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062, 016, 062, 004, 005, 034, 074 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 065, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051, 054, 072, 013, 042, 010, 050 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 075, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077, 076, 034, 006, 044, 056, 070 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 027, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027, 012, 053, 075, 030, 020, 073 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 034, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035, 034, 072, 027, 022, 024, 040 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 046, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020, 037, 021, 005, 077, 040, 031 } };
const uint8_t ENCODE_MATRIX_452620[26U][45U] = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 012, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007, 024 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 021, 020, 011, 012, 002, 034 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 036, 024, 045, 063, 072, 007 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 022, 057, 030, 071, 016, 013 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 034, 045, 005, 050, 044, 071 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 045, 002, 035, 037, 016, 072 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 031, 046, 021, 013, 017, 015 },
{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 013, 053, 071, 033, 046, 075 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 001, 071, 064, 063, 066, 024 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 036, 017, 025, 012, 021, 070 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 071, 007, 041, 020, 075, 010 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 030, 044, 054, 055, 046, 040 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 055, 017, 046, 027, 070, 061 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 020, 037, 020, 054, 072, 012 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 024, 056, 017, 001, 002, 004 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 052, 032, 002, 061, 043, 014 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047, 037, 012, 062, 011, 071, 003 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057, 071, 054, 045, 013, 025, 012 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070, 046, 036, 022, 061, 022, 062 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066, 050, 071, 026, 073, 046, 015 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062, 016, 062, 004, 005, 034, 074 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051, 054, 072, 013, 042, 010, 050 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077, 076, 034, 006, 044, 056, 070 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027, 012, 053, 075, 030, 020, 073 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035, 034, 072, 027, 022, 024, 040 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020, 037, 021, 005, 077, 040, 031 } };
const uint8_t ENCODE_MATRIX_441629[16U][44U] = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007, 024, 013, 034, 045, 060, 030, 011, 047, 014, 003, 014 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 021, 020, 011, 012, 002, 034, 045, 060, 030, 011, 047, 014, 003, 014, 026, 004 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 036, 024, 045, 063, 072, 007, 027, 012, 032, 077, 066, 020, 035, 071, 030, 045 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 022, 057, 030, 071, 016, 013, 074, 020, 074, 010, 022, 016, 040, 001, 070, 013 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 034, 045, 005, 050, 044, 071, 003, 060, 053, 024, 017, 061, 040, 020, 030, 011 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 045, 002, 035, 037, 016, 072, 003, 044, 011, 074, 020, 073, 024, 072, 053, 064 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 031, 046, 021, 013, 017, 015, 002, 047, 003, 024, 051, 074, 064, 002, 066, 072 },
{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 013, 053, 071, 033, 046, 075, 016, 041, 066, 014, 054, 075, 003, 076, 017, 064 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 001, 071, 064, 063, 066, 024, 076, 055, 060, 071, 064, 070, 002, 011, 063, 015 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 036, 017, 025, 012, 021, 070, 040, 020, 015, 021, 011, 013, 016, 074, 061, 052 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 071, 007, 041, 020, 075, 010, 030, 020, 071, 053, 015, 003, 065, 013, 033, 060 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 030, 044, 054, 055, 046, 040, 012, 033, 016, 063, 072, 025, 051, 071, 074, 046 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 055, 017, 046, 027, 070, 061, 064, 024, 022, 011, 037, 017, 035, 022, 046, 044 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 020, 037, 020, 054, 072, 012, 062, 027, 005, 035, 061, 013, 060, 027, 037, 044 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 024, 056, 017, 001, 002, 004, 054, 007, 034, 075, 062, 023, 010, 041, 052, 032 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 052, 032, 002, 061, 043, 014, 060, 002, 047, 075, 015, 015, 045, 066, 031, 063 } };
/** /**
* @brief Define a reed-solomon codec. * @brief Define a reed-solomon codec.
* @param TYPE Data type primitive * @param TYPE Data type primitive
@ -129,6 +272,15 @@ public:
}; };
RS6355 rs24169; // 8 bit / 4 bit corrections max / 2 bytes total RS6355 rs24169; // 8 bit / 4 bit corrections max / 2 bytes total
/**
* @brief Implements Reed-Solomon (63,35,29)
*/
class RS6335 : public __RS_63(35) {
public:
RS6335() : __RS_63(35)() { /* stub */ }
};
RS6335 rs633529; // 28 bit / 14 bit corrections max / 4 bytes total
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -291,6 +443,206 @@ void RS634717::encode362017(uint8_t* data)
Utils::hex2Bin(codeword[i], data, offset); Utils::hex2Bin(codeword[i], data, offset);
} }
/* Decode RS (52,30,23) FEC. */
bool RS634717::decode523023(uint8_t* data)
{
assert(data != nullptr);
std::vector<uint8_t> codeword(63, 0);
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 52U; i++, offset += 6)
codeword[11 + i] = Utils::bin2Hex(data, offset);
int ec = rs633529.decode(codeword);
#if DEBUG_RS
LogDebugEx(LOG_HOST, "RS634717::decode523023()", "errors = %d\n", ec);
#endif
offset = 0U;
for (uint32_t i = 0U; i < 30U; i++, offset += 6)
Utils::hex2Bin(codeword[11 + i], data, offset);
if ((ec == -1) || (ec >= 11)) {
return false;
}
return true;
}
/* Encode RS (52,30,23) FEC. */
void RS634717::encode523023(uint8_t* data)
{
assert(data != nullptr);
uint8_t codeword[52U];
for (uint32_t i = 0U; i < 52U; i++) {
codeword[i] = 0x00U;
uint32_t offset = 0U;
for (uint32_t j = 0U; j < 30U; j++, offset += 6U) {
uint8_t hexbit = Utils::bin2Hex(data, offset);
codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_523023[j][i]);
}
}
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 52U; i++, offset += 6U)
Utils::hex2Bin(codeword[i], data, offset);
}
/* Decode RS (46,26,21) FEC. */
bool RS634717::decode462621(uint8_t* data)
{
assert(data != nullptr);
std::vector<uint8_t> codeword(63, 0);
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 46U; i++, offset += 6)
codeword[17 + i] = Utils::bin2Hex(data, offset);
int ec = rs633529.decode(codeword);
#if DEBUG_RS
LogDebugEx(LOG_HOST, "RS634717::decode462621()", "errors = %d\n", ec);
#endif
offset = 0U;
for (uint32_t i = 0U; i < 26U; i++, offset += 6)
Utils::hex2Bin(codeword[17 + i], data, offset);
if ((ec == -1) || (ec >= 10)) {
return false;
}
return true;
}
/* Encode RS (46,26,21) FEC. */
void RS634717::encode462621(uint8_t* data)
{
assert(data != nullptr);
uint8_t codeword[46U];
for (uint32_t i = 0U; i < 46U; i++) {
codeword[i] = 0x00U;
uint32_t offset = 0U;
for (uint32_t j = 0U; j < 26U; j++, offset += 6U) {
uint8_t hexbit = Utils::bin2Hex(data, offset);
codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_462621[j][i]);
}
}
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 46U; i++, offset += 6U)
Utils::hex2Bin(codeword[i], data, offset);
}
/* Decode RS (45,26,20) FEC. */
bool RS634717::decode452620(uint8_t* data)
{
assert(data != nullptr);
std::vector<uint8_t> codeword(63, 0);
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 45U; i++, offset += 6)
codeword[18 + i] = Utils::bin2Hex(data, offset);
int ec = rs633529.decode(codeword);
#if DEBUG_RS
LogDebugEx(LOG_HOST, "RS634717::decode452620()", "errors = %d\n", ec);
#endif
offset = 0U;
for (uint32_t i = 0U; i < 26U; i++, offset += 6)
Utils::hex2Bin(codeword[18 + i], data, offset);
if ((ec == -1) || (ec >= 9)) {
return false;
}
return true;
}
/* Encode RS (45,26,20) FEC. */
void RS634717::encode452620(uint8_t* data)
{
assert(data != nullptr);
uint8_t codeword[45U];
for (uint32_t i = 0U; i < 45U; i++) {
codeword[i] = 0x00U;
uint32_t offset = 0U;
for (uint32_t j = 0U; j < 26U; j++, offset += 6U) {
uint8_t hexbit = Utils::bin2Hex(data, offset);
codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_452620[j][i]);
}
}
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 45U; i++, offset += 6U)
Utils::hex2Bin(codeword[i], data, offset);
}
/* Decode RS (44,16,29) FEC. */
bool RS634717::decode441629(uint8_t* data)
{
assert(data != nullptr);
std::vector<uint8_t> codeword(63, 0);
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 44U; i++, offset += 6)
codeword[19 + i] = Utils::bin2Hex(data, offset);
int ec = rs633529.decode(codeword);
#if DEBUG_RS
LogDebugEx(LOG_HOST, "RS634717::decode441629()", "errors = %d\n", ec);
#endif
offset = 0U;
for (uint32_t i = 0U; i < 16U; i++, offset += 6)
Utils::hex2Bin(codeword[19 + i], data, offset);
if ((ec == -1) || (ec >= 14)) {
return false;
}
return true;
}
/* Encode RS (44,16,29) FEC. */
void RS634717::encode441629(uint8_t* data)
{
assert(data != nullptr);
uint8_t codeword[44U];
for (uint32_t i = 0U; i < 44U; i++) {
codeword[i] = 0x00U;
uint32_t offset = 0U;
for (uint32_t j = 0U; j < 16U; j++, offset += 6U) {
uint8_t hexbit = Utils::bin2Hex(data, offset);
codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_441629[j][i]);
}
}
uint32_t offset = 0U;
for (uint32_t i = 0U; i < 44U; i++, offset += 6U)
Utils::hex2Bin(codeword[i], data, offset);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Private Class Members // Private Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2016 Jonathan Naylor, G4KLX * Copyright (C) 2016 Jonathan Naylor, G4KLX
* Copyright (C) 2017,2023 Bryan Biedenkapp, N2PLL * Copyright (C) 2017,2023,2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -27,8 +27,8 @@ namespace edac
/** /**
* @brief Implements Reed-Solomon (63,47,17). Which is also used to implement * @brief Implements Reed-Solomon (63,47,17). Which is also used to implement
* Reed-Solomon (24,12,13), (24,16,9) and (36,20,17) forward * Reed-Solomon (24,12,13), (24,16,9), (36,20,17), (52,30,23), (46,26,21),
* error correction. * (45,26,20). (44,16,29) forward error correction.
* @ingroup edac * @ingroup edac
*/ */
class HOST_SW_API RS634717 { class HOST_SW_API RS634717 {
@ -42,6 +42,8 @@ namespace edac
*/ */
~RS634717(); ~RS634717();
/** Project 25 Phase I Reed-Solomon (TIA-102.BAAA-B Section 4.9) */
/** /**
* @brief Decode RS (24,12,13) FEC. * @brief Decode RS (24,12,13) FEC.
* @param data Reed-Solomon FEC encoded data to decode. * @param data Reed-Solomon FEC encoded data to decode.
@ -78,6 +80,56 @@ namespace edac
*/ */
void encode362017(uint8_t* data); void encode362017(uint8_t* data);
/** Project 25 Phase II Reed-Solomon (TIA-102.BBAC-A Section 5.6) */
/**
* @brief Decode RS (52,30,23) FEC.
* @param data Reed-Solomon FEC encoded data to decode.
* @returns bool True, if data was decoded, otherwise false.
*/
bool decode523023(uint8_t* data);
/**
* @brief Encode RS (52,30,23) FEC.
* @param data Raw data to encode with Reed-Solomon FEC.
*/
void encode523023(uint8_t* data);
/**
* @brief Decode RS (46,26,21) FEC.
* @param data Reed-Solomon FEC encoded data to decode.
* @returns bool True, if data was decoded, otherwise false.
*/
bool decode462621(uint8_t* data);
/**
* @brief Encode RS (46,26,21) FEC.
* @param data Raw data to encode with Reed-Solomon FEC.
*/
void encode462621(uint8_t* data);
/**
* @brief Decode RS (45,26,20) FEC.
* @param data Reed-Solomon FEC encoded data to decode.
* @returns bool True, if data was decoded, otherwise false.
*/
bool decode452620(uint8_t* data);
/**
* @brief Encode RS (45,26,20) FEC.
* @param data Raw data to encode with Reed-Solomon FEC.
*/
void encode452620(uint8_t* data);
/**
* @brief Decode RS (44,16,29) FEC.
* @param data Reed-Solomon FEC encoded data to decode.
* @returns bool True, if data was decoded, otherwise false.
*/
bool decode441629(uint8_t* data);
/**
* @brief Encode RS (44,16,29) FEC.
* @param data Raw data to encode with Reed-Solomon FEC.
*/
void encode441629(uint8_t* data);
private: private:
/** /**
* @brief GF(2 ^ 6) multiply (for Reed-Solomon encoder). * @brief GF(2 ^ 6) multiply (for Reed-Solomon encoder).

@ -4,10 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @derivedfrom picojson (https://github.com/kazuho/picojson)
* @license BSD-2-Clause License (https://opensource.org/licenses/BSD-2-Clause)
*
* Copyright 2009-2010 Cybozu Labs, Inc. * Copyright 2009-2010 Cybozu Labs, Inc.
* Copyright 2011-2014 Kazuho Oku., All rights reserved. * Copyright 2011-2014 Kazuho Oku., All rights reserved.
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL

@ -21,8 +21,8 @@ using namespace lookups;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::mutex AdjSiteMapLookup::m_mutex; std::mutex AdjSiteMapLookup::s_mutex;
bool AdjSiteMapLookup::m_locked = false; bool AdjSiteMapLookup::s_locked = false;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Macros // Macros
@ -30,16 +30,16 @@ bool AdjSiteMapLookup::m_locked = false;
// Lock the table. // Lock the table.
#define __LOCK_TABLE() \ #define __LOCK_TABLE() \
std::lock_guard<std::mutex> lock(m_mutex); \ std::lock_guard<std::mutex> lock(s_mutex); \
m_locked = true; s_locked = true;
// Unlock the table. // Unlock the table.
#define __UNLOCK_TABLE() m_locked = false; #define __UNLOCK_TABLE() s_locked = false;
// Spinlock wait for table to be read unlocked. // Spinlock wait for table to be read unlocked.
#define __SPINLOCK() \ #define __SPINLOCK() \
if (m_locked) { \ if (s_locked) { \
while (m_locked) \ while (s_locked) \
Thread::sleep(2U); \ Thread::sleep(2U); \
} }
@ -133,8 +133,7 @@ void AdjSiteMapLookup::addEntry(AdjPeerMapEntry entry)
__LOCK_TABLE(); __LOCK_TABLE();
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
[&](AdjPeerMapEntry x) [&](AdjPeerMapEntry& x) {
{
return x.peerId() == id; return x.peerId() == id;
}); });
if (it != m_adjPeerMap.end()) { if (it != m_adjPeerMap.end()) {
@ -153,7 +152,10 @@ void AdjSiteMapLookup::eraseEntry(uint32_t id)
{ {
__LOCK_TABLE(); __LOCK_TABLE();
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), [&](AdjPeerMapEntry x) { return x.peerId() == id; }); auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
[&](AdjPeerMapEntry& x) {
return x.peerId() == id;
});
if (it != m_adjPeerMap.end()) { if (it != m_adjPeerMap.end()) {
m_adjPeerMap.erase(it); m_adjPeerMap.erase(it);
} }
@ -169,10 +171,9 @@ AdjPeerMapEntry AdjSiteMapLookup::find(uint32_t id)
__SPINLOCK(); __SPINLOCK();
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
[&](AdjPeerMapEntry x) [&](AdjPeerMapEntry& x) {
{
return x.peerId() == id; return x.peerId() == id;
}); });
if (it != m_adjPeerMap.end()) { if (it != m_adjPeerMap.end()) {
@ -224,7 +225,7 @@ bool AdjSiteMapLookup::load()
if (peerList.size() == 0U) { if (peerList.size() == 0U) {
::LogError(LOG_HOST, "No adj site map peer list defined!"); ::LogError(LOG_HOST, "No adj site map peer list defined!");
m_locked = false; s_locked = false;
return false; return false;
} }
@ -254,7 +255,7 @@ bool AdjSiteMapLookup::save()
return false; return false;
} }
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
// New list for our new group voice rules // New list for our new group voice rules
yaml::Node peerList; yaml::Node peerList;
@ -275,7 +276,7 @@ bool AdjSiteMapLookup::save()
} }
try { try {
LogMessage(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str()); LogInfoEx(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str());
yaml::Serialize(newRules, m_rulesFile.c_str()); yaml::Serialize(newRules, m_rulesFile.c_str());
LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str()); LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str());
} }

@ -233,8 +233,8 @@ namespace lookups
bool m_stop; bool m_stop;
static std::mutex m_mutex; //! Mutex used for change locking. static std::mutex s_mutex; //!< Mutex used for change locking.
static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used.
/** /**
* @brief Loads the table from the passed lookup table file. * @brief Loads the table from the passed lookup table file.

@ -67,15 +67,19 @@ void AffiliationLookup::unitReg(uint32_t srcId)
return; return;
} }
__lock();
m_unitRegTable.push_back(srcId); m_unitRegTable.push_back(srcId);
m_unitRegTimers[srcId] = Timer(1000U, UNIT_REG_TIMEOUT); m_unitRegTimers[srcId] = Timer(1000U, UNIT_REG_TIMEOUT);
m_unitRegTimers[srcId].start(); m_unitRegTimers[srcId].start();
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_HOST, "%s, unit registration, srcId = %u", LogInfoEx(LOG_HOST, "%s, unit registration, srcId = %u",
m_name.c_str(), srcId); m_name.c_str(), srcId);
} }
__unlock();
} }
/* Helper to group unaffiliate a source ID. */ /* Helper to group unaffiliate a source ID. */
@ -88,13 +92,15 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic)
return false; return false;
} }
groupUnaff(srcId);
__lock();
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_HOST, "%s, unit deregistration, srcId = %u", LogInfoEx(LOG_HOST, "%s, unit deregistration, srcId = %u",
m_name.c_str(), srcId); m_name.c_str(), srcId);
} }
groupUnaff(srcId);
m_unitRegTimers[srcId].stop(); m_unitRegTimers[srcId].stop();
// remove dynamic unit registration table entry // remove dynamic unit registration table entry
@ -113,6 +119,8 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic)
} }
} }
__unlock();
return ret; return ret;
} }
@ -124,6 +132,8 @@ void AffiliationLookup::touchUnitReg(uint32_t srcId)
return; return;
} }
__spinlock();
if (isUnitReg(srcId)) { if (isUnitReg(srcId)) {
m_unitRegTimers[srcId].start(); m_unitRegTimers[srcId].start();
} }
@ -137,6 +147,8 @@ uint32_t AffiliationLookup::unitRegTimeout(uint32_t srcId)
return 0U; return 0U;
} }
__spinlock();
if (isUnitReg(srcId)) { if (isUnitReg(srcId)) {
return m_unitRegTimers[srcId].getTimeout(); return m_unitRegTimers[srcId].getTimeout();
} }
@ -152,6 +164,8 @@ uint32_t AffiliationLookup::unitRegTimer(uint32_t srcId)
return 0U; return 0U;
} }
__spinlock();
if (isUnitReg(srcId)) { if (isUnitReg(srcId)) {
return m_unitRegTimers[srcId].getTimer(); return m_unitRegTimers[srcId].getTimer();
} }
@ -163,6 +177,8 @@ uint32_t AffiliationLookup::unitRegTimer(uint32_t srcId)
bool AffiliationLookup::isUnitReg(uint32_t srcId) const bool AffiliationLookup::isUnitReg(uint32_t srcId) const
{ {
__spinlock();
// lookup dynamic unit registration table entry // lookup dynamic unit registration table entry
m_unitRegTable.lock(false); m_unitRegTable.lock(false);
if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) {
@ -179,9 +195,11 @@ bool AffiliationLookup::isUnitReg(uint32_t srcId) const
void AffiliationLookup::clearUnitReg() void AffiliationLookup::clearUnitReg()
{ {
__lock();
std::vector<uint32_t> srcToRel = std::vector<uint32_t>(); std::vector<uint32_t> srcToRel = std::vector<uint32_t>();
LogWarning(LOG_HOST, "%s, releasing all unit registrations", m_name.c_str()); LogWarning(LOG_HOST, "%s, releasing all unit registrations", m_name.c_str());
m_unitRegTable.clear(); m_unitRegTable.clear();
__unlock();
} }
/* Helper to group affiliate a source ID. */ /* Helper to group affiliate a source ID. */
@ -189,13 +207,17 @@ void AffiliationLookup::clearUnitReg()
void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId) void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId)
{ {
if (!isGroupAff(srcId, dstId)) { if (!isGroupAff(srcId, dstId)) {
__lock();
// update dynamic affiliation table // update dynamic affiliation table
m_grpAffTable[srcId] = dstId; m_grpAffTable[srcId] = dstId;
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_HOST, "%s, group affiliation, srcId = %u, dstId = %u", LogInfoEx(LOG_HOST, "%s, group affiliation, srcId = %u, dstId = %u",
m_name.c_str(), srcId, dstId); m_name.c_str(), srcId, dstId);
} }
__unlock();
} }
} }
@ -203,14 +225,17 @@ void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId)
bool AffiliationLookup::groupUnaff(uint32_t srcId) bool AffiliationLookup::groupUnaff(uint32_t srcId)
{ {
__lock();
// lookup dynamic affiliation table entry // lookup dynamic affiliation table entry
if (m_grpAffTable.find(srcId) != m_grpAffTable.end()) { auto it = m_grpAffTable.find(srcId);
uint32_t tblDstId = m_grpAffTable.at(srcId); if (it != m_grpAffTable.end()) {
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_HOST, "%s, group unaffiliation, srcId = %u, dstId = %u", LogInfoEx(LOG_HOST, "%s, group unaffiliation, srcId = %u, dstId = %u",
m_name.c_str(), srcId, tblDstId); m_name.c_str(), srcId, it->second);
} }
} else { } else {
__unlock();
return false; return false;
} }
@ -219,9 +244,11 @@ bool AffiliationLookup::groupUnaff(uint32_t srcId)
uint32_t entry = m_grpAffTable.at(srcId); // this value will get discarded uint32_t entry = m_grpAffTable.at(srcId); // this value will get discarded
(void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard (void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard
m_grpAffTable.erase(srcId); m_grpAffTable.erase(srcId);
__unlock();
return true; return true;
} }
catch (...) { catch (...) {
__unlock();
return false; return false;
} }
} }
@ -230,6 +257,8 @@ bool AffiliationLookup::groupUnaff(uint32_t srcId)
bool AffiliationLookup::hasGroupAff(uint32_t dstId) const bool AffiliationLookup::hasGroupAff(uint32_t dstId) const
{ {
__spinlock();
// lookup dynamic affiliation table entry // lookup dynamic affiliation table entry
m_grpAffTable.lock(false); m_grpAffTable.lock(false);
for (auto entry : m_grpAffTable) { for (auto entry : m_grpAffTable) {
@ -247,11 +276,13 @@ bool AffiliationLookup::hasGroupAff(uint32_t dstId) const
bool AffiliationLookup::isGroupAff(uint32_t srcId, uint32_t dstId) const bool AffiliationLookup::isGroupAff(uint32_t srcId, uint32_t dstId) const
{ {
__spinlock();
// lookup dynamic affiliation table entry // lookup dynamic affiliation table entry
m_grpAffTable.lock(false); m_grpAffTable.lock(false);
if (m_grpAffTable.find(srcId) != m_grpAffTable.end()) { auto it = m_grpAffTable.find(srcId);
uint32_t tblDstId = m_grpAffTable.at(srcId); if (it != m_grpAffTable.end()) {
if (tblDstId == dstId) { if (it->second == dstId) {
m_grpAffTable.unlock(); m_grpAffTable.unlock();
return true; return true;
} }
@ -288,10 +319,14 @@ std::vector<uint32_t> AffiliationLookup::clearGroupAff(uint32_t dstId, bool rele
} }
} }
__lock();
for (auto srcId : srcToRel) { for (auto srcId : srcToRel) {
m_grpAffTable.erase(srcId); m_grpAffTable.erase(srcId);
} }
__unlock();
return srcToRel; return srcToRel;
} }
@ -312,6 +347,8 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi
return false; return false;
} }
__lock();
m_grantChTable[dstId] = chNo; m_grantChTable[dstId] = chNo;
m_grantSrcIdTable[dstId] = srcId; m_grantSrcIdTable[dstId] = srcId;
m_rfGrantChCnt++; m_rfGrantChCnt++;
@ -323,10 +360,12 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi
m_grantTimers[dstId].start(); m_grantTimers[dstId].start();
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_HOST, "%s, granting channel, chNo = %u, dstId = %u, srcId = %u, group = %u", LogInfoEx(LOG_HOST, "%s, granting channel, chNo = %u, dstId = %u, srcId = %u, group = %u",
m_name.c_str(), chNo, dstId, srcId, grp); m_name.c_str(), chNo, dstId, srcId, grp);
} }
__unlock();
return true; return true;
} }
@ -338,9 +377,17 @@ void AffiliationLookup::touchGrant(uint32_t dstId)
return; return;
} }
/*
** bryanb: this doesn't __spinlock(), this is dangerous but necessary
** otherwise an affiliations lookup lock can cause audio cuts if this is
** used in an audio processing chain
*/
m_grantTimers.lock(false);
if (isGranted(dstId)) { if (isGranted(dstId)) {
m_grantTimers[dstId].start(); m_grantTimers[dstId].start();
} }
m_grantTimers.unlock();
} }
/* Helper to release the channel grant for the destination ID. */ /* Helper to release the channel grant for the destination ID. */
@ -355,7 +402,7 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
if (dstId == 0U && releaseAll) { if (dstId == 0U && releaseAll) {
LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str());
m_grantChTable.lock(); m_grantChTable.lock(false);
std::vector<uint32_t> gntsToRel = std::vector<uint32_t>(); std::vector<uint32_t> gntsToRel = std::vector<uint32_t>();
for (auto entry : m_grantChTable) { for (auto entry : m_grantChTable) {
uint32_t dstId = entry.first; uint32_t dstId = entry.first;
@ -372,17 +419,29 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
} }
if (isGranted(dstId)) { if (isGranted(dstId)) {
uint32_t chNo = m_grantChTable.at(dstId); uint32_t chNo = 0U;
m_grantChTable.lock(false);
for (auto entry : m_grantChTable) {
if (entry.first == dstId) {
chNo = entry.second;
break;
}
}
m_grantChTable.unlock();
uint32_t srcId = getGrantedSrcId(dstId);
if (m_verbose) { if (m_verbose) {
LogMessage(LOG_HOST, "%s, releasing channel grant, chNo = %u, dstId = %u", LogInfoEx(LOG_HOST, "%s, releasing channel grant, chNo = %u, dstId = %u",
m_name.c_str(), chNo, dstId); m_name.c_str(), chNo, dstId);
} }
if (m_releaseGrant != nullptr) { if (m_releaseGrant != nullptr) {
m_releaseGrant(chNo, dstId, 0U); m_releaseGrant(chNo, srcId, dstId, 0U);
} }
__lock();
m_grantChTable.erase(dstId); m_grantChTable.erase(dstId);
m_grantSrcIdTable.erase(dstId); m_grantSrcIdTable.erase(dstId);
m_uuGrantedTable.erase(dstId); m_uuGrantedTable.erase(dstId);
@ -398,6 +457,8 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
m_grantTimers[dstId].stop(); m_grantTimers[dstId].stop();
__unlock();
return true; return true;
} }
@ -412,6 +473,8 @@ bool AffiliationLookup::isChBusy(uint32_t chNo) const
return false; return false;
} }
__spinlock();
// lookup dynamic channel grant table entry // lookup dynamic channel grant table entry
m_grantChTable.lock(false); m_grantChTable.lock(false);
for (auto entry : m_grantChTable) { for (auto entry : m_grantChTable) {
@ -433,6 +496,12 @@ bool AffiliationLookup::isGranted(uint32_t dstId) const
return false; return false;
} }
/*
** bryanb: this doesn't __spinlock(), this is dangerous but necessary
** otherwise an affiliations lookup lock can cause audio cuts if this is
** used in an audio processing chain
*/
// lookup dynamic channel grant table entry // lookup dynamic channel grant table entry
m_grantChTable.lock(false); m_grantChTable.lock(false);
for (auto entry : m_grantChTable) { for (auto entry : m_grantChTable) {
@ -457,6 +526,8 @@ bool AffiliationLookup::isGroup(uint32_t dstId) const
return true; return true;
} }
__spinlock();
// lookup U-U grant flag table entry // lookup U-U grant flag table entry
m_uuGrantedTable.lock(false); m_uuGrantedTable.lock(false);
for (auto entry : m_uuGrantedTable) { for (auto entry : m_uuGrantedTable) {
@ -481,6 +552,8 @@ bool AffiliationLookup::isNetGranted(uint32_t dstId) const
return false; return false;
} }
__spinlock();
// lookup net granted flag table entry // lookup net granted flag table entry
m_netGrantedTable.lock(false); m_netGrantedTable.lock(false);
for (auto entry : m_netGrantedTable) { for (auto entry : m_netGrantedTable) {
@ -505,9 +578,18 @@ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId)
return 0U; return 0U;
} }
__spinlock();
if (isGranted(dstId)) { if (isGranted(dstId)) {
// lookup dynamic channel grant table entry
m_grantChTable.lock(false);
auto it = m_grantChTable.find(dstId);
if (it != m_grantChTable.end()) {
m_grantChTable.unlock();
return m_grantChTable[dstId]; return m_grantChTable[dstId];
} }
m_grantChTable.unlock();
}
return 0U; return 0U;
} }
@ -516,6 +598,8 @@ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId)
uint32_t AffiliationLookup::getGrantedDstByCh(uint32_t chNo) uint32_t AffiliationLookup::getGrantedDstByCh(uint32_t chNo)
{ {
__spinlock();
// lookup dynamic channel grant table entry // lookup dynamic channel grant table entry
m_grantChTable.lock(false); m_grantChTable.lock(false);
for (auto entry : m_grantChTable) { for (auto entry : m_grantChTable) {
@ -537,6 +621,8 @@ uint32_t AffiliationLookup::getGrantedBySrcId(uint32_t srcId)
return 0U; return 0U;
} }
__spinlock();
// lookup dynamic channel grant source table entry // lookup dynamic channel grant source table entry
m_grantSrcIdTable.lock(false); m_grantSrcIdTable.lock(false);
for (auto entry : m_grantSrcIdTable) { for (auto entry : m_grantSrcIdTable) {
@ -558,9 +644,18 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId)
return 0U; return 0U;
} }
__spinlock();
if (isGranted(dstId)) { if (isGranted(dstId)) {
// lookup dynamic channel grant source table entry
m_grantSrcIdTable.lock(false);
auto it = m_grantSrcIdTable.find(dstId);
if (it != m_grantSrcIdTable.end()) {
m_grantSrcIdTable.unlock();
return m_grantSrcIdTable[dstId]; return m_grantSrcIdTable[dstId];
} }
m_grantSrcIdTable.unlock();
}
return 0U; return 0U;
} }

@ -21,6 +21,7 @@
#define __AFFILIATION_LOOKUP_H__ #define __AFFILIATION_LOOKUP_H__
#include "common/Defines.h" #include "common/Defines.h"
#include "common/concurrent/concurrent_lock.h"
#include "common/concurrent/vector.h" #include "common/concurrent/vector.h"
#include "common/concurrent/unordered_map.h" #include "common/concurrent/unordered_map.h"
#include "common/lookups/ChannelLookup.h" #include "common/lookups/ChannelLookup.h"
@ -41,7 +42,7 @@ namespace lookups
* and group affiliation information. * and group affiliation information.
* @ingroup lookups_aff * @ingroup lookups_aff
*/ */
class HOST_SW_API AffiliationLookup { class HOST_SW_API AffiliationLookup : public concurrent::concurrent_lock {
public: public:
/** /**
* @brief Initializes a new instance of the AffiliationLookup class. * @brief Initializes a new instance of the AffiliationLookup class.
@ -265,11 +266,15 @@ namespace lookups
/** /**
* @brief Helper to set the release grant callback. * @brief Helper to set the release grant callback.
* @note Do not call AffiliationLookup get functions from within this callback, deadlock protection
* is not guaranteed.
* @param callback Relase grant function callback. * @param callback Relase grant function callback.
*/ */
void setReleaseGrantCallback(std::function<void(uint32_t, uint32_t, uint8_t)>&& callback) { m_releaseGrant = callback; } void setReleaseGrantCallback(std::function<void(uint32_t, uint32_t, uint32_t, uint8_t)>&& callback) { m_releaseGrant = callback; }
/** /**
* @brief Helper to set the unit deregistration callback. * @brief Helper to set the unit deregistration callback.
* @note Do not call AffiliationLookup get functions from within this callback, deadlock protection
* is not guaranteed.
* @param callback Unit deregistration function callback. * @param callback Unit deregistration function callback.
*/ */
void setUnitDeregCallback(std::function<void(uint32_t, bool)>&& callback) { m_unitDereg = callback; } void setUnitDeregCallback(std::function<void(uint32_t, bool)>&& callback) { m_unitDereg = callback; }
@ -287,8 +292,8 @@ namespace lookups
concurrent::unordered_map<uint32_t, bool> m_netGrantedTable; concurrent::unordered_map<uint32_t, bool> m_netGrantedTable;
concurrent::unordered_map<uint32_t, Timer> m_grantTimers; concurrent::unordered_map<uint32_t, Timer> m_grantTimers;
// chNo dstId slot // chNo srcId dstId slot
std::function<void(uint32_t, uint32_t, uint8_t)> m_releaseGrant; std::function<void(uint32_t, uint32_t, uint32_t, uint8_t)> m_releaseGrant;
// srcId auto // srcId auto
std::function<void(uint32_t, bool)> m_unitDereg; std::function<void(uint32_t, bool)> m_unitDereg;

@ -4,9 +4,6 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */

@ -22,7 +22,7 @@ using namespace lookups;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::mutex IdenTableLookup::m_mutex; std::mutex IdenTableLookup::s_mutex;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
@ -39,7 +39,7 @@ IdenTableLookup::IdenTableLookup(const std::string& filename, uint32_t reloadTim
void IdenTableLookup::clear() void IdenTableLookup::clear()
{ {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
m_table.clear(); m_table.clear();
} }
@ -49,7 +49,7 @@ IdenTable IdenTableLookup::find(uint32_t id)
{ {
IdenTable entry; IdenTable entry;
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
try { try {
entry = m_table.at(id); entry = m_table.at(id);
} catch (...) { } catch (...) {
@ -103,7 +103,7 @@ bool IdenTableLookup::load()
// clear table // clear table
clear(); clear();
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
// read lines from file // read lines from file
std::string line; std::string line;
@ -143,7 +143,7 @@ bool IdenTableLookup::load()
IdenTable entry = IdenTable(channelId, baseFrequency, chSpaceKhz, txOffsetMhz, chBandwidthKhz); IdenTable entry = IdenTable(channelId, baseFrequency, chSpaceKhz, txOffsetMhz, chBandwidthKhz);
LogMessage(LOG_HOST, "Channel Id %u: BaseFrequency = %uHz, TXOffsetMhz = %fMHz, BandwidthKhz = %fKHz, SpaceKhz = %fKHz", LogInfoEx(LOG_HOST, "Channel Id %u: BaseFrequency = %uHz, TXOffsetMhz = %fMHz, BandwidthKhz = %fKHz, SpaceKhz = %fKHz",
entry.channelId(), entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz()); entry.channelId(), entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz());
m_table[channelId] = entry; m_table[channelId] = entry;
@ -163,7 +163,7 @@ bool IdenTableLookup::load()
/* Saves the table to the passed lookup table file. */ /* Saves the table to the passed lookup table file. */
bool IdenTableLookup::save() bool IdenTableLookup::save(bool quiet)
{ {
return false; return false;
} }

@ -154,12 +154,13 @@ namespace lookups
/** /**
* @brief Saves the table to the passed lookup table file. * @brief Saves the table to the passed lookup table file.
* @param quiet Disable logging during save operation.
* @returns bool True, if lookup table was saved, otherwise false. * @returns bool True, if lookup table was saved, otherwise false.
*/ */
bool save() override; bool save(bool quiet = false) override;
private: private:
static std::mutex m_mutex; static std::mutex s_mutex;
}; };
} // namespace lookups } // namespace lookups

@ -199,9 +199,10 @@ namespace lookups
/** /**
* @brief Saves the table from the lookup table in memory. * @brief Saves the table from the lookup table in memory.
* @param quiet Disable logging during save operation.
* @returns bool True, if lookup table was saved, otherwise false. * @returns bool True, if lookup table was saved, otherwise false.
*/ */
virtual bool save() = 0; virtual bool save(bool quiet = false) = 0;
}; };
} // namespace lookups } // namespace lookups

@ -22,8 +22,8 @@ using namespace lookups;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::mutex PeerListLookup::m_mutex; std::mutex PeerListLookup::s_mutex;
bool PeerListLookup::m_locked = false; bool PeerListLookup::s_locked = false;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Macros // Macros
@ -31,16 +31,16 @@ bool PeerListLookup::m_locked = false;
// Lock the table. // Lock the table.
#define __LOCK_TABLE() \ #define __LOCK_TABLE() \
std::lock_guard<std::mutex> lock(m_mutex); \ std::lock_guard<std::mutex> lock(s_mutex); \
m_locked = true; s_locked = true;
// Unlock the table. // Unlock the table.
#define __UNLOCK_TABLE() m_locked = false; #define __UNLOCK_TABLE() s_locked = false;
// Spinlock wait for table to be read unlocked. // Spinlock wait for table to be read unlocked.
#define __SPINLOCK() \ #define __SPINLOCK() \
if (m_locked) { \ if (s_locked) { \
while (m_locked) \ while (s_locked) \
Thread::sleep(2U); \ Thread::sleep(2U); \
} }
@ -123,9 +123,9 @@ PeerId PeerListLookup::find(uint32_t id)
/* Commit the table. */ /* Commit the table. */
void PeerListLookup::commit() void PeerListLookup::commit(bool quiet)
{ {
save(); save(quiet);
} }
/* Gets whether the lookup is enabled. */ /* Gets whether the lookup is enabled. */
@ -223,9 +223,9 @@ bool PeerListLookup::load()
alias = parsed[3].c_str(); alias = parsed[3].c_str();
// parse peer link flag // parse peer link flag
bool peerLink = false; bool peerReplica = false;
if (parsed.size() >= 3) if (parsed.size() >= 3)
peerLink = ::atoi(parsed[2].c_str()) == 1; peerReplica = ::atoi(parsed[2].c_str()) == 1;
// parse can request keys flag // parse can request keys flag
bool canRequestKeys = false; bool canRequestKeys = false;
@ -237,6 +237,11 @@ bool PeerListLookup::load()
if (parsed.size() >= 6) if (parsed.size() >= 6)
canIssueInhibit = ::atoi(parsed[5].c_str()) == 1; canIssueInhibit = ::atoi(parsed[5].c_str()) == 1;
// parse can issue inhibit flag
bool hasCallPriority = false;
if (parsed.size() >= 7)
hasCallPriority = ::atoi(parsed[6].c_str()) == 1;
// parse optional password // parse optional password
std::string password = ""; std::string password = "";
if (parsed.size() >= 2) if (parsed.size() >= 2)
@ -244,19 +249,21 @@ bool PeerListLookup::load()
// load into table // load into table
PeerId entry = PeerId(id, alias, password, false); PeerId entry = PeerId(id, alias, password, false);
entry.peerLink(peerLink); entry.peerReplica(peerReplica);
entry.canRequestKeys(canRequestKeys); entry.canRequestKeys(canRequestKeys);
entry.canIssueInhibit(canIssueInhibit); entry.canIssueInhibit(canIssueInhibit);
entry.hasCallPriority(hasCallPriority);
m_table[id] = entry; m_table[id] = entry;
// log depending on what was loaded // log depending on what was loaded
LogMessage(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s", id, LogInfoEx(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s%s", id,
(!alias.empty() ? (" (" + alias + ")").c_str() : ""), (!alias.empty() ? (" (" + alias + ")").c_str() : ""),
(!password.empty() ? "using unique peer password" : "using master password"), (!password.empty() ? "using unique peer password" : "using master password"),
(peerLink) ? ", Peer-Link Enabled" : "", (peerReplica) ? ", Replication Enabled" : "",
(canRequestKeys) ? ", Can Request Keys" : "", (canRequestKeys) ? ", Can Request Keys" : "",
(canIssueInhibit) ? ", Can Issue Inhibit" : ""); (canIssueInhibit) ? ", Can Issue Inhibit" : "",
(hasCallPriority) ? ", Has Call Priority" : "");
} }
} }
@ -273,7 +280,7 @@ bool PeerListLookup::load()
/* Saves the table to the passed lookup table file. */ /* Saves the table to the passed lookup table file. */
bool PeerListLookup::save() bool PeerListLookup::save(bool quiet)
{ {
if (m_filename.empty()) { if (m_filename.empty()) {
return false; return false;
@ -285,12 +292,13 @@ bool PeerListLookup::save()
return false; return false;
} }
LogMessage(LOG_HOST, "Saving peer lookup file to %s", m_filename.c_str()); if (!quiet)
LogInfoEx(LOG_HOST, "Saving peer lookup file to %s", m_filename.c_str());
// Counter for lines written // Counter for lines written
unsigned int lines = 0; unsigned int lines = 0;
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
// String for writing // String for writing
std::string line; std::string line;
@ -310,9 +318,9 @@ bool PeerListLookup::save()
} }
line += ","; line += ",";
// add peerLink flag // add peer replication flag
bool peerLink = entry.second.peerLink(); bool peerReplica = entry.second.peerReplica();
if (peerLink) { if (peerReplica) {
line += "1,"; line += "1,";
} else { } else {
line += "0,"; line += "0,";
@ -342,6 +350,14 @@ bool PeerListLookup::save()
line += "0,"; line += "0,";
} }
// add hasCallPriority flag
bool hasCallPriority = entry.second.hasCallPriority();
if (hasCallPriority) {
line += "1,";
} else {
line += "0,";
}
line += "\n"; line += "\n";
file << line; file << line;
lines++; lines++;
@ -352,6 +368,7 @@ bool PeerListLookup::save()
if (lines != m_table.size()) if (lines != m_table.size())
return false; return false;
if (!quiet)
LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str()); LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str());
return true; return true;

@ -49,9 +49,10 @@ namespace lookups
m_peerId(0U), m_peerId(0U),
m_peerAlias(), m_peerAlias(),
m_peerPassword(), m_peerPassword(),
m_peerLink(false), m_peerReplica(false),
m_canRequestKeys(false), m_canRequestKeys(false),
m_canIssueInhibit(false), m_canIssueInhibit(false),
m_hasCallPriority(false),
m_peerDefault(false) m_peerDefault(false)
{ {
/* stub */ /* stub */
@ -68,9 +69,10 @@ namespace lookups
m_peerId(peerId), m_peerId(peerId),
m_peerAlias(peerAlias), m_peerAlias(peerAlias),
m_peerPassword(peerPassword), m_peerPassword(peerPassword),
m_peerLink(false), m_peerReplica(false),
m_canRequestKeys(false), m_canRequestKeys(false),
m_canIssueInhibit(false), m_canIssueInhibit(false),
m_hasCallPriority(false),
m_peerDefault(peerDefault) m_peerDefault(peerDefault)
{ {
/* stub */ /* stub */
@ -86,9 +88,10 @@ namespace lookups
m_peerId = data.m_peerId; m_peerId = data.m_peerId;
m_peerAlias = data.m_peerAlias; m_peerAlias = data.m_peerAlias;
m_peerPassword = data.m_peerPassword; m_peerPassword = data.m_peerPassword;
m_peerLink = data.m_peerLink; m_peerReplica = data.m_peerReplica;
m_canRequestKeys = data.m_canRequestKeys; m_canRequestKeys = data.m_canRequestKeys;
m_canIssueInhibit = data.m_canIssueInhibit; m_canIssueInhibit = data.m_canIssueInhibit;
m_hasCallPriority = data.m_hasCallPriority;
m_peerDefault = data.m_peerDefault; m_peerDefault = data.m_peerDefault;
} }
@ -117,7 +120,7 @@ namespace lookups
*/ */
DECLARE_PROPERTY_PLAIN(uint32_t, peerId); DECLARE_PROPERTY_PLAIN(uint32_t, peerId);
/** /**
* @breif Peer Alias * @brief Peer Alias
*/ */
DECLARE_PROPERTY_PLAIN(std::string, peerAlias); DECLARE_PROPERTY_PLAIN(std::string, peerAlias);
/** /**
@ -125,9 +128,9 @@ namespace lookups
*/ */
DECLARE_PROPERTY_PLAIN(std::string, peerPassword); DECLARE_PROPERTY_PLAIN(std::string, peerPassword);
/** /**
* @brief Flag indicating if the peer participates in peer link and should be sent configuration. * @brief Flag indicating if the peer participates in peer replication and should be sent configuration.
*/ */
DECLARE_PROPERTY_PLAIN(bool, peerLink); DECLARE_PROPERTY_PLAIN(bool, peerReplica);
/** /**
* @brief Flag indicating if the peer can request encryption keys. * @brief Flag indicating if the peer can request encryption keys.
*/ */
@ -136,6 +139,10 @@ namespace lookups
* @brief Flag indicating if the peer can issue inhibit/uninhibit packets. * @brief Flag indicating if the peer can issue inhibit/uninhibit packets.
*/ */
DECLARE_PROPERTY_PLAIN(bool, canIssueInhibit); DECLARE_PROPERTY_PLAIN(bool, canIssueInhibit);
/**
* @brief Flag indicating if the peer has call transmit priority.
*/
DECLARE_PROPERTY_PLAIN(bool, hasCallPriority);
/** /**
* @brief Flag indicating if the peer is default. * @brief Flag indicating if the peer is default.
*/ */
@ -186,8 +193,9 @@ namespace lookups
/** /**
* @brief Commit the table. * @brief Commit the table.
* @param quiet Disable logging during save operation.
*/ */
void commit(); void commit(bool quiet = false);
/** /**
* @brief Gets whether the lookup is enabled. * @brief Gets whether the lookup is enabled.
@ -236,13 +244,14 @@ namespace lookups
/** /**
* @brief Saves the table to the passed lookup table file. * @brief Saves the table to the passed lookup table file.
* @param quiet Disable logging during save operation.
* @return True, if lookup table was saved, otherwise false. * @return True, if lookup table was saved, otherwise false.
*/ */
bool save() override; bool save(bool quiet = false) override;
private: private:
static std::mutex m_mutex; //! Mutex used for change locking. static std::mutex s_mutex; //!< Mutex used for change locking.
static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used.
}; };
} // namespace lookups } // namespace lookups

@ -24,8 +24,8 @@ using namespace lookups;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::mutex RadioIdLookup::m_mutex; std::mutex RadioIdLookup::s_mutex;
bool RadioIdLookup::m_locked = false; bool RadioIdLookup::s_locked = false;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Macros // Macros
@ -33,16 +33,16 @@ bool RadioIdLookup::m_locked = false;
// Lock the table. // Lock the table.
#define __LOCK_TABLE() \ #define __LOCK_TABLE() \
std::lock_guard<std::mutex> lock(m_mutex); \ std::lock_guard<std::mutex> lock(s_mutex); \
m_locked = true; s_locked = true;
// Unlock the table. // Unlock the table.
#define __UNLOCK_TABLE() m_locked = false; #define __UNLOCK_TABLE() s_locked = false;
// Spinlock wait for table to be read unlocked. // Spinlock wait for table to be read unlocked.
#define __SPINLOCK() \ #define __SPINLOCK() \
if (m_locked) { \ if (s_locked) { \
while (m_locked) \ while (s_locked) \
Thread::sleep(2U); \ Thread::sleep(2U); \
} }
@ -148,9 +148,9 @@ RadioId RadioIdLookup::find(uint32_t id)
/* Saves loaded talkgroup rules. */ /* Saves loaded talkgroup rules. */
void RadioIdLookup::commit() void RadioIdLookup::commit(bool quiet)
{ {
save(); save(quiet);
} }
/* Flag indicating whether radio ID access control is enabled or not. */ /* Flag indicating whether radio ID access control is enabled or not. */
@ -241,7 +241,7 @@ bool RadioIdLookup::load()
/* Saves the table to the passed lookup table file. */ /* Saves the table to the passed lookup table file. */
bool RadioIdLookup::save() bool RadioIdLookup::save(bool quiet)
{ {
if (m_filename.empty()) { if (m_filename.empty()) {
return false; return false;
@ -253,12 +253,13 @@ bool RadioIdLookup::save()
return false; return false;
} }
LogMessage(LOG_HOST, "Saving RID lookup file to %s", m_filename.c_str()); if (!quiet)
LogInfoEx(LOG_HOST, "Saving RID lookup file to %s", m_filename.c_str());
// Counter for lines written // Counter for lines written
unsigned int lines = 0; unsigned int lines = 0;
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
// String for writing // String for writing
std::string line; std::string line;
@ -296,6 +297,7 @@ bool RadioIdLookup::save()
if (lines != m_table.size()) if (lines != m_table.size())
return false; return false;
if (!quiet)
LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str()); LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str());
return true; return true;

@ -184,8 +184,9 @@ namespace lookups
/** /**
* @brief Saves loaded radio ID lookups. * @brief Saves loaded radio ID lookups.
* @param quiet Disable logging during save operation.
*/ */
void commit(); void commit(bool quiet = false);
/** /**
* @brief Flag indicating whether radio ID access control is enabled or not. * @brief Flag indicating whether radio ID access control is enabled or not.
@ -203,13 +204,14 @@ namespace lookups
/** /**
* @brief Saves the table to the passed lookup table file. * @brief Saves the table to the passed lookup table file.
* @param quiet Disable logging during save operation.
* @return True, if lookup table was saved, otherwise false. * @return True, if lookup table was saved, otherwise false.
*/ */
bool save() override; bool save(bool quiet = false) override;
private: private:
static std::mutex m_mutex; //! Mutex used for change locking. static std::mutex s_mutex; //!< Mutex used for change locking.
static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used.
}; };
} // namespace lookups } // namespace lookups

@ -22,8 +22,8 @@ using namespace lookups;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::mutex TalkgroupRulesLookup::m_mutex; std::mutex TalkgroupRulesLookup::s_mutex;
bool TalkgroupRulesLookup::m_locked = false; bool TalkgroupRulesLookup::s_locked = false;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Macros // Macros
@ -31,16 +31,16 @@ bool TalkgroupRulesLookup::m_locked = false;
// Lock the table. // Lock the table.
#define __LOCK_TABLE() \ #define __LOCK_TABLE() \
std::lock_guard<std::mutex> lock(m_mutex); \ std::lock_guard<std::mutex> lock(s_mutex); \
m_locked = true; s_locked = true;
// Unlock the table. // Unlock the table.
#define __UNLOCK_TABLE() m_locked = false; #define __UNLOCK_TABLE() s_locked = false;
// Spinlock wait for table to be read unlocked. // Spinlock wait for table to be read unlocked.
#define __SPINLOCK() \ #define __SPINLOCK() \
if (m_locked) { \ if (s_locked) { \
while (m_locked) \ while (s_locked) \
Thread::sleep(2U); \ Thread::sleep(2U); \
} }
@ -143,8 +143,7 @@ void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled, boo
__LOCK_TABLE(); __LOCK_TABLE();
auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(),
[&](TalkgroupRuleGroupVoice x) [&](TalkgroupRuleGroupVoice& x) {
{
if (slot != 0U) { if (slot != 0U) {
return x.source().tgId() == id && x.source().tgSlot() == slot; return x.source().tgId() == id && x.source().tgSlot() == slot;
} }
@ -192,8 +191,7 @@ void TalkgroupRulesLookup::addEntry(TalkgroupRuleGroupVoice groupVoice)
__LOCK_TABLE(); __LOCK_TABLE();
auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(),
[&](TalkgroupRuleGroupVoice x) [&](TalkgroupRuleGroupVoice& x) {
{
if (slot != 0U) { if (slot != 0U) {
return x.source().tgId() == id && x.source().tgSlot() == slot; return x.source().tgId() == id && x.source().tgSlot() == slot;
} }
@ -216,7 +214,10 @@ void TalkgroupRulesLookup::eraseEntry(uint32_t id, uint8_t slot)
{ {
__LOCK_TABLE(); __LOCK_TABLE();
auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { return x.source().tgId() == id && x.source().tgSlot() == slot; }); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(),
[&](TalkgroupRuleGroupVoice& x) {
return x.source().tgId() == id && x.source().tgSlot() == slot;
});
if (it != m_groupVoice.end()) { if (it != m_groupVoice.end()) {
m_groupVoice.erase(it); m_groupVoice.erase(it);
} }
@ -232,9 +233,9 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::find(uint32_t id, uint8_t slot)
__SPINLOCK(); __SPINLOCK();
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(),
[&](TalkgroupRuleGroupVoice x) [&](TalkgroupRuleGroupVoice& x)
{ {
if (slot != 0U) { if (slot != 0U) {
return x.source().tgId() == id && x.source().tgSlot() == slot; return x.source().tgId() == id && x.source().tgSlot() == slot;
@ -259,17 +260,15 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByRewrite(uint32_t peerId, uin
__SPINLOCK(); __SPINLOCK();
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(),
[&](TalkgroupRuleGroupVoice x) [&](TalkgroupRuleGroupVoice& x) {
{
if (x.config().rewrite().size() == 0) if (x.config().rewrite().size() == 0)
return false; return false;
std::vector<TalkgroupRuleRewrite> rewrite = x.config().rewrite(); std::vector<TalkgroupRuleRewrite> rewrite = x.config().rewrite();
auto innerIt = std::find_if(rewrite.begin(), rewrite.end(), auto innerIt = std::find_if(rewrite.begin(), rewrite.end(),
[&](TalkgroupRuleRewrite y) [&](TalkgroupRuleRewrite& y) {
{
if (slot != 0U) { if (slot != 0U) {
return y.peerId() == peerId && y.tgId() == id && y.tgSlot() == slot; return y.peerId() == peerId && y.tgId() == id && y.tgSlot() == slot;
} }
@ -292,9 +291,9 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByRewrite(uint32_t peerId, uin
/* Saves loaded talkgroup rules. */ /* Saves loaded talkgroup rules. */
bool TalkgroupRulesLookup::commit() bool TalkgroupRulesLookup::commit(bool quiet)
{ {
return save(); return save(quiet);
} }
/* Flag indicating whether talkgroup ID access control is enabled or not. */ /* Flag indicating whether talkgroup ID access control is enabled or not. */
@ -337,7 +336,7 @@ bool TalkgroupRulesLookup::load()
if (groupVoiceList.size() == 0U) { if (groupVoiceList.size() == 0U) {
::LogError(LOG_HOST, "No group voice rules list defined!"); ::LogError(LOG_HOST, "No group voice rules list defined!");
m_locked = false; s_locked = false;
return false; return false;
} }
@ -384,14 +383,14 @@ bool TalkgroupRulesLookup::load()
/* Saves the table to the passed lookup table file. */ /* Saves the table to the passed lookup table file. */
bool TalkgroupRulesLookup::save() bool TalkgroupRulesLookup::save(bool quiet)
{ {
// Make sure file is valid // Make sure file is valid
if (m_rulesFile.length() <= 0) { if (m_rulesFile.length() <= 0) {
return false; return false;
} }
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(s_mutex);
// New list for our new group voice rules // New list for our new group voice rules
yaml::Node groupVoiceList; yaml::Node groupVoiceList;
@ -415,9 +414,9 @@ bool TalkgroupRulesLookup::save()
} }
try { try {
LogMessage(LOG_HOST, "Saving talkgroup rules file to %s", m_rulesFile.c_str()); if (!quiet)
LogInfoEx(LOG_HOST, "Saving talkgroup rules file to %s", m_rulesFile.c_str());
yaml::Serialize(newRules, m_rulesFile.c_str()); yaml::Serialize(newRules, m_rulesFile.c_str());
LogDebug(LOG_HOST, "Saved TGID config file to %s", m_rulesFile.c_str());
} }
catch (yaml::OperationException const& e) { catch (yaml::OperationException const& e) {
LogError(LOG_HOST, "Cannot save the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); LogError(LOG_HOST, "Cannot save the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message());

@ -609,8 +609,9 @@ namespace lookups
/** /**
* @brief Saves loaded talkgroup rules. * @brief Saves loaded talkgroup rules.
* @param quiet Disable logging during save operation.
*/ */
bool commit(); bool commit(bool quiet = false);
/** /**
* @brief Flag indicating whether talkgroup ID access control is enabled or not. * @brief Flag indicating whether talkgroup ID access control is enabled or not.
@ -643,8 +644,8 @@ namespace lookups
bool m_acl; bool m_acl;
bool m_stop; bool m_stop;
static std::mutex m_mutex; //! Mutex used for change locking. static std::mutex s_mutex; //!< Mutex used for change locking.
static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used.
/** /**
* @brief Loads the table from the passed lookup table file. * @brief Loads the table from the passed lookup table file.
@ -653,9 +654,10 @@ namespace lookups
bool load(); bool load();
/** /**
* @brief Saves the table to the passed lookup table file. * @brief Saves the table to the passed lookup table file.
* @param quiet Disable logging during save operation.
* @return True, if lookup table was saved, otherwise false. * @return True, if lookup table was saved, otherwise false.
*/ */
bool save(); bool save(bool quiet = false);
public: public:
/** /**

@ -163,10 +163,17 @@ bool BaseNetwork::writeActLog(const char* message)
char buffer[DATA_PACKET_LENGTH]; char buffer[DATA_PACKET_LENGTH];
uint32_t len = ::strlen(message); uint32_t len = ::strlen(message);
#if !defined(_WIN32)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
::strncpy(buffer + 11U, message, len); ::strncpy(buffer + 11U, message, len);
#if !defined(_WIN32)
#pragma GCC diagnostic pop
#endif
return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, (uint8_t*)buffer, (uint32_t)len + 11U, return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, (uint8_t*)buffer, (uint32_t)len + 11U,
RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics); RTP_END_OF_CALL_SEQ, 0U, m_useAlternatePortForDiagnostics);
} }
/* Writes the local diagnostics log to the network. */ /* Writes the local diagnostics log to the network. */
@ -184,10 +191,17 @@ bool BaseNetwork::writeDiagLog(const char* message)
char buffer[DATA_PACKET_LENGTH]; char buffer[DATA_PACKET_LENGTH];
uint32_t len = ::strlen(message); uint32_t len = ::strlen(message);
#if !defined(_WIN32)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
::strncpy(buffer + 11U, message, len); ::strncpy(buffer + 11U, message, len);
#if !defined(_WIN32)
#pragma GCC diagnostic pop
#endif
return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG }, (uint8_t*)buffer, (uint32_t)len + 11U, return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG }, (uint8_t*)buffer, (uint32_t)len + 11U,
RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics); RTP_END_OF_CALL_SEQ, 0U, m_useAlternatePortForDiagnostics);
} }
/* Writes the local status to the network. */ /* Writes the local status to the network. */
@ -209,10 +223,17 @@ bool BaseNetwork::writePeerStatus(json::object obj)
char buffer[DATA_PACKET_LENGTH]; char buffer[DATA_PACKET_LENGTH];
uint32_t len = ::strlen(json.c_str()); uint32_t len = ::strlen(json.c_str());
#if !defined(_WIN32)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
::strncpy(buffer + 11U, json.c_str(), len); ::strncpy(buffer + 11U, json.c_str(), len);
#if !defined(_WIN32)
#pragma GCC diagnostic pop
#endif
return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, (uint8_t*)buffer, (uint32_t)len + 11U, return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, (uint8_t*)buffer, (uint32_t)len + 11U,
RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics); RTP_END_OF_CALL_SEQ, 0U, m_useAlternatePortForDiagnostics);
} }
/* Writes a group affiliation to the network. */ /* Writes a group affiliation to the network. */
@ -376,7 +397,7 @@ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const
/* Helper to send a data message to the master. */ /* Helper to send a data message to the master. */
bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId,
bool queueOnly, bool useAlternatePort, uint32_t peerId, uint32_t ssrc) bool useAlternatePort, uint32_t peerId, uint32_t ssrc)
{ {
if (peerId == 0U) if (peerId == 0U)
peerId = m_peerId; peerId = m_peerId;
@ -391,17 +412,11 @@ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data
uint16_t port = udp::Socket::port(m_addr) + 1U; uint16_t port = udp::Socket::port(m_addr) + 1U;
if (udp::Socket::lookup(address, port, addr, addrLen) == 0) { if (udp::Socket::lookup(address, port, addr, addrLen) == 0) {
if (!queueOnly)
return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen);
else
m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, addr, addrLen);
} }
} }
else { else {
if (!queueOnly)
return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen); return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen);
else
m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, m_addr, m_addrLen);
} }
return true; return true;

@ -32,8 +32,8 @@
#include "common/p25/lc/LC.h" #include "common/p25/lc/LC.h"
#include "common/p25/Audio.h" #include "common/p25/Audio.h"
#include "common/nxdn/lc/RTCH.h" #include "common/nxdn/lc/RTCH.h"
#include "common/json/json.h"
#include "common/network/FrameQueue.h" #include "common/network/FrameQueue.h"
#include "common/network/json/json.h"
#include "common/network/udp/Socket.h" #include "common/network/udp/Socket.h"
#include "common/RingBuffer.h" #include "common/RingBuffer.h"
#include "common/Utils.h" #include "common/Utils.h"
@ -63,13 +63,15 @@
#define TAG_REPEATER_GRANT "RPTG" #define TAG_REPEATER_GRANT "RPTG"
#define TAG_REPEATER_KEY "RKEY" #define TAG_REPEATER_KEY "RKEY"
#define TAG_INCALL_CTRL "ICC "
#define TAG_TRANSFER "TRNS" #define TAG_TRANSFER "TRNS"
#define TAG_TRANSFER_ACT_LOG "TRNSLOG" #define TAG_TRANSFER_ACT_LOG "TRNSLOG"
#define TAG_TRANSFER_DIAG_LOG "TRNSDIAG" #define TAG_TRANSFER_DIAG_LOG "TRNSDIAG"
#define TAG_TRANSFER_STATUS "TRNSSTS" #define TAG_TRANSFER_STATUS "TRNSSTS"
#define TAG_ANNOUNCE "ANNC" #define TAG_ANNOUNCE "ANNC"
#define TAG_PEER_LINK "PRLNK" #define TAG_PEER_REPLICA "REPL"
#define MAX_PEER_PING_TIME 60U // 60 seconds #define MAX_PEER_PING_TIME 60U // 60 seconds
@ -93,25 +95,27 @@ namespace network
const uint32_t NXDN_PACKET_LENGTH = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer const uint32_t NXDN_PACKET_LENGTH = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer
const uint32_t ANALOG_PACKET_LENGTH = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer const uint32_t ANALOG_PACKET_LENGTH = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer
const uint32_t HA_PARAMS_ENTRY_LEN = 20U;
/** /**
* @brief Network Peer Connection Status * @brief Network Peer Connection Status
* @ingroup network_core * @ingroup network_core
*/ */
enum NET_CONN_STATUS { enum NET_CONN_STATUS {
// Common States // Common States
NET_STAT_WAITING_CONNECT, //! Waiting for Connection NET_STAT_WAITING_CONNECT, //!< Waiting for Connection
NET_STAT_WAITING_LOGIN, //! Waiting for Login NET_STAT_WAITING_LOGIN, //!< Waiting for Login
NET_STAT_WAITING_AUTHORISATION, //! Waiting for Authorization NET_STAT_WAITING_AUTHORISATION, //!< Waiting for Authorization
NET_STAT_WAITING_CONFIG, //! Waiting for Configuration NET_STAT_WAITING_CONFIG, //!< Waiting for Configuration
NET_STAT_RUNNING, //! Peer Running NET_STAT_RUNNING, //!< Peer Running
// Master States // Master States
NET_STAT_RPTL_RECEIVED, //! Login Received NET_STAT_RPTL_RECEIVED, //!< Login Received
NET_STAT_CHALLENGE_SENT, //! Authentication Challenge Sent NET_STAT_CHALLENGE_SENT, //!< Authentication Challenge Sent
NET_STAT_MST_RUNNING, //! Master Running NET_STAT_MST_RUNNING, //!< Master Running
NET_STAT_INVALID = 0x7FFFFFF //! Invalid NET_STAT_INVALID = 0x7FFFFFF //!< Invalid
}; };
/** /**
@ -119,20 +123,21 @@ namespace network
* @ingroup network_core * @ingroup network_core
*/ */
enum NET_CONN_NAK_REASON { enum NET_CONN_NAK_REASON {
NET_CONN_NAK_GENERAL_FAILURE, //! General Failure NET_CONN_NAK_GENERAL_FAILURE, //!< General Failure
NET_CONN_NAK_MODE_NOT_ENABLED, //! Mode Not Enabled NET_CONN_NAK_MODE_NOT_ENABLED, //!< Mode Not Enabled
NET_CONN_NAK_ILLEGAL_PACKET, //! Illegal Packet NET_CONN_NAK_ILLEGAL_PACKET, //!< Illegal Packet
NET_CONN_NAK_FNE_UNAUTHORIZED, //! FNE Unauthorized NET_CONN_NAK_FNE_UNAUTHORIZED, //!< FNE Unauthorized
NET_CONN_NAK_BAD_CONN_STATE, //! Bad Connection State NET_CONN_NAK_BAD_CONN_STATE, //!< Bad Connection State
NET_CONN_NAK_INVALID_CONFIG_DATA, //! Invalid Configuration Data NET_CONN_NAK_INVALID_CONFIG_DATA, //!< Invalid Configuration Data
NET_CONN_NAK_PEER_RESET, //! Peer Reset NET_CONN_NAK_PEER_RESET, //!< Peer Reset
NET_CONN_NAK_PEER_ACL, //! Peer ACL NET_CONN_NAK_PEER_ACL, //!< Peer ACL
NET_CONN_NAK_FNE_MAX_CONN, //! FNE Maximum Connections NET_CONN_NAK_FNE_MAX_CONN, //!< FNE Maximum Connections
NET_CONN_NAK_FNE_DUPLICATE_CONN, //!< FNE Duplicate Connection
NET_CONN_NAK_INVALID = 0xFFFF //! Invalid NET_CONN_NAK_INVALID = 0xFFFF //!< Invalid
}; };
/** /**
@ -141,11 +146,219 @@ namespace network
* @ingroup network_core * @ingroup network_core
*/ */
enum CONTROL_BYTE { enum CONTROL_BYTE {
NET_CTRL_GRANT_DEMAND = 0x80U, //! Grant Demand NET_CTRL_GRANT_DEMAND = 0x80U, //!< Grant Demand
NET_CTRL_GRANT_DENIAL = 0x40U, //! Grant Denial NET_CTRL_GRANT_DENIAL = 0x40U, //!< Grant Denial
NET_CTRL_SWITCH_OVER = 0x20U, //! Call Source RID Switch Over NET_CTRL_SWITCH_OVER = 0x20U, //!< Call Source RID Switch Over
NET_CTRL_GRANT_ENCRYPT = 0x08U, //! Grant Encrypt NET_CTRL_GRANT_ENCRYPT = 0x08U, //!< Grant Encrypt
NET_CTRL_U2U = 0x01U, //! Unit-to-Unit NET_CTRL_U2U = 0x01U, //!< Unit-to-Unit
};
/**
* @brief RTP Stream Multiplex Validation Return Codes
* @ingroup network_core
*/
enum MULTIPLEX_RET_CODE {
MUX_VALID_SUCCESS = 0U, //!< Successful Validation
MUX_LOST_FRAMES = 1U, //!< Lost Frames
MUX_OUT_OF_ORDER = 2U //!< Out-of-Order
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Handles dealing with maintaining RTP sequencing for multiple multiplexed RTP streams.
* @ingroup fne_network
*/
class HOST_SW_API RTPStreamMultiplex {
public:
auto operator=(RTPStreamMultiplex&) -> RTPStreamMultiplex& = delete;
auto operator=(RTPStreamMultiplex&&) -> RTPStreamMultiplex& = delete;
RTPStreamMultiplex(RTPStreamMultiplex&) = delete;
/**
* @brief Initializes a new instance of the RTPStreamMultiplex class.
*/
RTPStreamMultiplex() :
m_mutex(),
m_streamSeqNos()
{
/* stub */
}
/**
* @brief Finalizes a instance of the RTPStreamMultiplex class.
*/
~RTPStreamMultiplex()
{
m_streamSeqNos.clear();
}
/**
* @brief Helper to verify the given RTP sequence for the given multiplexed RTP stream.
* @param streamId Stream ID.
* @param pktSeq Packet Sequence.
* @param func Network function.
* @param[out] lastRxSeq Last Received Sequence.
* @return MULTIPLEX_RET_CODE Return code.
*/
MULTIPLEX_RET_CODE verifyStream(uint64_t streamId, uint16_t pktSeq, uint8_t func, uint16_t* lastRxSeq)
{
MULTIPLEX_RET_CODE ret = MUX_VALID_SUCCESS;
if (pktSeq == RTP_END_OF_CALL_SEQ) {
// only reset packet sequences if we're a PROTOCOL or RPTC function
if ((func == NET_FUNC::PROTOCOL) || (func == NET_FUNC::RPTC)) {
erasePktSeq(streamId); // attempt to erase packet sequence for the stream
}
} else {
if (hasPktSeq(streamId)) {
*lastRxSeq = getPktSeq(streamId);
if (*lastRxSeq == RTP_END_OF_CALL_SEQ) {
// reset the received sequence back to 0
setPktSeq(streamId, 0U);
}
else {
if ((pktSeq >= *lastRxSeq) || (pktSeq == 0U)) {
// if the sequence isn't 0, and is greater then the last received sequence + 1 frame
// assume a packet was lost
if ((pktSeq != 0U) && pktSeq >= *lastRxSeq + 1U) {
ret = MUX_LOST_FRAMES;
}
setPktSeq(streamId, pktSeq);
}
else {
if (pktSeq < *lastRxSeq) {
ret = MUX_OUT_OF_ORDER;
}
}
}
}
}
return ret;
}
/**
* @brief Helper to return the current count of multiplexed RTP streams.
* @returns size_t Count of stored streams.
*/
size_t streamCount()
{
return m_streamSeqNos.size();
}
/**
* @brief Helper to determine if the given multiplexed stream has a stored RTP sequence.
* @param streamId Stream ID.
* @returns bool True, if stream ID has a stored RTP sequence, otherwise false.
*/
bool hasPktSeq(uint64_t streamId)
{
bool ret = false;
std::lock_guard<std::recursive_mutex> lock(m_mutex);
// determine if the stream has a current sequence no and return
{
auto it = m_streamSeqNos.find(streamId);
if (it == m_streamSeqNos.end()) {
ret = false;
}
else {
ret = true;
}
}
return ret;
}
/**
* @brief Helper to get the stored RTP sequence for the given multiplexed stream.
* @param streamId Stream ID.
* @returns uint16_t Sequence number.
*/
uint16_t getPktSeq(uint64_t streamId)
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
// find the current sequence no and return
uint32_t pktSeq = 0U;
{
auto it = m_streamSeqNos.find(streamId);
if (it == m_streamSeqNos.end()) {
pktSeq = RTP_END_OF_CALL_SEQ;
} else {
pktSeq = m_streamSeqNos[streamId];
}
}
return pktSeq;
}
/**
* @brief Helper to set the stored RTP sequence for the given multiplexed stream.
* @param streamId Stream ID.
* @param seq Sequence number.
*/
void setPktSeq(uint64_t streamId, uint16_t seq)
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto it = m_streamSeqNos.find(streamId);
if (it == m_streamSeqNos.end()) {
m_streamSeqNos.insert({streamId, seq});
} else {
m_streamSeqNos[streamId] = seq;
}
}
/**
* @brief Helper to increment the stored RTP sequence for the given multiplexed stream.
* @param streamId Stream ID.
* @returns uint16_t Incremented packet sequence.
*/
uint16_t incPktSeq(uint64_t streamId)
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
uint32_t pktSeq = 0U;
auto it = m_streamSeqNos.find(streamId);
if (it == m_streamSeqNos.end()) {
m_streamSeqNos.insert({streamId, pktSeq});
} else {
pktSeq = m_streamSeqNos[streamId];
++pktSeq;
if (pktSeq > (RTP_END_OF_CALL_SEQ - 1U))
pktSeq = 0U;
m_streamSeqNos[streamId] = pktSeq;
}
return pktSeq;
}
/**
* @brief Helper to erase the stored RTP sequence for the given multiplexed stream.
* @param streamId Stream ID.
*/
void erasePktSeq(uint64_t streamId)
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
// find the sequence no and erase
{
auto entry = m_streamSeqNos.find(streamId);
if (entry != m_streamSeqNos.end()) {
m_streamSeqNos.erase(streamId);
}
}
}
private:
std::recursive_mutex m_mutex;
std::unordered_map<uint64_t, uint16_t> m_streamSeqNos;
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -183,6 +396,26 @@ namespace network
/** /**
* @brief Writes a grant request to the network. * @brief Writes a grant request to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the group affiliation
* announcement message. The message is 24 bytes in length.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | Source ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Source ID | Destination ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Destination ID | Reserved |S| Reserverd |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Mode | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param mode Digital Mode. * @param mode Digital Mode.
* @param srcId Source Radio ID. * @param srcId Source Radio ID.
* @param dstId Destination Radio ID. * @param dstId Destination Radio ID.
@ -202,6 +435,22 @@ namespace network
/** /**
* @brief Writes the local activity log to the network. * @brief Writes the local activity log to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the activity log message.
* The message is variable length bytes.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | Log Message . |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Log Message ................................................. |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param message Textual string to send as activity log information. * @param message Textual string to send as activity log information.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
@ -209,13 +458,100 @@ namespace network
/** /**
* @brief Writes the local diagnostic logs to the network. * @brief Writes the local diagnostic logs to the network.
* @param message Textual string to send as diagnostic log information. * \code{.unparsed}
* Below is the representation of the data layout for the diagnostic log message.
* The message is variable length bytes.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | Log Message . |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Log Message ................................................. |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
virtual bool writeDiagLog(const char* message); virtual bool writeDiagLog(const char* message);
/** /**
* @brief Writes the local status to the network. * @brief Writes the local status to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the peer status message.
* The message is variable length bytes.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | Variable |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Length JSON Payload ......................................... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* The JSON payload is variable length and looks like this:
* {
* "state": <Modem State>,
* "isTxCW": <Boolean flag indicating if the end-point is transmitting CW>,
* "fixedMode": <Boolean flag indicating if the end-point is operating in fixed mode>,
* "dmrTSCCEnable": <Boolean flag indicating whether or not dedicated DMR TSCC is enabled>,
* "dmrCC": <Boolean flag indicating whether or not DMR CC is enabled>,
* "p25CtrlEnable": <Boolean flag indicating whether or not dedicatd P25 control is enabled>,
* "p25CC": <Boolean flag indicating whether or not P25 CC is enabled>,
* "nxdnCtrlEnable": <Boolean flag indicating whether or not dedicatd NXDN control is enabled>,
* "nxdnCC": <Boolean flag indicating whether or not NXDN CC is enabled>,
* "tx": <Boolean flag indicating whether end-point is transmitting>,
* "channelId": <Channel ID from the IDEN channel bandplan>,
* "channelNo": <Channel Number from the IDEN channel bandplan>,
* "lastDstId": <Last destination ID transmitted, may revert to 0 after a call ends>,
* "lastSrcId": <Last source ID transmitted, may revert to 0 after a call ends>,
* "peerId": <Unique Peer Identification Number>,
* "sysId": <System ID>,
* "siteId": <Site ID>,
* "p25RfssId": <P25 RFSS ID>,
* "p25NetId": <P25 WACN/Network ID>,
* "p25NAC": <P25 NAC>,
* "vcChannels": [
* {
* "channelId": <Channel ID from the IDEN channel bandplan>,
* "channelNo": <Channel Number from the IDEN channel bandplan>,
* "tx": <Boolean flag indicating whether end-point is transmitting>,
* "lastDstId": <Last destination ID transmitted, may revert to 0 after a call ends>,
* "lastSrcId": <Last source ID transmitted, may revert to 0 after a call ends>,
* }
* ],
* "modem": {
* "portType": "<Port Type>",
* "modemPort": "<Modem Port>",
* "portSpeed": <Port Speed>,
* "rxLevel": <Configured Rx Level>,
* "cwTxLevel": <Configured CWID Tx Level>,
* "dmrTxLevel": <Configured DMR Tx Level>,
* "p25TxLevel": <Configured P25 Tx Level>,
* "nxdnTxLevel": <Configured NXDN Tx Level>,
* "rxDCOffset": <Configured Rx DC Offset>,
* "txDCOffset": <Configured Tx DC Offset>,
* "fdmaPremables": <Configured FDMA Preambles>,
* "dmrRxDelay": <Configured DMR Rx Delay>,
* "p25CorrCount": <Configured P25 Correlation Count>,
* "rxFrequency": <Peer Rx Frequency in Hz>,
* "txFrequency": <Peer Tx Frequency in Hz>,
* "rxTuning": <Peer Rx Tuning Offset>,
* "txTuning": <Peer Tx Tuning Offset>,
* "rxFrequencyEffective": <Peer Rx Effective Frequency in Hz>,
* "txFrequencyEffective": <Peer Tx Effective Frequency in Hz>,
* "v24Connected": <Boolean indicating V.24 is connected (if a V.24 modem)>,
* "protoVer": <Protocol version>
* }
* }
* \endcode
* @param obj JSON object representing the local peer status. * @param obj JSON object representing the local peer status.
* @returns bool True, if peer status was sent, otherwise false. * @returns bool True, if peer status was sent, otherwise false.
*/ */
@ -242,6 +578,16 @@ namespace network
virtual bool announceGroupAffiliation(uint32_t srcId, uint32_t dstId); virtual bool announceGroupAffiliation(uint32_t srcId, uint32_t dstId);
/** /**
* @brief Writes a group affiliation removal to the network. * @brief Writes a group affiliation removal to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the unit registration
* announcement message. The message is 3 bytes in length.
*
* Byte 0 1 2
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Source ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param srcId Source Radio ID. * @param srcId Source Radio ID.
* @returns bool True, if group affiliation announcement was sent, otherwise false. * @returns bool True, if group affiliation announcement was sent, otherwise false.
*/ */
@ -265,6 +611,16 @@ namespace network
virtual bool announceUnitRegistration(uint32_t srcId); virtual bool announceUnitRegistration(uint32_t srcId);
/** /**
* @brief Writes a unit deregistration to the network. * @brief Writes a unit deregistration to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the unit deregistration
* announcement message. The message is 3 bytes in length.
*
* Byte 0 1 2
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Source ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param srcId Source Radio ID. * @param srcId Source Radio ID.
* @returns bool True, if unit deregistration announcement was sent, otherwise false. * @returns bool True, if unit deregistration announcement was sent, otherwise false.
*/ */
@ -272,6 +628,22 @@ namespace network
/** /**
* @brief Writes a complete update of the peer affiliation list to the network. * @brief Writes a complete update of the peer affiliation list to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the repeater/end point login message.
* The message is variable bytes in length.
*
* Each affiliation update entry is 7 bytes.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Number of entries |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Entry: Source ID | E: Dst Id |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Entry: Destination ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param affs Complete map of peer unit affiliations. * @param affs Complete map of peer unit affiliations.
* @returns bool True, if affiliation update announcement was sent, otherwise false. * @returns bool True, if affiliation update announcement was sent, otherwise false.
*/ */
@ -279,6 +651,20 @@ namespace network
/** /**
* @brief Writes a complete update of the peer's voice channel list to the network. * @brief Writes a complete update of the peer's voice channel list to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the repeater/end point login message.
* The message is variable bytes in length.
*
* Each peer ID entry is 4 bytes.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Number of entries |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Entry: Peer ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param peers List of voice channel peers. * @param peers List of voice channel peers.
* @returns bool True, if peer update announcement was sent, otherwise false. * @returns bool True, if peer update announcement was sent, otherwise false.
*/ */
@ -348,15 +734,13 @@ namespace network
* @param length Length of buffer to write. * @param length Length of buffer to write.
* @param pktSeq RTP packet sequence. * @param pktSeq RTP packet sequence.
* @param streamId Stream ID. * @param streamId Stream ID.
* @param queueOnly Flag indicating this message should be queued instead of send immediately.
* @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics). * @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics).
* @param peerId If non-zero, overrides the peer ID sent in the packet to the master. * @param peerId If non-zero, overrides the peer ID sent in the packet to the master.
* @param ssrc If non-zero, overrides the RTP synchronization source ID sent in the packet to the master. * @param ssrc If non-zero, overrides the RTP synchronization source ID sent in the packet to the master.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length,
uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U, uint16_t pktSeq, uint32_t streamId, bool useAlternatePort = false, uint32_t peerId = 0U, uint32_t ssrc = 0U);
uint32_t ssrc = 0U);
// Digital Mobile Radio // Digital Mobile Radio
/** /**
@ -592,6 +976,9 @@ namespace network
* | Reserved | * | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* *
* S = Slot Number (clear Slot 1, set Slot 2)
* G = Group Flag
*
* The data starting at offset 20 for 33 bytes of the raw DMR frame. * The data starting at offset 20 for 33 bytes of the raw DMR frame.
* *
* DMR frame message has 2 trailing bytes: * DMR frame message has 2 trailing bytes:
@ -754,6 +1141,8 @@ namespace network
* | Blk to Flw | Current Block | DUID | Frame Length | * | Blk to Flw | Current Block | DUID | Frame Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* *
* C = Confirmed PDU Flag
*
* The data starting at offset 24 for variable number of bytes (DUID dependant) * The data starting at offset 24 for variable number of bytes (DUID dependant)
* is the P25 frame. * is the P25 frame.
* \endcode * \endcode
@ -789,6 +1178,8 @@ namespace network
* | | Frame Length | * | | Frame Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* *
* G = Group Flag
*
* The data starting at offset 24 for 48 bytes if the raw NXDN frame. * The data starting at offset 24 for 48 bytes if the raw NXDN frame.
* \endcode * \endcode
* @param[out] length Length of network message buffer. * @param[out] length Length of network message buffer.

@ -172,17 +172,21 @@ bool FrameQueue::write(const uint8_t* message, uint32_t length, uint32_t streamI
/* Cache message to frame queue. */ /* Cache message to frame queue. */
void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, void FrameQueue::enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId,
OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) uint32_t peerId, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen)
{ {
enqueueMessage(message, length, streamId, peerId, peerId, opcode, rtpSeq, addr, addrLen); enqueueMessage(queue, message, length, streamId, peerId, peerId, opcode, rtpSeq, addr, addrLen);
} }
/* Cache message to frame queue. */ /* Cache message to frame queue. */
void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, void FrameQueue::enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId,
uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen)
{ {
if (queue == nullptr) {
LogError(LOG_NET, "FrameQueue::enqueueMessage(), queue is null");
return;
}
if (message == nullptr) { if (message == nullptr) {
LogError(LOG_NET, "FrameQueue::enqueueMessage(), message is null"); LogError(LOG_NET, "FrameQueue::enqueueMessage(), message is null");
return; return;
@ -207,7 +211,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_
dgram->address = addr; dgram->address = addr;
dgram->addrLen = addrLen; dgram->addrLen = addrLen;
m_buffers.push_back(dgram); queue->push(dgram);
} }
/* Helper method to clear any tracked stream timestamps. */ /* Helper method to clear any tracked stream timestamps. */

@ -90,6 +90,7 @@ namespace network
/** /**
* @brief Cache message to frame queue. * @brief Cache message to frame queue.
* @param[in] queue Queue of messages.
* @param[in] message Message buffer to frame and queue. * @param[in] message Message buffer to frame and queue.
* @param length Length of message. * @param length Length of message.
* @param streamId Message stream ID. * @param streamId Message stream ID.
@ -99,10 +100,11 @@ namespace network
* @param addr IP address to write data to. * @param addr IP address to write data to.
* @param addrLen * @param addrLen
*/ */
void enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, void enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId,
OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); uint32_t peerId, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen);
/** /**
* @brief Cache message to frame queue. * @brief Cache message to frame queue.
* @param[in] queue Queue of messages.
* @param[in] message Message buffer to frame and queue. * @param[in] message Message buffer to frame and queue.
* @param length Length of message. * @param length Length of message.
* @param streamId Message stream ID. * @param streamId Message stream ID.
@ -113,8 +115,8 @@ namespace network
* @param addr IP address to write data to. * @param addr IP address to write data to.
* @param addrLen * @param addrLen
*/ */
void enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, void enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId,
uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen);
/** /**
* @brief Helper method to clear any tracked stream timestamps. * @brief Helper method to clear any tracked stream timestamps.

@ -10,8 +10,8 @@
#include "Defines.h" #include "Defines.h"
#include "common/edac/CRC.h" #include "common/edac/CRC.h"
#include "common/edac/SHA256.h" #include "common/edac/SHA256.h"
#include "common/json/json.h"
#include "common/network/RPCHeader.h" #include "common/network/RPCHeader.h"
#include "common/network/json/json.h"
#include "common/Log.h" #include "common/Log.h"
#include "common/Thread.h" #include "common/Thread.h"
#include "common/Utils.h" #include "common/Utils.h"
@ -286,7 +286,7 @@ void NetRPC::defaultResponse(json::object& reply, std::string message, StatusTyp
bool NetRPC::open() bool NetRPC::open()
{ {
if (m_debug) if (m_debug)
LogMessage(LOG_NET, "Opening RPC network"); LogInfoEx(LOG_NET, "Opening RPC network");
// generate AES256 key // generate AES256 key
size_t size = m_password.size(); size_t size = m_password.size();
@ -313,16 +313,56 @@ bool NetRPC::open()
void NetRPC::close() void NetRPC::close()
{ {
if (m_debug) if (m_debug)
LogMessage(LOG_NET, "Closing RPC network"); LogInfoEx(LOG_NET, "Closing RPC network");
m_socket->close(); m_socket->close();
} }
/* Helper to register an RPC handler. */
bool NetRPC::registerHandler(uint16_t func, RPCType handler)
{
// 32767 is the maximum possible function
if (func > RPC_MAX_FUNC)
return false;
auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&](RPCHandlerMapPair x) { return x.first == func; });
if (it != m_handlers.end()) {
LogError(LOG_HOST, "NetRPC::registerHandler() can't register RPC $%04X already registered. BUGBUG.", func);
return false; // handler is already registered
}
if (m_debug)
LogDebugEx(LOG_HOST, "NetRPC::registerHandler()", "registering RPC $%04X", func);
m_handlers[func] = handler;
return true;
}
/* Helper to unregister an RPC handler. */
bool NetRPC::unregisterHandler(uint16_t func)
{
// 32767 is the maximum possible function
if (func > RPC_MAX_FUNC)
return false;
auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&](RPCHandlerMapPair x) { return x.first == func; });
if (it != m_handlers.end()) {
if (m_debug)
LogDebugEx(LOG_HOST, "NetRPC::registerHandler()", "unregistering RPC $%04X", func);
m_handlers.erase(func);
return true;
}
return false;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Private Class Members // Private Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Writes an RPC reply to the network. */ /* Writes an RPC reply to the network. */
bool NetRPC::reply(uint16_t func, json::object& reply, sockaddr_storage& address, uint32_t addrLen) bool NetRPC::reply(uint16_t func, json::object& reply, sockaddr_storage& address, uint32_t addrLen)

@ -21,9 +21,9 @@
#endif // defined(_WIN32) #endif // defined(_WIN32)
#include "common/Defines.h" #include "common/Defines.h"
#include "common/json/json.h"
#include "common/network/udp/Socket.h" #include "common/network/udp/Socket.h"
#include "common/network/RawFrameQueue.h" #include "common/network/RawFrameQueue.h"
#include "common/network/json/json.h"
#include <string> #include <string>
#include <cstdint> #include <cstdint>
@ -63,13 +63,17 @@ namespace network
* @brief Status/Response Codes * @brief Status/Response Codes
*/ */
enum StatusType { enum StatusType {
OK = 200, //! OK 200 OK = 200, //!< OK 200
BAD_REQUEST = 400, //! Bad Request 400 BAD_REQUEST = 400, //!< Bad Request 400
INVALID_ARGS = 401, //! Invalid Arguments 401 INVALID_ARGS = 401, //!< Invalid Arguments 401
UNHANDLED_REQUEST = 402, //! Unhandled Request 402 UNHANDLED_REQUEST = 402, //!< Unhandled Request 402
} status; } status;
auto operator=(NetRPC&) -> NetRPC& = delete;
auto operator=(NetRPC&&) -> NetRPC& = delete;
NetRPC(NetRPC&) = delete;
/** /**
* @brief Initializes a new instance of the NetRPC class. * @brief Initializes a new instance of the NetRPC class.
* @param address Network Hostname/IP address to connect to. * @param address Network Hostname/IP address to connect to.
@ -137,13 +141,15 @@ namespace network
* @brief Helper to register an RPC handler. * @brief Helper to register an RPC handler.
* @param func Function opcode. * @param func Function opcode.
* @param handler Function handler. * @param handler Function handler.
* @returns bool True, if handler is registered, otherwise false.
*/ */
void registerHandler(uint16_t func, RPCType handler) { m_handlers[func] = handler; } bool registerHandler(uint16_t func, RPCType handler);
/** /**
* @brief Helper to unregister an RPC handler. * @brief Helper to unregister an RPC handler.
* @param func Function opcode. * @param func Function opcode.
* @returns bool True, if handler is unregistered, otherwise false.
*/ */
void unregisterHandler(uint16_t func) { m_handlers.erase(func); } bool unregisterHandler(uint16_t func);
private: private:
std::string m_address; std::string m_address;
@ -156,6 +162,7 @@ namespace network
std::string m_password; std::string m_password;
typedef std::pair<const uint16_t, RPCType> RPCHandlerMapPair;
std::map<uint16_t, RPCType> m_handlers; std::map<uint16_t, RPCType> m_handlers;
std::map<uint16_t, bool> m_handlerReplied; std::map<uint16_t, bool> m_handlerReplied;

@ -9,8 +9,8 @@
*/ */
#include "Defines.h" #include "Defines.h"
#include "common/edac/SHA256.h" #include "common/edac/SHA256.h"
#include "common/network/json/json.h"
#include "common/p25/kmm/KMMFactory.h" #include "common/p25/kmm/KMMFactory.h"
#include "common/json/json.h"
#include "common/Log.h" #include "common/Log.h"
#include "common/Utils.h" #include "common/Utils.h"
#include "network/Network.h" #include "network/Network.h"
@ -25,7 +25,13 @@ using namespace network;
// Constants // Constants
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#define DEFAULT_RETRY_TIME 10U // 10 seconds
#define DUPLICATE_CONN_RETRY_TIME 3600U // 60 minutes
#define MAX_RETRY_BEFORE_RECONNECT 4U #define MAX_RETRY_BEFORE_RECONNECT 4U
#define MAX_RETRY_HA_RECONNECT 2U
#define MAX_RETRY_DUP_RECONNECT 2U
#define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds #define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -41,6 +47,10 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_pktLastSeq(0U), m_pktLastSeq(0U),
m_address(address), m_address(address),
m_port(port), m_port(port),
m_configuredAddress(address),
m_configuredPort(port),
m_haIPs(),
m_currentHAIP(0U),
m_password(password), m_password(password),
m_enabled(false), m_enabled(false),
m_dmrEnabled(dmr), m_dmrEnabled(dmr),
@ -52,12 +62,16 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_ridLookup(nullptr), m_ridLookup(nullptr),
m_tidLookup(nullptr), m_tidLookup(nullptr),
m_salt(nullptr), m_salt(nullptr),
m_retryTimer(1000U, 10U), m_retryTimer(1000U, DEFAULT_RETRY_TIME),
m_retryCount(0U), m_retryCount(0U),
m_maxRetryCount(MAX_RETRY_BEFORE_RECONNECT),
m_flaggedDuplicateConn(false),
m_timeoutTimer(1000U, MAX_PEER_PING_TIME), m_timeoutTimer(1000U, MAX_PEER_PING_TIME),
m_pingsReceived(0U),
m_pktSeq(0U), m_pktSeq(0U),
m_loginStreamId(0U), m_loginStreamId(0U),
m_metadata(nullptr), m_metadata(nullptr),
m_mux(nullptr),
m_remotePeerId(0U), m_remotePeerId(0U),
m_promiscuousPeer(false), m_promiscuousPeer(false),
m_userHandleProtocol(false), m_userHandleProtocol(false),
@ -84,6 +98,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_rxAnalogStreamId = 0U; m_rxAnalogStreamId = 0U;
m_metadata = new PeerMetadata(); m_metadata = new PeerMetadata();
m_mux = new RTPStreamMultiplex();
} }
/* Finalizes a instance of the Network class. */ /* Finalizes a instance of the Network class. */
@ -93,6 +108,7 @@ Network::~Network()
delete[] m_salt; delete[] m_salt;
delete[] m_rxDMRStreamId; delete[] m_rxDMRStreamId;
delete m_metadata; delete m_metadata;
delete m_mux;
} }
/* Resets the DMR ring buffer for the given slot. */ /* Resets the DMR ring buffer for the given slot. */
@ -187,19 +203,24 @@ void Network::clock(uint32_t ms)
m_retryTimer.clock(ms); m_retryTimer.clock(ms);
if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) {
if (m_enabled) { if (m_enabled) {
if (m_retryCount > MAX_RETRY_BEFORE_RECONNECT) { if (m_retryCount > m_maxRetryCount) {
m_retryCount = 0U; if (m_flaggedDuplicateConn) {
LogError(LOG_NET, "PEER %u exceeded maximum duplicate connection retries, increasing delay between connection attempts", m_peerId);
}
LogError(LOG_NET, "PEER %u connection to the master has timed out, retrying connection, remotePeerId = %u", m_peerId, m_remotePeerId); LogError(LOG_NET, "PEER %u connection to the master has timed out, retrying connection, remotePeerId = %u", m_peerId, m_remotePeerId);
close(); close();
open(); open();
m_retryCount = 0U;
m_retryTimer.start(); m_retryTimer.start();
return; return;
} }
bool ret = m_socket->open(m_addr.ss_family); bool ret = m_socket->open(m_addr.ss_family);
if (ret) { if (ret) {
m_socket->recvBufSize(262144U); // 256K recv buffer
ret = writeLogin(); ret = writeLogin();
if (!ret) { if (!ret) {
m_retryTimer.start(); m_retryTimer.start();
@ -280,11 +301,6 @@ void Network::clock(uint32_t ms)
m_pktSeq = rtpHeader.getSequence(); m_pktSeq = rtpHeader.getSequence();
if (m_pktSeq == RTP_END_OF_CALL_SEQ) {
m_pktSeq = 0U;
m_pktLastSeq = 0U;
}
// process incoming message function opcodes // process incoming message function opcodes
switch (fneHeader.getFunction()) { switch (fneHeader.getFunction()) {
case NET_FUNC::PROTOCOL: // Protocol case NET_FUNC::PROTOCOL: // Protocol
@ -296,7 +312,7 @@ void Network::clock(uint32_t ms)
break; break;
} }
// process incomfing message subfunction opcodes // process incoming message subfunction opcodes
switch (fneHeader.getSubFunction()) { switch (fneHeader.getSubFunction()) {
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame
{ {
@ -311,6 +327,23 @@ void Network::clock(uint32_t ms)
if (m_promiscuousPeer) { if (m_promiscuousPeer) {
m_rxDMRStreamId[slotNo] = streamId; m_rxDMRStreamId[slotNo] = streamId;
m_pktLastSeq = m_pktSeq; m_pktLastSeq = m_pktSeq;
uint16_t lastRxSeq = 0U;
MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence());
}
else if (ret == MUX_OUT_OF_ORDER) {
LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq);
}
#if DEBUG_RTP_MUX
else {
LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId);
}
#endif
} }
else { else {
if (m_rxDMRStreamId[slotNo] == 0U) { if (m_rxDMRStreamId[slotNo] == 0U) {
@ -325,21 +358,36 @@ void Network::clock(uint32_t ms)
} }
else { else {
if (m_rxDMRStreamId[slotNo] == streamId) { if (m_rxDMRStreamId[slotNo] == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) { uint16_t lastRxSeq = 0U;
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "DMR Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogWarning(LOG_NET, "DMR Slot %u stream %u possible lost frames; got %u, expected %u",
slotNo, streamId, m_pktSeq, lastRxSeq);
} }
else if (ret == MUX_OUT_OF_ORDER) {
LogWarning(LOG_NET, "DMR Slot %u stream %u out-of-order; got %u, expected %u",
slotNo, streamId, m_pktSeq, lastRxSeq);
} }
#if DEBUG_RTP_MUX
m_pktLastSeq = m_pktSeq; else {
LogDebugEx(LOG_NET, "Network::clock()", "DMR Slot %u valid seq, seq = %u, streamId = %u", slotNo, rtpHeader.getSequence(), streamId);
} }
#endif
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxDMRStreamId[slotNo] = 0U; m_rxDMRStreamId[slotNo] = 0U;
} }
} }
} }
// check if we need to skip this stream -- a non-zero stream ID means the network client is locked
// to receiving a specific stream; a zero stream ID means the network is promiscuously
// receiving streams sent to this peer
if (m_rxDMRStreamId[slotNo] != 0U && m_rxDMRStreamId[slotNo] != streamId) {
break;
}
}
if (m_debug) if (m_debug)
Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer.get(), length);
if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD)) if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD))
@ -363,6 +411,23 @@ void Network::clock(uint32_t ms)
if (m_promiscuousPeer) { if (m_promiscuousPeer) {
m_rxP25StreamId = streamId; m_rxP25StreamId = streamId;
m_pktLastSeq = m_pktSeq; m_pktLastSeq = m_pktSeq;
uint16_t lastRxSeq = 0U;
MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence());
}
else if (ret == MUX_OUT_OF_ORDER) {
LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq);
}
#if DEBUG_RTP_MUX
else {
LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId);
}
#endif
} }
else { else {
if (m_rxP25StreamId == 0U) { if (m_rxP25StreamId == 0U) {
@ -377,21 +442,37 @@ void Network::clock(uint32_t ms)
} }
else { else {
if (m_rxP25StreamId == streamId) { if (m_rxP25StreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) { uint16_t lastRxSeq = 0U;
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "P25 Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogWarning(LOG_NET, "P25 stream %u possible lost frames; got %u, expected %u",
streamId, m_pktSeq, lastRxSeq);
} }
else if (ret == MUX_OUT_OF_ORDER) {
LogWarning(LOG_NET, "P25 stream %u out-of-order; got %u, expected %u",
streamId, m_pktSeq, lastRxSeq);
} }
#if DEBUG_RTP_MUX
m_pktLastSeq = m_pktSeq; else {
LogDebugEx(LOG_NET, "Network::clock()", "P25 valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId);
} }
#endif
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxP25StreamId = 0U; m_rxP25StreamId = 0U;
} }
} }
} }
// check if we need to skip this stream -- a non-zero stream ID means the network client is locked
// to receiving a specific stream; a zero stream ID means the network is promiscuously
// receiving streams sent to this peer
if (m_rxP25StreamId != 0U && m_rxP25StreamId != streamId) {
break;
}
}
if (m_debug) if (m_debug)
Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length);
if (length > 512) if (length > 512)
@ -424,6 +505,23 @@ void Network::clock(uint32_t ms)
if (m_promiscuousPeer) { if (m_promiscuousPeer) {
m_rxNXDNStreamId = streamId; m_rxNXDNStreamId = streamId;
m_pktLastSeq = m_pktSeq; m_pktLastSeq = m_pktSeq;
uint16_t lastRxSeq = 0U;
MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence());
}
else if (ret == MUX_OUT_OF_ORDER) {
LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq);
}
#if DEBUG_RTP_MUX
else {
LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId);
}
#endif
} }
else { else {
if (m_rxNXDNStreamId == 0U) { if (m_rxNXDNStreamId == 0U) {
@ -438,21 +536,36 @@ void Network::clock(uint32_t ms)
} }
else { else {
if (m_rxNXDNStreamId == streamId) { if (m_rxNXDNStreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) { uint16_t lastRxSeq = 0U;
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "NXDN Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogWarning(LOG_NET, "NXDN stream %u possible lost frames; got %u, expected %u",
streamId, m_pktSeq, lastRxSeq);
} }
else if (ret == MUX_OUT_OF_ORDER) {
LogWarning(LOG_NET, "NXDN stream %u out-of-order; got %u, expected %u",
streamId, m_pktSeq, lastRxSeq);
} }
#if DEBUG_RTP_MUX
m_pktLastSeq = m_pktSeq; else {
LogDebugEx(LOG_NET, "Network::clock()", "NXDN valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId);
} }
#endif
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxNXDNStreamId = 0U; m_rxNXDNStreamId = 0U;
} }
} }
} }
// check if we need to skip this stream -- a non-zero stream ID means the network client is locked
// to receiving a specific stream; a zero stream ID means the network is promiscuously
// receiving streams sent to this peer
if (m_rxNXDNStreamId != 0U && m_rxNXDNStreamId != streamId) {
break;
}
}
if (m_debug) if (m_debug)
Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer.get(), length);
if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD)) if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD))
@ -476,6 +589,23 @@ void Network::clock(uint32_t ms)
if (m_promiscuousPeer) { if (m_promiscuousPeer) {
m_rxAnalogStreamId = streamId; m_rxAnalogStreamId = streamId;
m_pktLastSeq = m_pktSeq; m_pktLastSeq = m_pktSeq;
uint16_t lastRxSeq = 0U;
MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence());
}
else if (ret == MUX_OUT_OF_ORDER) {
LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId,
streamId, rtpHeader.getSequence(), lastRxSeq);
}
#if DEBUG_RTP_MUX
else {
LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId);
}
#endif
} }
else { else {
if (m_rxAnalogStreamId == 0U) { if (m_rxAnalogStreamId == 0U) {
@ -490,21 +620,36 @@ void Network::clock(uint32_t ms)
} }
else { else {
if (m_rxAnalogStreamId == streamId) { if (m_rxAnalogStreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) { uint16_t lastRxSeq = 0U;
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "Analog Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq);
if (ret == MUX_LOST_FRAMES) {
LogWarning(LOG_NET, "Analog stream %u possible lost frames; got %u, expected %u",
streamId, m_pktSeq, lastRxSeq);
} }
else if (ret == MUX_OUT_OF_ORDER) {
LogWarning(LOG_NET, "Analog stream %u out-of-order; got %u, expected %u",
streamId, m_pktSeq, lastRxSeq);
} }
#if DEBUG_RTP_MUX
m_pktLastSeq = m_pktSeq; else {
LogDebugEx(LOG_NET, "Network::clock()", "Analog valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId);
} }
#endif
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxAnalogStreamId = 0U; m_rxAnalogStreamId = 0U;
} }
} }
} }
// check if we need to skip this stream -- a non-zero stream ID means the network client is locked
// to receiving a specific stream; a zero stream ID means the network is promiscuously
// receiving streams sent to this peer
if (m_rxAnalogStreamId != 0U && m_rxAnalogStreamId != streamId) {
break;
}
}
if (m_debug) if (m_debug)
Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer.get(), length);
if (length < (int)ANALOG_PACKET_LENGTH) { if (length < (int)ANALOG_PACKET_LENGTH) {
@ -534,7 +679,7 @@ void Network::clock(uint32_t ms)
case NET_FUNC::MASTER: // Master case NET_FUNC::MASTER: // Master
{ {
// process incomfing message subfunction opcodes // process incoming message subfunction opcodes
switch (fneHeader.getSubFunction()) { switch (fneHeader.getSubFunction()) {
case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist
{ {
@ -552,7 +697,7 @@ void Network::clock(uint32_t ms)
offs += 4U; offs += 4U;
} }
LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len); LogInfoEx(LOG_NET, "Network Announced %u whitelisted RIDs", len);
// save to file if enabled and we got RIDs // save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) { if (m_saveLookup && len > 0) {
@ -578,7 +723,7 @@ void Network::clock(uint32_t ms)
offs += 4U; offs += 4U;
} }
LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len); LogInfoEx(LOG_NET, "Network Announced %u blacklisted RIDs", len);
// save to file if enabled and we got RIDs // save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) { if (m_saveLookup && len > 0) {
@ -621,7 +766,7 @@ void Network::clock(uint32_t ms)
m_tidLookup->eraseEntry(id, slot); m_tidLookup->eraseEntry(id, slot);
} }
LogMessage(LOG_NET, "Activated%s%s TG %u TS %u in TGID table", LogInfoEx(LOG_NET, "Activated%s%s TG %u TS %u in TGID table",
(nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot); (nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot);
m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred); m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred);
} }
@ -629,7 +774,7 @@ void Network::clock(uint32_t ms)
offs += 5U; offs += 5U;
} }
LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); LogInfoEx(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size());
// save if saving from network is enabled // save if saving from network is enabled
if (m_saveLookup && len > 0) { if (m_saveLookup && len > 0) {
@ -655,14 +800,14 @@ void Network::clock(uint32_t ms)
lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot);
if (!tid.isInvalid()) { if (!tid.isInvalid()) {
LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); LogInfoEx(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot);
m_tidLookup->eraseEntry(id, slot); m_tidLookup->eraseEntry(id, slot);
} }
offs += 5U; offs += 5U;
} }
LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); LogInfoEx(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size());
// save if saving from network is enabled // save if saving from network is enabled
if (m_saveLookup && len > 0) { if (m_saveLookup && len > 0) {
@ -673,6 +818,46 @@ void Network::clock(uint32_t ms)
} }
break; break;
case NET_SUBFUNC::MASTER_HA_PARAMS: // HA Parameters
{
if (m_enabled) {
if (m_debug)
Utils::dump(1U, "Network::clock(), Network Rx, HA PARAMS", buffer.get(), length);
m_haIPs.clear();
m_currentHAIP = 0U;
m_maxRetryCount = MAX_RETRY_HA_RECONNECT;
// always add the configured address to the HA IP list
m_haIPs.push_back(PeerHAIPEntry(m_configuredAddress, m_configuredPort));
uint32_t len = GET_UINT32(buffer, 6U);
if (len > 0U) {
len /= HA_PARAMS_ENTRY_LEN;
}
uint8_t offs = 10U;
for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) {
uint32_t ipAddr = GET_UINT32(buffer, offs + 4U);
uint16_t port = GET_UINT16(buffer, offs + 8U);
std::string address = __IP_FROM_UINT(ipAddr);
if (m_debug)
LogDebugEx(LOG_NET, "Network::clock()", "HA PARAMS, %s:%u", address.c_str(), port);
m_haIPs.push_back(PeerHAIPEntry(address, port));
}
if (m_haIPs.size() > 1U) {
m_currentHAIP = 1U; // because the first entry is our configured entry, set
// the current HA IP to the next available
LogInfoEx(LOG_NET, "Loaded %u HA IPs from master", m_haIPs.size() - 1U);
}
}
}
break;
default: default:
Utils::dump("unknown master control opcode from the master", buffer.get(), length); Utils::dump("unknown master control opcode from the master", buffer.get(), length);
break; break;
@ -682,7 +867,13 @@ void Network::clock(uint32_t ms)
case NET_FUNC::INCALL_CTRL: // In-Call Control case NET_FUNC::INCALL_CTRL: // In-Call Control
{ {
// process incomfing message subfunction opcodes uint32_t ssrc = rtpHeader.getSSRC();
if (!m_promiscuousPeer && ssrc != peerId) {
LogWarning(LOG_NET, "PEER %u, ignoring in-call control not destined for this peer SSRC %u", m_peerId, ssrc);
break;
}
// process incoming message subfunction opcodes
switch (fneHeader.getSubFunction()) { switch (fneHeader.getSubFunction()) {
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control
{ {
@ -693,7 +884,7 @@ void Network::clock(uint32_t ms)
// fire off DMR in-call callback if we have one // fire off DMR in-call callback if we have one
if (m_dmrInCallCallback != nullptr) { if (m_dmrInCallCallback != nullptr) {
m_dmrInCallCallback(command, dstId, slot); m_dmrInCallCallback(command, dstId, slot, peerId, ssrc, streamId);
} }
} }
} }
@ -706,7 +897,7 @@ void Network::clock(uint32_t ms)
// fire off P25 in-call callback if we have one // fire off P25 in-call callback if we have one
if (m_p25InCallCallback != nullptr) { if (m_p25InCallCallback != nullptr) {
m_p25InCallCallback(command, dstId); m_p25InCallCallback(command, dstId, peerId, ssrc, streamId);
} }
} }
} }
@ -719,7 +910,7 @@ void Network::clock(uint32_t ms)
// fire off NXDN in-call callback if we have one // fire off NXDN in-call callback if we have one
if (m_nxdnInCallCallback != nullptr) { if (m_nxdnInCallCallback != nullptr) {
m_nxdnInCallCallback(command, dstId); m_nxdnInCallCallback(command, dstId, peerId, ssrc, streamId);
} }
} }
} }
@ -732,7 +923,7 @@ void Network::clock(uint32_t ms)
// fire off analog in-call callback if we have one // fire off analog in-call callback if we have one
if (m_analogInCallCallback != nullptr) { if (m_analogInCallCallback != nullptr) {
m_analogInCallCallback(command, dstId); m_analogInCallCallback(command, dstId, peerId, ssrc, streamId);
} }
} }
} }
@ -765,7 +956,7 @@ void Network::clock(uint32_t ms)
if (ks.keys().size() > 0U) { if (ks.keys().size() > 0U) {
// fetch first key (a master response should never really send back more then one key) // fetch first key (a master response should never really send back more then one key)
KeyItem ki = ks.keys()[0]; KeyItem ki = ks.keys()[0];
LogMessage(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", m_peerId, LogInfoEx(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", m_peerId,
ks.algId(), ki.kId()); ks.algId(), ki.kId());
// fire off key response callback if we have one // fire off key response callback if we have one
@ -821,6 +1012,15 @@ void Network::clock(uint32_t ms)
} }
break; break;
case NET_CONN_NAK_FNE_DUPLICATE_CONN:
LogWarning(LOG_NET, "PEER %u master NAK; duplicate connection to FNE, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
m_status = NET_STAT_WAITING_CONNECT;
m_remotePeerId = 0U;
m_flaggedDuplicateConn = true;
m_maxRetryCount = MAX_RETRY_DUP_RECONNECT;
m_retryTimer.start();
return;
case NET_CONN_NAK_GENERAL_FAILURE: case NET_CONN_NAK_GENERAL_FAILURE:
default: default:
LogWarning(LOG_NET, "PEER %u master NAK; general failure, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); LogWarning(LOG_NET, "PEER %u master NAK; general failure, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
@ -848,7 +1048,7 @@ void Network::clock(uint32_t ms)
{ {
switch (m_status) { switch (m_status) {
case NET_STAT_WAITING_LOGIN: case NET_STAT_WAITING_LOGIN:
LogMessage(LOG_NET, "PEER %u RPTL ACK, performing login exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); LogInfoEx(LOG_NET, "PEER %u RPTL ACK, performing login exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
::memcpy(m_salt, buffer.get() + 6U, sizeof(uint32_t)); ::memcpy(m_salt, buffer.get() + 6U, sizeof(uint32_t));
writeAuthorisation(); writeAuthorisation();
@ -858,7 +1058,7 @@ void Network::clock(uint32_t ms)
m_retryTimer.start(); m_retryTimer.start();
break; break;
case NET_STAT_WAITING_AUTHORISATION: case NET_STAT_WAITING_AUTHORISATION:
LogMessage(LOG_NET, "PEER %u RPTK ACK, performing configuration exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); LogInfoEx(LOG_NET, "PEER %u RPTK ACK, performing configuration exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
writeConfig(); writeConfig();
@ -867,7 +1067,7 @@ void Network::clock(uint32_t ms)
m_retryTimer.start(); m_retryTimer.start();
break; break;
case NET_STAT_WAITING_CONFIG: case NET_STAT_WAITING_CONFIG:
LogMessage(LOG_NET, "PEER %u RPTC ACK, logged into the master successfully, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); LogInfoEx(LOG_NET, "PEER %u RPTC ACK, logged into the master successfully, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
m_loginStreamId = 0U; m_loginStreamId = 0U;
m_remotePeerId = rtpHeader.getSSRC(); m_remotePeerId = rtpHeader.getSSRC();
@ -880,12 +1080,13 @@ void Network::clock(uint32_t ms)
m_status = NET_STAT_RUNNING; m_status = NET_STAT_RUNNING;
m_timeoutTimer.start(); m_timeoutTimer.start();
m_retryTimer.setTimeout(DEFAULT_RETRY_TIME);
m_retryTimer.start(); m_retryTimer.start();
if (length > 6) { if (length > 6) {
m_useAlternatePortForDiagnostics = (buffer[6U] & 0x80U) == 0x80U; m_useAlternatePortForDiagnostics = (buffer[6U] & 0x80U) == 0x80U;
if (m_useAlternatePortForDiagnostics) { if (m_useAlternatePortForDiagnostics) {
LogMessage(LOG_NET, "PEER %u RPTC ACK, master commanded alternate port for diagnostics and activity logging, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); LogInfoEx(LOG_NET, "PEER %u RPTC ACK, master commanded alternate port for diagnostics and activity logging, remotePeerId = %u", m_peerId, rtpHeader.getSSRC());
} else { } else {
// disable diagnostic and activity logging automatically if the master doesn't utilize the alternate port // disable diagnostic and activity logging automatically if the master doesn't utilize the alternate port
m_allowDiagnosticTransfer = false; m_allowDiagnosticTransfer = false;
@ -935,6 +1136,13 @@ void Network::clock(uint32_t ms)
uint64_t dt = (uint64_t)fabs((double)now - (double)serverNow); uint64_t dt = (uint64_t)fabs((double)now - (double)serverNow);
if (dt > MAX_SERVER_DIFF) if (dt > MAX_SERVER_DIFF)
LogWarning(LOG_NET, "PEER %u pong, time delay greater than %llums, now = %llu, server = %llu, dt = %llu", m_peerId, MAX_SERVER_DIFF, now, serverNow, dt); LogWarning(LOG_NET, "PEER %u pong, time delay greater than %llums, now = %llu, server = %llu, dt = %llu", m_peerId, MAX_SERVER_DIFF, now, serverNow, dt);
++m_pingsReceived;
// if we've been connected for at least 10 PING/PONG cycles and we're flagged duplicate connection, clear the flag
if (m_pingsReceived > 10U && m_flaggedDuplicateConn) {
m_flaggedDuplicateConn = false;
}
} }
break; break;
default: default:
@ -988,15 +1196,35 @@ bool Network::open()
if (!m_enabled) if (!m_enabled)
return false; return false;
if (m_debug) if (m_debug)
LogMessage(LOG_NET, "PEER %u opening network", m_peerId); LogInfoEx(LOG_NET, "PEER %u opening network", m_peerId);
m_status = NET_STAT_WAITING_CONNECT; m_status = NET_STAT_WAITING_CONNECT;
// are we rotating IPs for HA reconnect?
if ((m_haIPs.size() - 1) > 1 && m_retryCount > 0U && !m_flaggedDuplicateConn &&
m_maxRetryCount == MAX_RETRY_HA_RECONNECT) {
if (m_currentHAIP > (m_haIPs.size() - 1)) {
m_currentHAIP = 0U;
}
PeerHAIPEntry entry = m_haIPs[m_currentHAIP];
m_currentHAIP++;
LogInfoEx(LOG_NET, "PEER %u connection to the master has timed out, %s:%u is non-responsive, trying next HA %s:%u", m_peerId,
m_address.c_str(), m_port, entry.masterAddress.c_str(), entry.masterPort);
m_address = entry.masterAddress;
m_port = entry.masterPort;
}
m_timeoutTimer.start(); m_timeoutTimer.start();
m_retryTimer.start(); m_retryTimer.start();
m_retryCount = 0U; m_retryCount = 0U;
m_pingsReceived = 0U;
if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) { if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) {
LogMessage(LOG_NET, "!!! Could not lookup the address of the master!"); LogInfoEx(LOG_NET, "!!! Could not lookup the address of the master!");
return false; return false;
} }
@ -1008,7 +1236,7 @@ bool Network::open()
void Network::close() void Network::close()
{ {
if (m_debug) if (m_debug)
LogMessage(LOG_NET, "PEER %u closing Network", m_peerId); LogInfoEx(LOG_NET, "PEER %u closing Network", m_peerId);
if (m_status == NET_STAT_RUNNING) { if (m_status == NET_STAT_RUNNING) {
uint8_t buffer[1U]; uint8_t buffer[1U];
@ -1020,9 +1248,17 @@ void Network::close()
m_socket->close(); m_socket->close();
m_retryTimer.stop(); m_retryTimer.stop();
if (m_flaggedDuplicateConn) {
// if we were flagged a duplicate connection, increase the retry time to avoid rapid reconnect attempts
m_retryTimer.setTimeout(DUPLICATE_CONN_RETRY_TIME);
}
else {
m_retryTimer.setTimeout(DEFAULT_RETRY_TIME);
}
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_status = NET_STAT_WAITING_CONNECT; m_status = NET_STAT_WAITING_CONNECT;
m_remotePeerId = 0U;
} }
/* Sets flag enabling network communication. */ /* Sets flag enabling network communication. */
@ -1036,6 +1272,38 @@ void Network::enable(bool enabled)
// Protected Class Members // Protected Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Helper to verify the given RTP sequence for the given RTP stream. */
MULTIPLEX_RET_CODE Network::verifyStream(uint16_t* lastRxSeq)
{
MULTIPLEX_RET_CODE ret = MUX_VALID_SUCCESS;
if (m_pktSeq == RTP_END_OF_CALL_SEQ) {
// reset the received sequence back to 0
m_pktLastSeq = 0U;
}
else {
*lastRxSeq = m_pktLastSeq;
if ((m_pktSeq >= m_pktLastSeq) || (m_pktSeq == 0U)) {
// if the sequence isn't 0, and is greater then the last received sequence + 1 frame
// assume a packet was lost
if ((m_pktSeq != 0U) && m_pktSeq > m_pktLastSeq + 1U) {
ret = MUX_LOST_FRAMES;
}
m_pktLastSeq = m_pktSeq;
}
else {
if (m_pktSeq < m_pktLastSeq) {
ret = MUX_OUT_OF_ORDER;
}
}
}
m_pktLastSeq = m_pktSeq;
return ret;
}
/* User overrideable handler that allows user code to process network packets not handled by this class. */ /* User overrideable handler that allows user code to process network packets not handled by this class. */
void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId,
@ -1052,6 +1320,11 @@ bool Network::writeLogin()
return false; return false;
} }
// reset retry timer default timeout
if (m_retryTimer.isRunning())
m_retryTimer.stop();
m_retryTimer.setTimeout(DEFAULT_RETRY_TIME);
uint8_t buffer[8U]; uint8_t buffer[8U];
::memcpy(buffer + 0U, TAG_REPEATER_LOGIN, 4U); ::memcpy(buffer + 0U, TAG_REPEATER_LOGIN, 4U);
SET_UINT32(m_peerId, buffer, 4U); // Peer ID SET_UINT32(m_peerId, buffer, 4U); // Peer ID

@ -40,36 +40,87 @@ namespace network
*/ */
struct PeerMetadata { struct PeerMetadata {
/** @name Identity and Frequency */ /** @name Identity and Frequency */
std::string identity; //! Peer Identity std::string identity; //!< Peer Identity
uint32_t rxFrequency; //! Peer Rx Frequency uint32_t rxFrequency; //!< Peer Rx Frequency
uint32_t txFrequency; //! Peer Tx Frequency uint32_t txFrequency; //!< Peer Tx Frequency
/** @} */ /** @} */
/** @name System Info */ /** @name System Info */
uint32_t power; //! Peer Tx Power (W) uint32_t power; //!< Peer Tx Power (W)
float latitude; //! Location Latitude (decmial notation) float latitude; //!< Location Latitude (decmial notation)
float longitude; //! Location Longitude (decmial notation) float longitude; //!< Location Longitude (decmial notation)
int height; //! Height (M) int height; //!< Height (M)
std::string location; //! Textual Location std::string location; //!< Textual Location
/** @} */ /** @} */
/** @name Channel Data */ /** @name Channel Data */
float txOffsetMhz; //! Tx Offset (MHz) float txOffsetMhz; //!< Tx Offset (MHz)
float chBandwidthKhz; //! Channel Bandwidth (kHz) float chBandwidthKhz; //!< Channel Bandwidth (kHz)
uint8_t channelId; //! Channel ID uint8_t channelId; //!< Channel ID
uint32_t channelNo; //! Channel Number uint32_t channelNo; //!< Channel Number
/** @} */ /** @} */
/** @name RCON */ /** @name RCON */
std::string restApiPassword; //! REST API Password std::string restApiPassword; //!< REST API Password
uint16_t restApiPort; //! REST API Port uint16_t restApiPort; //!< REST API Port
/** @} */ /** @} */
/** @name Flags */ /** @name Flags */
bool isConventional; //! Flag indicating peer is a conventional peer. bool isConventional; //!< Flag indicating peer is a conventional peer.
/** @} */ /** @} */
}; };
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents high availiability IP address data.
* @ingroup network_core
*/
struct PeerHAIPEntry {
public:
/**
* @brief Initializes a new instance of the PeerHAIPEntry class
*/
PeerHAIPEntry() :
masterAddress(),
masterPort(TRAFFIC_DEFAULT_PORT)
{
/* stub **/
}
/**
* @brief Initializes a new instance of the PeerHAIPEntry class
* @param address Master IP Address.
* @param port Master Port.
*/
PeerHAIPEntry(std::string address, uint16_t port) :
masterAddress(address),
masterPort(port)
{
/* stub */
}
/**
* @brief Equals operator. Copies this PeerHAIPEntry to another PeerHAIPEntry.
* @param data Instance of PeerHAIPEntry to copy.
*/
PeerHAIPEntry& operator=(const PeerHAIPEntry& data)
{
if (this != &data) {
masterAddress = data.masterAddress;
masterPort = data.masterPort;
}
return *this;
}
public:
std::string masterAddress; //!< Master IP Address.
uint16_t masterPort; //!< Master Port.
};
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -193,6 +244,11 @@ namespace network
*/ */
void enable(bool enabled); void enable(bool enabled);
/**
* @brief Helper to clear the duplicate connection flag.
*/
void clearDuplicateConnFlag() { m_flaggedDuplicateConn = false; }
/** /**
* @brief Helper to set the peer connected callback. * @brief Helper to set the peer connected callback.
* @param callback * @param callback
@ -208,22 +264,22 @@ namespace network
* @brief Helper to set the DMR In-Call Control callback. * @brief Helper to set the DMR In-Call Control callback.
* @param callback * @param callback
*/ */
void setDMRICCCallback(std::function<void(NET_ICC::ENUM, uint32_t, uint8_t)>&& callback) { m_dmrInCallCallback = callback; } void setDMRICCCallback(std::function<void(NET_ICC::ENUM, uint32_t, uint8_t, uint32_t, uint32_t, uint32_t)>&& callback) { m_dmrInCallCallback = callback; }
/** /**
* @brief Helper to set the P25 In-Call Control callback. * @brief Helper to set the P25 In-Call Control callback.
* @param callback * @param callback
*/ */
void setP25ICCCallback(std::function<void(NET_ICC::ENUM, uint32_t)>&& callback) { m_p25InCallCallback = callback; } void setP25ICCCallback(std::function<void(NET_ICC::ENUM, uint32_t, uint32_t, uint32_t, uint32_t)>&& callback) { m_p25InCallCallback = callback; }
/** /**
* @brief Helper to set the NXDN In-Call Control callback. * @brief Helper to set the NXDN In-Call Control callback.
* @param callback * @param callback
*/ */
void setNXDNICCCallback(std::function<void(NET_ICC::ENUM, uint32_t)>&& callback) { m_nxdnInCallCallback = callback; } void setNXDNICCCallback(std::function<void(NET_ICC::ENUM, uint32_t, uint32_t, uint32_t, uint32_t)>&& callback) { m_nxdnInCallCallback = callback; }
/** /**
* @brief Helper to set the analog In-Call Control callback. * @brief Helper to set the analog In-Call Control callback.
* @param callback * @param callback
*/ */
void setAnalogICCCallback(std::function<void(NET_ICC::ENUM, uint32_t)>&& callback) { m_analogInCallCallback = callback; } void setAnalogICCCallback(std::function<void(NET_ICC::ENUM, uint32_t, uint32_t, uint32_t, uint32_t)>&& callback) { m_analogInCallCallback = callback; }
/** /**
* @brief Helper to set the enc. key response callback. * @brief Helper to set the enc. key response callback.
@ -241,6 +297,12 @@ namespace network
std::string m_address; std::string m_address;
uint16_t m_port; uint16_t m_port;
std::string m_configuredAddress;
uint16_t m_configuredPort;
std::vector<PeerHAIPEntry> m_haIPs;
uint8_t m_currentHAIP;
std::string m_password; std::string m_password;
bool m_enabled; bool m_enabled;
@ -260,8 +322,12 @@ namespace network
Timer m_retryTimer; Timer m_retryTimer;
uint8_t m_retryCount; uint8_t m_retryCount;
uint8_t m_maxRetryCount;
bool m_flaggedDuplicateConn;
Timer m_timeoutTimer; Timer m_timeoutTimer;
uint32_t m_pingsReceived;
uint32_t* m_rxDMRStreamId; uint32_t* m_rxDMRStreamId;
uint32_t m_rxP25StreamId; uint32_t m_rxP25StreamId;
uint32_t m_rxNXDNStreamId; uint32_t m_rxNXDNStreamId;
@ -271,6 +337,7 @@ namespace network
uint32_t m_loginStreamId; uint32_t m_loginStreamId;
PeerMetadata* m_metadata; PeerMetadata* m_metadata;
RTPStreamMultiplex* m_mux;
uint32_t m_remotePeerId; uint32_t m_remotePeerId;
@ -303,22 +370,26 @@ namespace network
* @brief DMR In-Call Control Function Callback. * @brief DMR In-Call Control Function Callback.
* (This is called when the master sends a In-Call Control request.) * (This is called when the master sends a In-Call Control request.)
*/ */
std::function<void(NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo)> m_dmrInCallCallback; std::function<void(NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo,
uint32_t peerId, uint32_t ssrc, uint32_t streamId)> m_dmrInCallCallback;
/** /**
* @brief P25 In-Call Control Function Callback. * @brief P25 In-Call Control Function Callback.
* (This is called once the master sends a In-Call Control request.) * (This is called once the master sends a In-Call Control request.)
*/ */
std::function<void(NET_ICC::ENUM command, uint32_t dstId)> m_p25InCallCallback; std::function<void(NET_ICC::ENUM command, uint32_t dstId,
uint32_t peerId, uint32_t ssrc, uint32_t streamId)> m_p25InCallCallback;
/** /**
* @brief NXDN In-Call Control Function Callback. * @brief NXDN In-Call Control Function Callback.
* (This is called once the master sends a In-Call Control request.) * (This is called once the master sends a In-Call Control request.)
*/ */
std::function<void(NET_ICC::ENUM command, uint32_t dstId)> m_nxdnInCallCallback; std::function<void(NET_ICC::ENUM command, uint32_t dstId,
uint32_t peerId, uint32_t ssrc, uint32_t streamId)> m_nxdnInCallCallback;
/** /**
* @brief Analog In-Call Control Function Callback. * @brief Analog In-Call Control Function Callback.
* (This is called once the master sends a In-Call Control request.) * (This is called once the master sends a In-Call Control request.)
*/ */
std::function<void(NET_ICC::ENUM command, uint32_t dstId)> m_analogInCallCallback; std::function<void(NET_ICC::ENUM command, uint32_t dstId,
uint32_t peerId, uint32_t ssrc, uint32_t streamId)> m_analogInCallCallback;
/** /**
* @brief Encryption Key Response Function Callback. * @brief Encryption Key Response Function Callback.
@ -326,6 +397,13 @@ namespace network
*/ */
std::function<void(p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength)> m_keyRespCallback; std::function<void(p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength)> m_keyRespCallback;
/**
* @brief Helper to verify the given RTP sequence for the given RTP stream.
* @param[out] lastRxSeq Last Received Sequence.
* @return MULTIPLEX_RET_CODE Return code.
*/
MULTIPLEX_RET_CODE verifyStream(uint16_t* lastRxSeq);
/** /**
* @brief User overrideable handler that allows user code to process network packets not handled by this class. * @brief User overrideable handler that allows user code to process network packets not handled by this class.
* @param peerId Peer ID. * @param peerId Peer ID.
@ -341,21 +419,104 @@ namespace network
/** /**
* @brief Writes login request to the network. * @brief Writes login request to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the repeater/end point login message.
* The message is 8 bytes in length.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Protocol Tag (RPTL) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Peer ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @returns bool True, if login request was sent, otherwise false. * @returns bool True, if login request was sent, otherwise false.
*/ */
bool writeLogin(); bool writeLogin();
/** /**
* @brief Writes network authentication challenge. * @brief Writes network authentication challenge.
* \code{.unparsed}
* Below is the representation of the data layout for the repeater/end point login message.
* The message is 40 bytes in length.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Protocol Tag (RPTK) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Peer ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | 30 bytes of SHA-256 ......................................... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @returns bool True, if authorization response was sent, otherwise false. * @returns bool True, if authorization response was sent, otherwise false.
*/ */
bool writeAuthorisation(); bool writeAuthorisation();
/** /**
* @brief Writes modem configuration to the network. * @brief Writes modem configuration to the network.
* \code{.unparsed}
* Below is the representation of the data layout for the repeater/end point login message.
* The message is 40 bytes in length.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Protocol Tag (RPTC) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Variable Length JSON Payload ................................ |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* The JSON payload is variable length and looks like this:
* {
* "identity": "<Peer Identity String>",
* "rxFrequency": <Peer Rx Frequency in Hz>,
* "txFrequency": <Peer Tx Frequency in Hz>,
* "info":
* {
* "latitude": <Peer Geographical Latitude>,
* "longitude": <Peer Geographical Longitude>,
* "height": <Peer Height (in meters)>,
* "location": "<Textual String Describing Peer Location>"
* },
* "channel":
* {
* "txPower": <Peer Transmit Power (in W)>,
* "txOffsetMhz": <Peer Transmit Offset (in MHz)>,
* "chBandwidthKhz": <Peer Channel Bandwidth (in KHz>,
* "channelId": <Channel ID from the IDEN channel bandplan>,
* "channelNo": <Channel Number from the IDEN channel bandplan>,
* },
* "software": "<Textual hardcoded string containing software watermark>",
* }
*
* These are extra parameters used in the root of the above JSON.
* {
* "externalPeer": <Boolean flag indicating whether or not this peer is a linked CFNE peer>,
* "masterPeerId": <Linked CFNE peer's FNE master peer ID>,
*
* "conventionalPeer": <Boolean flag indicating whether or not this is a conventional peer>,
*
* "sysView": <Boolean flag indicating whether or not this peer is a SysView peer>,
* }
* \endcode
* @returns bool True, if configuration response was sent, otherwise false. * @returns bool True, if configuration response was sent, otherwise false.
*/ */
virtual bool writeConfig(); virtual bool writeConfig();
/** /**
* @brief Writes a network stay-alive ping. * @brief Writes a network stay-alive ping.
* \code{.unparsed}
* Below is the representation of the data layout for the repeater/end point login message.
* The message is 1 bytes in length.
*
* Byte 0
* Bit 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+
* \endcode
* @returns bool True, if stay-alive ping was sent, otherwise false. * @returns bool True, if stay-alive ping was sent, otherwise false.
*/ */
bool writePing(); bool writePing();

@ -80,7 +80,6 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL
// do we have all the blocks? // do we have all the blocks?
if (fragments.size() == blockCnt + 1U) { if (fragments.size() == blockCnt + 1U) {
uint8_t* buffer = nullptr;
fragments.lock(false); fragments.lock(false);
if (fragments[0] == nullptr) { if (fragments[0] == nullptr) {
LogError(LOG_NET, "%s, Packet Fragment, error missing block 0? Packet dropped.", m_name); LogError(LOG_NET, "%s, Packet Fragment, error missing block 0? Packet dropped.", m_name);
@ -106,8 +105,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL
uint32_t compressedLen = fragments[0]->compressedSize; uint32_t compressedLen = fragments[0]->compressedSize;
uint32_t len = fragments[0]->size; uint32_t len = fragments[0]->size;
buffer = new uint8_t[len + 1U]; DECLARE_UINT8_ARRAY(buffer, len + 1U);
::memset(buffer, 0x00U, len + 1U);
if (fragments.size() == 1U) { if (fragments.size() == 1U) {
::memcpy(buffer, fragments[0U]->data, len); ::memcpy(buffer, fragments[0U]->data, len);
} else { } else {
@ -136,7 +134,11 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL
} }
} }
else { else {
*message = buffer; uint8_t* outBuf = new uint8_t[len + 1U];
::memset(outBuf, 0x00U, len + 1U);
::memcpy(outBuf, buffer, len);
*message = outBuf;
if (outLength != nullptr) { if (outLength != nullptr) {
*outLength = len; *outLength = len;
} }

@ -39,9 +39,27 @@ namespace network
/** /**
* @brief Represents a fragmented packet buffer. * @brief Represents a fragmented packet buffer.
* @ingroup network_core * @ingroup network_core
* \code{.unparsed}
* Below is the representation of the fragment layout for a packet buffer message. The ultimate
* buffer is variable length up to a maximum of 534 bytes for a single fragment.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Uncompressed Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Compressed Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Block Number | Total Blocks | Payload ..................... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
*/ */
class HOST_SW_API PacketBuffer { class HOST_SW_API PacketBuffer {
public: public:
auto operator=(PacketBuffer&) -> PacketBuffer& = delete;
auto operator=(PacketBuffer&&) -> PacketBuffer& = delete;
PacketBuffer(PacketBuffer&) = delete;
/** /**
* @brief Initializes a new instance of the PacketBuffer class. * @brief Initializes a new instance of the PacketBuffer class.
* @param compression Flag indicating whether packet data should be compressed automatically. * @param compression Flag indicating whether packet data should be compressed automatically.

@ -24,6 +24,7 @@
#define RPC_HEADER_LENGTH_BYTES 8 #define RPC_HEADER_LENGTH_BYTES 8
#define RPC_REPLY_FUNC 0x8000U #define RPC_REPLY_FUNC 0x8000U
#define RPC_MAX_FUNC 0x7FFFU
namespace network namespace network
{ {

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -44,35 +44,36 @@ namespace network
*/ */
namespace NET_FUNC { namespace NET_FUNC {
enum ENUM : uint8_t { enum ENUM : uint8_t {
ILLEGAL = 0xFFU, //! Illegal Function ILLEGAL = 0xFFU, //!< Illegal Function
PROTOCOL = 0x00U, //! Digital Protocol Function PROTOCOL = 0x00U, //!< Digital Protocol Function
MASTER = 0x01U, //! Network Master Function MASTER = 0x01U, //!< Network Master Function
RPTL = 0x60U, //! Repeater Login RPTL = 0x60U, //!< Repeater Login
RPTK = 0x61U, //! Repeater Authorisation RPTK = 0x61U, //!< Repeater Authorisation
RPTC = 0x62U, //! Repeater Configuration RPTC = 0x62U, //!< Repeater Configuration
RPT_DISC = 0x70U, //! Repeater Disconnect RPT_DISC = 0x70U, //!< Repeater Disconnect
MST_DISC = 0x71U, //! Master Disconnect MST_DISC = 0x71U, //!< Master Disconnect
PING = 0x74U, //! Ping PING = 0x74U, //!< Ping
PONG = 0x75U, //! Pong PONG = 0x75U, //!< Pong
GRANT_REQ = 0x7AU, //! Grant Request GRANT_REQ = 0x7AU, //!< Grant Request
INCALL_CTRL = 0x7BU, //! In-Call Control INCALL_CTRL = 0x7BU, //!< In-Call Control
KEY_REQ = 0x7CU, //! Encryption Key Request KEY_REQ = 0x7CU, //!< Encryption Key Request
KEY_RSP = 0x7DU, //! Encryption Key Response KEY_RSP = 0x7DU, //!< Encryption Key Response
ACK = 0x7EU, //! Packet Acknowledge ACK = 0x7EU, //!< Packet Acknowledge
NAK = 0x7FU, //! Packet Negative Acknowledge NAK = 0x7FU, //!< Packet Negative Acknowledge
TRANSFER = 0x90U, //! Network Transfer Function TRANSFER = 0x90U, //!< Network Transfer Function
ANNOUNCE = 0x91U, //! Network Announce Function ANNOUNCE = 0x91U, //!< Network Announce Function
PEER_LINK = 0x92U //! FNE Peer-Link Function REPL = 0x92U, //!< FNE Replication Function
NET_TREE = 0x93U //!< FNE Network Tree Function
}; };
}; };
@ -82,34 +83,40 @@ namespace network
*/ */
namespace NET_SUBFUNC { namespace NET_SUBFUNC {
enum ENUM : uint8_t { enum ENUM : uint8_t {
NOP = 0xFFU, //! No Operation Sub-Function NOP = 0xFFU, //!< No Operation Sub-Function
PROTOCOL_SUBFUNC_DMR = 0x00U, //! DMR PROTOCOL_SUBFUNC_DMR = 0x00U, //!< DMR
PROTOCOL_SUBFUNC_P25 = 0x01U, //! P25 PROTOCOL_SUBFUNC_P25 = 0x01U, //!< P25
PROTOCOL_SUBFUNC_NXDN = 0x02U, //! NXDN PROTOCOL_SUBFUNC_NXDN = 0x02U, //!< NXDN
PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //! Analog PROTOCOL_SUBFUNC_P25_P2 = 0x03U, //!< P25 Phase 2
PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //!< Analog
MASTER_SUBFUNC_WL_RID = 0x00U, //! Whitelist RIDs
MASTER_SUBFUNC_BL_RID = 0x01U, //! Blacklist RIDs MASTER_SUBFUNC_WL_RID = 0x00U, //!< Whitelist RIDs
MASTER_SUBFUNC_ACTIVE_TGS = 0x02U, //! Active TGIDs MASTER_SUBFUNC_BL_RID = 0x01U, //!< Blacklist RIDs
MASTER_SUBFUNC_DEACTIVE_TGS = 0x03U, //! Deactive TGIDs MASTER_SUBFUNC_ACTIVE_TGS = 0x02U, //!< Active TGIDs
MASTER_SUBFUNC_DEACTIVE_TGS = 0x03U, //!< Deactive TGIDs
TRANSFER_SUBFUNC_ACTIVITY = 0x01U, //! Activity Log Transfer MASTER_HA_PARAMS = 0xA3U, //!< HA Parameters
TRANSFER_SUBFUNC_DIAG = 0x02U, //! Diagnostic Log Transfer
TRANSFER_SUBFUNC_STATUS = 0x03U, //! Status Transfer TRANSFER_SUBFUNC_ACTIVITY = 0x01U, //!< Activity Log Transfer
TRANSFER_SUBFUNC_DIAG = 0x02U, //!< Diagnostic Log Transfer
ANNC_SUBFUNC_GRP_AFFIL = 0x00U, //! Announce Group Affiliation TRANSFER_SUBFUNC_STATUS = 0x03U, //!< Status Transfer
ANNC_SUBFUNC_UNIT_REG = 0x01U, //! Announce Unit Registration
ANNC_SUBFUNC_UNIT_DEREG = 0x02U, //! Announce Unit Deregistration ANNC_SUBFUNC_GRP_AFFIL = 0x00U, //!< Announce Group Affiliation
ANNC_SUBFUNC_GRP_UNAFFIL = 0x03U, //! Announce Group Affiliation Removal ANNC_SUBFUNC_UNIT_REG = 0x01U, //!< Announce Unit Registration
ANNC_SUBFUNC_AFFILS = 0x90U, //! Update All Affiliations ANNC_SUBFUNC_UNIT_DEREG = 0x02U, //!< Announce Unit Deregistration
ANNC_SUBFUNC_SITE_VC = 0x9AU, //! Announce Site VCs ANNC_SUBFUNC_GRP_UNAFFIL = 0x03U, //!< Announce Group Affiliation Removal
ANNC_SUBFUNC_AFFILS = 0x90U, //!< Update All Affiliations
PL_TALKGROUP_LIST = 0x00U, //! FNE Peer-Link Talkgroup Transfer ANNC_SUBFUNC_SITE_VC = 0x9AU, //!< Announce Site VCs
PL_RID_LIST = 0x01U, //! FNE Peer-Link Radio ID Transfer
PL_PEER_LIST = 0x02U, //! FNE Peer-Link Peer List Transfer REPL_TALKGROUP_LIST = 0x00U, //!< FNE Replication Talkgroup Transfer
REPL_RID_LIST = 0x01U, //!< FNE Replication Radio ID Transfer
PL_ACT_PEER_LIST = 0xA2U, //! FNE Peer-Link Active Peer List Transfer REPL_PEER_LIST = 0x02U, //!< FNE Replication Peer List Transfer
REPL_ACT_PEER_LIST = 0xA2U, //!< FNE Replication Active Peer List Transfer
REPL_HA_PARAMS = 0xA3U, //!< FNE Replication HA Parameters
NET_TREE_LIST = 0x00U, //!< FNE Network Tree List
NET_TREE_DISC = 0x01U //!< FNE Network Tree Disconnect
}; };
}; };
@ -119,10 +126,10 @@ namespace network
*/ */
namespace NET_ICC { namespace NET_ICC {
enum ENUM : uint8_t { enum ENUM : uint8_t {
NOP = 0xFFU, //! No Operation Sub-Function NOP = 0xFFU, //!< No Operation Sub-Function
BUSY_DENY = 0x00U, //! Busy Deny BUSY_DENY = 0x00U, //!< Busy Deny
REJECT_TRAFFIC = 0x01U, //! Reject Active Traffic REJECT_TRAFFIC = 0x01U, //!< Reject Active Traffic
}; };
}; };

@ -23,8 +23,7 @@ using namespace network;
// Static Class Members // Static Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::mutex RawFrameQueue::m_queueMutex; std::mutex RawFrameQueue::s_flushMtx;
bool RawFrameQueue::m_queueFlushing = false;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
@ -34,7 +33,6 @@ bool RawFrameQueue::m_queueFlushing = false;
RawFrameQueue::RawFrameQueue(udp::Socket* socket, bool debug) : RawFrameQueue::RawFrameQueue(udp::Socket* socket, bool debug) :
m_socket(socket), m_socket(socket),
m_buffers(),
m_failedReadCnt(0U), m_failedReadCnt(0U),
m_debug(debug) m_debug(debug)
{ {
@ -43,10 +41,7 @@ RawFrameQueue::RawFrameQueue(udp::Socket* socket, bool debug) :
/* Finalizes a instance of the RawFrameQueue class. */ /* Finalizes a instance of the RawFrameQueue class. */
RawFrameQueue::~RawFrameQueue() RawFrameQueue::~RawFrameQueue() = default;
{
deleteBuffers();
}
/* Read message from the received UDP packet. */ /* Read message from the received UDP packet. */
@ -124,8 +119,12 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor
/* Cache message to frame queue. */ /* Cache message to frame queue. */
void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen) void RawFrameQueue::enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen)
{ {
if (queue == nullptr) {
LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), queue is null");
return;
}
if (message == nullptr) { if (message == nullptr) {
LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), message is null"); LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), message is null");
return; return;
@ -135,13 +134,6 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock
return; return;
} }
// if the queue is flushing -- don't attempt to enqueue any messages
if (m_queueFlushing) {
LogWarning(LOG_NET, "RawFrameQueue::enqueueMessage() -- queue is flushing, waiting to enqueue message");
while (m_queueFlushing)
Thread::sleep(2U);
}
// bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about // bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about
// this message // this message
if (length > (DATA_PACKET_LENGTH - OVERSIZED_PACKET_WARN)) { if (length > (DATA_PACKET_LENGTH - OVERSIZED_PACKET_WARN)) {
@ -161,69 +153,37 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock
dgram->address = addr; dgram->address = addr;
dgram->addrLen = addrLen; dgram->addrLen = addrLen;
m_buffers.push_back(dgram); queue->push(dgram);
} }
/* Flush the message queue. */ /* Flush the message queue. */
bool RawFrameQueue::flushQueue() bool RawFrameQueue::flushQueue(udp::BufferQueue* queue)
{ {
bool ret = true; if (queue == nullptr) {
LogError(LOG_NET, "RawFrameQueue::flushQueue(), queue is null");
return false;
}
// scope is intentional std::lock_guard<std::mutex> lock(s_flushMtx);
{
std::lock_guard<std::mutex> lock(m_queueMutex);
m_queueFlushing = true;
if (m_buffers.empty()) { if (queue->empty()) {
return false; return false;
} }
// bryanb: this is the same as above -- but for some assinine reason prevents // bryanb: this is the same as above -- but for some assinine reason prevents
// weirdness // weirdness
if (m_buffers.size() == 0U) { if (queue->size() == 0U) {
return false; return false;
} }
// LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); // LogDebug(LOG_NET, "queue len = %u", queue.size());
ret = true; bool ret = true;
if (!m_socket->write(m_buffers)) { if (!m_socket->write(queue)) {
// LogError(LOG_NET, "Failed writing data to the network"); // LogError(LOG_NET, "Failed writing data to the network");
ret = false; ret = false;
} }
m_queueFlushing = false;
}
deleteBuffers();
return ret; return ret;
} }
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Helper to ensure buffers are deleted. */
void RawFrameQueue::deleteBuffers()
{
std::lock_guard<std::mutex> lock(m_queueMutex);
m_queueFlushing = true;
for (auto& buffer : m_buffers) {
if (buffer != nullptr) {
// LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length);
if (buffer->buffer != nullptr) {
delete[] buffer->buffer;
buffer->length = 0;
buffer->buffer = nullptr;
}
delete buffer;
buffer = nullptr;
}
}
m_buffers.clear();
m_queueFlushing = false;
}

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -78,36 +78,30 @@ namespace network
/** /**
* @brief Cache message to frame queue. * @brief Cache message to frame queue.
* @param[in] queue Queue of messages.
* @param[in] message Message buffer to frame and queue. * @param[in] message Message buffer to frame and queue.
* @param length Length of message. * @param length Length of message.
* @param addr IP address to write data to. * @param addr IP address to write data to.
* @param addrLen * @param addrLen
*/ */
void enqueueMessage(const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen); void enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen);
/** /**
* @brief Flush the message queue. * @brief Flush the message queue.
* @param[in] queue Queue of messages.
*/ */
bool flushQueue(); bool flushQueue(udp::BufferQueue* queue);
protected: protected:
sockaddr_storage m_addr; sockaddr_storage m_addr;
uint32_t m_addrLen; uint32_t m_addrLen;
udp::Socket* m_socket; udp::Socket* m_socket;
static std::mutex m_queueMutex; static std::mutex s_flushMtx;
static bool m_queueFlushing;
udp::BufferVector m_buffers;
uint32_t m_failedReadCnt; uint32_t m_failedReadCnt;
bool m_debug; bool m_debug;
private:
/**
* @brief Helper to ensure buffers are deleted.
*/
void deleteBuffers();
}; };
} // namespace network } // namespace network

@ -1,335 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @defgroup rest REST Services
* @brief Implementation for REST services.
* @ingroup network_core
* @defgroup http Embedded HTTP Core
* @brief Implementation for basic HTTP services.
* @ingroup rest
*
* @file RequestDispatcher.h
* @ingroup rest
*/
#if !defined(__REST__DISPATCHER_H__)
#define __REST__DISPATCHER_H__
#include "common/Defines.h"
#include "common/network/rest/http/HTTPPayload.h"
#include "common/Log.h"
#include <functional>
#include <map>
#include <string>
#include <regex>
#include <memory>
namespace network
{
namespace rest
{
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Structure representing a REST API request match.
* @ingroup rest
*/
struct RequestMatch : std::smatch {
/**
* @brief Initializes a new instance of the RequestMatch structure.
* @param m String matcher.
* @param c Content.
*/
RequestMatch(const std::smatch& m, const std::string& c) : std::smatch(m), content(c) { /* stub */ }
std::string content;
};
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Structure representing a request matcher.
* @ingroup rest
*/
template<typename Request, typename Reply>
struct RequestMatcher {
typedef std::function<void(const Request&, Reply&, const RequestMatch&)> RequestHandlerType;
/**
* @brief Initializes a new instance of the RequestMatcher structure.
* @param expression Matching expression.
*/
explicit RequestMatcher(const std::string& expression) : m_expression(expression), m_isRegEx(false) { /* stub */ }
/**
* @brief Handler for GET requests.
* @param handler GET request handler.
* @return RequestMatcher* Instance of a RequestMatcher.
*/
RequestMatcher<Request, Reply>& get(RequestHandlerType handler) {
m_handlers[HTTP_GET] = handler;
return *this;
}
/**
* @brief Handler for POST requests.
* @param handler POST request handler.
* @return RequestMatcher* Instance of a RequestMatcher.
*/
RequestMatcher<Request, Reply>& post(RequestHandlerType handler) {
m_handlers[HTTP_POST] = handler;
return *this;
}
/**
* @brief Handler for PUT requests.
* @param handler PUT request handler.
* @return RequestMatcher* Instance of a RequestMatcher.
*/
RequestMatcher<Request, Reply>& put(RequestHandlerType handler) {
m_handlers[HTTP_PUT] = handler;
return *this;
}
/**
* @brief Handler for DELETE requests.
* @param handler DELETE request handler.
* @return RequestMatcher* Instance of a RequestMatcher.
*/
RequestMatcher<Request, Reply>& del(RequestHandlerType handler) {
m_handlers[HTTP_DELETE] = handler;
return *this;
}
/**
* @brief Handler for OPTIONS requests.
* @param handler OPTIONS request handler.
* @return RequestMatcher* Instance of a RequestMatcher.
*/
RequestMatcher<Request, Reply>& options(RequestHandlerType handler) {
m_handlers[HTTP_OPTIONS] = handler;
return *this;
}
/**
* @brief Helper to determine if the request matcher is a regular expression.
* @returns bool True, if request matcher is a regular expression, otherwise false.
*/
bool regex() const { return m_isRegEx; }
/**
* @brief Helper to set the regular expression flag.
* @param regEx Flag indicating whether or not the request matcher is a regular expression.
*/
void setRegEx(bool regEx) { m_isRegEx = regEx; }
/**
* @brief Helper to handle the actual request.
* @param request HTTP request.
* @param reply HTTP reply.
* @param what What matched.
*/
void handleRequest(const Request& request, Reply& reply, const std::smatch &what) {
// dispatching to matching based on handler
RequestMatch match(what, request.content);
auto& handler = m_handlers[request.method];
if (handler) {
handler(request, reply, match);
}
}
private:
std::string m_expression;
bool m_isRegEx;
std::map<std::string, RequestHandlerType> m_handlers;
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements RESTful web request dispatching.
* @tparam Request HTTP request.
* @tparam Reply HTTP reply.
*/
template<typename Request = http::HTTPPayload, typename Reply = http::HTTPPayload>
class RequestDispatcher {
typedef RequestMatcher<Request, Reply> MatcherType;
public:
/**
* @brief Initializes a new instance of the RequestDispatcher class.
*/
RequestDispatcher() : m_basePath(), m_debug(false) { /* stub */ }
/**
* @brief Initializes a new instance of the RequestDispatcher class.
* @param debug Flag indicating whether or not verbose logging should be enabled.
*/
RequestDispatcher(bool debug) : m_basePath(), m_debug(debug) { /* stub */ }
/**
* @brief Initializes a new instance of the RequestDispatcher class.
* @param basePath
* @param debug Flag indicating whether or not verbose logging should be enabled.
*/
RequestDispatcher(const std::string& basePath, bool debug) : m_basePath(basePath), m_debug(debug) { /* stub */ }
/**
* @brief Helper to match a request patch.
* @param expression Matching expression.
* @param regex Flag indicating whether or not this match is a regular expression.
* @returns MatcherType Instance of a request matcher.
*/
MatcherType& match(const std::string& expression, bool regex = false)
{
MatcherTypePtr& p = m_matchers[expression];
if (!p) {
if (m_debug) {
::LogDebug(LOG_REST, "creating RequestDispatcher, expression = %s", expression.c_str());
}
p = std::make_shared<MatcherType>(expression);
} else {
if (m_debug) {
::LogDebug(LOG_REST, "fetching RequestDispatcher, expression = %s", expression.c_str());
}
}
p->setRegEx(regex);
return *p;
}
/**
* @brief Helper to handle HTTP request.
* @param request HTTP request.
* @param reply HTTP reply.
*/
void handleRequest(const Request& request, Reply& reply)
{
for (const auto& matcher : m_matchers) {
std::smatch what;
if (!matcher.second->regex()) {
if (request.uri.find(matcher.first) != std::string::npos) {
if (m_debug) {
::LogDebug(LOG_REST, "non-regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str());
}
//what = matcher.first;
// ensure CORS headers are added
reply.headers.add("Access-Control-Allow-Origin", "*");
reply.headers.add("Access-Control-Allow-Methods", "*");
reply.headers.add("Access-Control-Allow-Headers", "*");
if (request.method == HTTP_OPTIONS) {
reply.status = http::HTTPPayload::OK;
}
matcher.second->handleRequest(request, reply, what);
return;
}
} else {
if (std::regex_match(request.uri, what, std::regex(matcher.first))) {
if (m_debug) {
::LogDebug(LOG_REST, "regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str());
}
matcher.second->handleRequest(request, reply, what);
return;
}
}
}
::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str());
reply = http::HTTPPayload::statusPayload(http::HTTPPayload::BAD_REQUEST, "application/json");
}
private:
typedef std::shared_ptr<MatcherType> MatcherTypePtr;
std::string m_basePath;
std::map<std::string, MatcherTypePtr> m_matchers;
bool m_debug;
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements a generic basic request dispatcher.
* @tparam Request HTTP request.
* @tparam Reply HTTP reply.
*/
template<typename Request = http::HTTPPayload, typename Reply = http::HTTPPayload>
class BasicRequestDispatcher {
public:
typedef std::function<void(const Request&, Reply&)> RequestHandlerType;
/**
* @brief Initializes a new instance of the BasicRequestDispatcher class.
*/
BasicRequestDispatcher() { /* stub */ }
/**
* @brief Initializes a new instance of the BasicRequestDispatcher class.
* @param handler Instance of a RequestHandlerType for this dispatcher.
*/
BasicRequestDispatcher(RequestHandlerType handler) : m_handler(handler) { /* stub */ }
/**
* @brief Helper to handle HTTP request.
* @param request HTTP request.
* @param reply HTTP reply.
*/
void handleRequest(const Request& request, Reply& reply)
{
if (m_handler) {
m_handler(request, reply);
}
}
private:
RequestHandlerType m_handler;
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements a generic debug request dispatcher.
* @tparam Request HTTP request.
* @tparam Reply HTTP reply.
*/
template<typename Request = http::HTTPPayload, typename Reply = http::HTTPPayload>
class DebugRequestDispatcher {
public:
/**
* @brief Initializes a new instance of the DebugRequestDispatcher class.
*/
DebugRequestDispatcher() { /* stub */ }
/**
* @brief Helper to handle HTTP request.
* @param request HTTP request.
* @param reply HTTP reply.
*/
void handleRequest(const Request& request, Reply& reply)
{
for (auto header : request.headers.headers())
::LogDebugEx(LOG_REST, "DebugRequestDispatcher::handleRequest()", "header = %s, value = %s", header.name.c_str(), header.value.c_str());
::LogDebugEx(LOG_REST, "DebugRequestDispatcher::handleRequest()", "content = %s", request.content.c_str());
}
};
typedef RequestDispatcher<http::HTTPPayload, http::HTTPPayload> DefaultRequestDispatcher;
} // namespace rest
} // namespace network
#endif // __REST__DISPATCHER_H__

@ -1,242 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file ClientConnection.h
* @ingroup http
*/
#if !defined(__REST_HTTP__CLIENT_CONNECTION_H__)
#define __REST_HTTP__CLIENT_CONNECTION_H__
#include "common/Defines.h"
#include "common/network/rest/http/HTTPLexer.h"
#include "common/network/rest/http/HTTPPayload.h"
#include "common/Log.h"
#include <array>
#include <memory>
#include <utility>
#include <iterator>
#include <asio.hpp>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class represents a single connection from a client.
* @tparam RequestHandlerType Type representing a request handler.
* @ingroup http
*/
template <typename RequestHandlerType>
class ClientConnection {
public:
auto operator=(ClientConnection&) -> ClientConnection& = delete;
auto operator=(ClientConnection&&) -> ClientConnection& = delete;
ClientConnection(ClientConnection&) = delete;
/**
* @brief Initializes a new instance of the ClientConnection class.
* @param socket TCP socket for this connection.
* @param handler Request handler for this connection.
*/
explicit ClientConnection(asio::ip::tcp::socket socket, RequestHandlerType& handler) :
m_socket(std::move(socket)),
m_requestHandler(handler),
m_lexer(HTTPLexer(true))
{
/* stub */
}
/**
* @brief Start the first asynchronous operation for the connection.
*/
void start() { read(); }
/**
* @brief Stop all asynchronous operations associated with the connection.
*/
void stop()
{
try
{
ensureNoLinger();
if (m_socket.is_open()) {
m_socket.close();
}
}
catch(const std::exception&) { /* ignore */ }
}
/**
* @brief Helper to enable the SO_LINGER socket option during shutdown.
*/
void ensureNoLinger()
{
try
{
// enable SO_LINGER timeout 0
asio::socket_base::linger linger(true, 0);
m_socket.set_option(linger);
}
catch(const asio::system_error& e)
{
asio::error_code ec = e.code();
if (ec) {
::LogError(LOG_REST, "ClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value());
}
}
}
/**
* @brief Perform an synchronous write operation.
* @param request HTTP request payload.
*/
void send(HTTPPayload request)
{
m_sizeToTransfer = m_bytesTransferred = 0U;
request.attachHostHeader(m_socket.remote_endpoint());
write(request);
}
private:
/**
* @brief Perform an asynchronous read operation.
*/
void read()
{
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
HTTPLexer::ResultType result;
char* content;
try
{
if (m_sizeToTransfer > 0U && (m_bytesTransferred + bytes_transferred) < m_sizeToTransfer) {
::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred);
m_bytesTransferred += bytes_transferred;
read();
}
else {
if (m_sizeToTransfer > 0U) {
// final copy
::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred);
m_bytesTransferred += bytes_transferred;
m_sizeToTransfer = 0U;
bytes_transferred = m_bytesTransferred;
// reset lexer and re-parse the full content
m_lexer.reset();
std::tie(result, content) = m_lexer.parse(m_request, m_fullBuffer.data(), m_fullBuffer.data() + bytes_transferred);
} else {
::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred);
m_bytesTransferred += bytes_transferred;
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
}
// determine content length
std::string contentLength = m_request.headers.find("Content-Length");
if (contentLength != "") {
size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
// setup a full read if necessary
if (length > bytes_transferred && m_sizeToTransfer == 0U) {
m_sizeToTransfer = length;
}
if (m_sizeToTransfer > 0U) {
result = HTTPLexer::CONTINUE;
} else {
m_request.content = std::string(content, length);
}
}
m_request.headers.add("RemoteHost", m_socket.remote_endpoint().address().to_string());
if (result == HTTPLexer::GOOD) {
m_sizeToTransfer = m_bytesTransferred = 0U;
m_requestHandler.handleRequest(m_request, m_reply);
}
else if (result == HTTPLexer::BAD) {
m_sizeToTransfer = m_bytesTransferred = 0U;
return;
}
else {
read();
}
}
}
catch(const std::exception& e) { ::LogError(LOG_REST, "ClientConnection::read(), %s", ec.message().c_str()); }
}
else if (ec != asio::error::operation_aborted) {
if (ec) {
::LogError(LOG_REST, "ClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value());
}
stop();
}
});
}
/**
* @brief Perform an synchronous write operation.
* @param request HTTP request payload.
*/
void write(HTTPPayload request)
{
try
{
auto buffers = request.toBuffers();
asio::write(m_socket, buffers);
}
catch(const asio::system_error& e)
{
asio::error_code ec = e.code();
if (ec) {
::LogError(LOG_REST, "ClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
try
{
// initiate graceful connection closure
asio::error_code ignored_ec;
m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
catch(const std::exception& e) {
::LogError(LOG_REST, "ClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
}
}
}
}
asio::ip::tcp::socket m_socket;
RequestHandlerType& m_requestHandler;
std::size_t m_sizeToTransfer;
std::size_t m_bytesTransferred;
std::array<char, 65535> m_fullBuffer;
std::array<char, 4096> m_buffer;
HTTPPayload m_request;
HTTPLexer m_lexer;
HTTPPayload m_reply;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__CLIENT_CONNECTION_H__

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save

Powered by TurnKey Linux.