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
parent
e9abdf63aa
commit
274a8f23fc
@ -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,
|
||||||
|
|||||||
Binary file not shown.
@ -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, ¤tModemState) < 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, ¤tModemState) < 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__
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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__
|
||||||
@ -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__
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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__
|
||||||
@ -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__
|
||||||
@ -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…
Reference in new issue