R04J32 Merge to Master (#95)

* implement user-defined adj site mapping to allow fine grained control over neighboring site announcements;

* BUGFIX: patch issue where the FNE would not maintain the source peer ID for certain routing scenarios;

* fix typo in original implementation, peerId should be the peerId and ssrc should become the originating peer ID; remove peer check throwing a warning;

* log ssrc for various data points (the ssrc is the RTP originating sync source which would be the peer ID of the origination of a RTP packet);

* bump build number;

* add peer ID masking support (this is useful for FNEs that are "public" links where internal peer IDs don't need to be retained); more work for maintaining the originating stream sync source; refactor how the FNE handles peer network connections;

* throw a warning if the user configured more then 8 upstream peer connections (more than this number can cause performance problems);

* make non-peer-link peer ID masking optional;

* add checking for traffic repeat to verify we are not trying to send traffic back to the source;

* add checking for traffic repeat to verify we are not trying to send traffic back to an external source;

* convert peer network protocol packet processing to a threaded model;

* update copyright dates;

* implement REST API for manipulating adjacent site map entries;

* fix missed REST API initializer, need to pass the adj site map;

* add DFSI/V.24 full duplex option (this allows dvmhost to repeat incoming frames back out);

* Fix incorrect check of DFSI and FSC operating modes that resulted in a segfault (#92)

Co-authored-by: faulty <faulty@evilcomputing.net>

* document p25CorrCount in depth more; set the default p25CorrCount to 2;

* properly call PacketBuffers clear() before leaving a scope to ensure contained buffers are deleted (otherwise we'll leak memory);

* skip the first 6 bits of the TIA-102 DFSI VHDR (this is very strange, and is probably not kosher);

* transparently pass voice frames with FID $20 (assumed to be Kenwood) when the PF flag is set (this allows Kenwoods flavor of encryption to pass);

* relabel FID_DMRA to FID_MOT (this feature ID is assigned to Motorola); handle more conditions for FID $20 (Kenwood) on voice frames;

* remove filterHeaders (this is deprecated, HDUs aren't sent over the network); perform frame length validation for P25 network frames (unlike DMR and NXDN P25 frames are variable length, requring length validation to prevent buffer under or overflows); fix issue with addr variable not being freed in InfluxDB handler;

* [EXPERIMENTAL] add very initial support for dvmpatch to talk to a MMDVM P25 Gateway in P25 mode;

* fixup dvmpatch support for talking to MMDVM P25 Gateway;

* fix up handling of MMDVM call termination;

* more work on better signalling end of call for MMDVM P25 gateway patches; better log traffic from MMDVM;

* correct some incorrect timing;

* add support for properly authorizing a peer to send Inhibit/Uninhibit commands; add some NXDN constants for remote control;

* add columns to main peered peer list to represent if a peer is allowed to inhibit;

* fixup informational logging;

* add auto generation of a peer password; slightly increase the dialog size of the peer edit window;

* fix TIA VHDR incorrect length; correct gatekeep accidentally setting TIA StartOfStream to 4 bytes instead of 3, because you know the TIA spec is incomprehensible with its bit alignment;

* add some permanent log trace for DFSI over UDP tracing; correct startOfStreamTIA incorrectly sending a LDU1 NID; correct some offsets;

* make sure we send out heartbeats *before* the heartbeat time;

* send Start of Stream with voice data;

* free memory in error case; remove unused variables;

* correct some buffer allocations; ensure the AES class is deleted after use;

* implement support for legacy radio affiliation (like P25) for DMR and NXDN (this fixes an issue where conventional DMR and NXDN systems would be unable to transmit onto affiliation only TGs); correct bad CSBK decoding in the FNE (forgot to check for dataSync);

* code cleanup to correct compiler warnings;

* add string file meta data info to Win32 EXEs; add icons to Win32 EXEs;

* migrate and condense analog audio helper and G.711 codec routines into a common class called AnalogAudio for easy reuse; stub network protocol entries to eventually handle analog audio;

* add support in the DVM network protocol for analog FM audio traffic; add support to the DVM FNE to properly switch analog FM audio traffic; bump revision from H to J;

* add analog enable flag to fne config YAML;

* prelim work to make dvmbridge pass analog audio directly into FNE network;

* handle locking inside deleteBuffers();

* refactor FrameQueue mutex handling; don't start and stop the thread pool when the peer network opens/closes;

* use unique_ptr for compression class instead of passing raw pointers;

* refactor FrameQueue from using a std::unordered_map to a simple fixed array that is directly controlled;

* refactor compression slightly;

* better handle multi-threaded problems;

* use a std::vector instead of a classical C allocated array for timestamp list; add error/logic checking for V.24/DFSI where the VHDR may send a TGID0 during a call causing a call rejection;

* make timestamps vector static;

* dump out of VHDR processing if call is in progress;

* bump tarball version build stamp;

* add option to forcibly allow TGID0 for badly behaved systems this will cause dvmhost to accept the call and not drop it, with the caveat that the TGID will be rewritten to TGID1, the network stack simply cannot service a TGID of 0 and as such we must rewrite the TGID a TIA-102 valid TGID;

* simplify late entry handling where V.24 may send a dstId of 0;

* whoops correct my own stupidity;

* whoops correct my own stupidity (again);

* add opcodes for Harris User Alias and GPS; add opcode for Motorola GPS on PTT; decode Harris User Alias in LDU LCs;

* attempt at decoding the LDU RS values for both V.24 and TIA DFSI, this does not abort decoding in the ModemV24 right now and will simply log an error if the RS values for the LDU1 or LDU2 are to badly invalid;

* alter field data based on recent testing; enhance and update debug log entries; add debug logging for Voice 1 and 10 start of voice frames for V.24;

* add missing tag information for V.24 PDU;

* fix incorrect handling of RFSS ID in host setup;

* experimental fix to ignore wildly illegal TGIDs in the HDU;

* simplify the V.24 HDU TGID logic, entirely ignore the TGID presented in the HDU and just wait for the LDU1 to set the rfLastDstId;

* add grantDemand documentation to dvmpatch;

* [EXPERIMENTAL] add experimental support for cross-encrypting patched P25 traffic;

* reset call algo when a P25 call ends;

* properly handle algo ID's for source and destinations;

* missed reverseCrypto check;

* [EXPERIMENTAL] for some configurations (**you know who you are god damn it**), allow dvmhost to be configured to "idle" on a non-zero TG when the RF talkgroup hangtimer stops, this effectively disables the promiscuous network Rx of the host for certain conventional operations, and requires RF-based TG steering to change the active talkgroup; cleanup the macros for DMR and NXDN that perform various repeated checks;

* major refactor for V.24 support;

* hide RSSI2 ICW errors; report on any voice frames reporting more then 0 errors;

* properly sent VHDR1 and VHDR2 with proper opcodes (whoops);

* add experimental support for TDULC over V.24; add some documentation for the V.24 and TIA voice header byte layout;

* experimental support for transporting V.24 over IP;

* initial super frame should start at 1; make sure to use proper constants instead of magic numbers;

* reorganize code slightly;

* cleanup format for, and make slightly more precise trace and error dumping log messaging;

* add option for the FNE to directly log traffic denials to the system log;

* centralize string for illegal RID access;

* log total voice frame errors for TIA/DFSI mode;

* minor PDU refactoring on when network PDU data is sent; correct issue with accepting a conventional data reg;

* disable allowExplicitSourceId by default (not all subscribers support this); correct some bad handling of LC data;

* log sysId and netId info for call start and end on the FNE log;

* add setting netId and sysId to dvmbridge and dvmpatch for future use;

* reorganize and expose decodeLC/encodeLC from LC; correct typo in TDULC headers; add explicit source id TDULC; add LC_GROUP explicit ID flag;

* use shorthand macros where able;

* enable or disable explicit source ID directly, don't require control to be enabled (important for trunked VC);

* minor correction to handling explicit source ID;

* properly set dummy SiteData for dvmbridge and dvmpatch (necessary to set the WACN and SysId);

* handle condition defaulting WACN and SysId if network data for WACN and SysId is 0;

* ensure unencrypted parameters if encryption is disabled;

* copy dvmbridge change to dvmpatch for: ensure unencrypted parameters if encryption is disabled;

* ignore extra ICW opcodes;

* use raw LDU values from the call data instead of processed values if the MFId on the LC isn't TIA standard;

* collate bit errors reported from V.24 or TIA;

* typos;

* allow a group affiliation on Conv+ (DVRS) to terminate a running TG hang timer;

* add missing stop to the timer;

* add WACN/SysId logging to SysView;

* add global to disable transmitting logging to the network (this is used when the UDP socket fails to send data to prevent a crash condition talking to a null socket); unify errno to string message processing for clearity in logs; refactor network reconnect and retry logic to better handle a full connection reset cycle (this is useful for conditions where the network loss isn't transient and is something like a Ethernet or WiFi link drop causing the entire interface to become down temporarily);

* remove unnecessary debug logging;

* allow encoding user alias at the LC level;

* see if we can calculate error percentages;

* get rid of magic numbers for properly defined constants;

* implement proper support for P25 LDU1 and LDU2 to pass call control bytes; allow a end-point to signal that a call is handing over/switching over from one stream ID/source ID to another; implement support on dvmbridge to properly handle stream/source ID switch over;

* log call source switch over events;

* whoops this should be a OR equals not equals;

* correct issue where network frames would be ignored during RF calls causing buffer overflows;

* cleanup and remove unnecessary and confusing C compiler macros;

* cleanup and unify design a bit, for P25 move traffic collision checking into separate function calls;

* add colorize-host.sh helper tool; update colorize-fne.sh tool for DMR and NXDN;

* store status data for a private call;

* during unit registration store the originating source peer ID for a given unit registration; use unit registration source ID to select the destination peer to send private call data to;

* initial experimental implementation of private call routing:
	this introduces a new configuration flag "restrictPrivateCallToRegOnly", when set, this flag will influence how private calls are routed through the system
	private calls using restrictPrivateCallToRegOnly require both the source and destination subscribers to be unit registered with the system, when a private call
	occurs the system will utilize the source peer ID to restrict repeating the private call traffic only to the 2 peers involved in the private call

	FNEs will *always* receive private call traffic

* rework stream validation for U2U calls slightly for P25; ensure P25 always sends the U2U control byte for private calls;

* enhance handling of NXDN LC on the FNE; fix issue where dvmhost would not process a network RCCH burst;

* refactor NXDN CC handling a bit;

* add support to translate a raw DENY/QUEUE/CAUSE/REASON value for DMR/P25/NXDN into a human readable string;

* whoops hastily missed std::string -> c_str conversion;

* ensure SSRC is maintained for unit registration announcements;

* more implementation for private call routing;

* add missing clear flag to DMR payload activate RPC;

* correct crash for fsc set to true when not using DFSI TIA-102/UDP;

* add support to disable a failed CRC-32 for P25 PDU data; ignore CRC-32 errors for AMBT PDUs;

* Add support in dvmbridge for a serial PTT activation switch.  RTS is asserted on the serial port defined in bridge-config.yml for the duration of audio received, then is removed. (#102)

* P25 PDU packet handling refactor (for future use);

* remove test code;

* fix issue where PDU RSPs weren't being sent to the FNE; correct timing around ARP and packet retry when subscriber is not ready;

* add some more verbose logging;

* experimental changes to PDU data handling;

* correct CSV parsing for iden, peer list and RID lookup tables (we would skip parameters if they were empty); make the FNE P25 packet data handler operate in the TIA-102 asymmetric addressing mode (as would be required with FNE configurations);

---------

Co-authored-by: Jamie <25770089+faultywarrior@users.noreply.github.com>
Co-authored-by: faulty <faulty@evilcomputing.net>
Co-authored-by: Lorenzo L. Romero <lorenzolrom@gmail.com>
4.32j_maint 2025-09-03
Bryan Biedenkapp 5 months ago committed by GitHub
parent fcdec00f32
commit 7c2bfb3914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,14 @@
#
# Digital Voice Modem - Adj. Site Map
#
#
# Peer List
#
peers:
# Peer ID.
- peerId: 1234567
# Flag indicating whether this talkgroup is active or not.
active: true
# List of peer IDs that are neighbors for this peer.
neighbors: []

@ -125,6 +125,11 @@ system:
# Textual Name # Textual Name
identity: BRIDGE identity: BRIDGE
# Network ID (WACN).
netId: BB800
# System ID.
sysId: 001
# PCM audio gain for received (from digital network) audio frames. # PCM audio gain for received (from digital network) audio frames.
# - This is used to apply gain to the decoded IMBE/AMBE audio, post-decoding. # - This is used to apply gain to the decoded IMBE/AMBE audio, post-decoding.
rxAudioGain: 1.0 rxAudioGain: 1.0
@ -145,7 +150,7 @@ system:
# - (Not used when utilizing external USB vocoder!) # - (Not used when utilizing external USB vocoder!)
vocoderEncoderAudioGain: 3.0 vocoderEncoderAudioGain: 3.0
# Audio transmit mode (1 - DMR, 2 - P25). # Audio transmit mode (1 - DMR, 2 - P25, 3 - Analog).
txMode: 1 txMode: 1
# Relative sample level for VOX to activate. # Relative sample level for VOX to activate.
@ -179,3 +184,9 @@ system:
trace: false trace: false
# Flag indicating whether or not debug logging is enabled. # Flag indicating whether or not debug logging is enabled.
debug: false debug: false
# RTS PTT Configuration
# Flag indicating whether RTS PTT control is enabled.
rtsPttEnable: false
# Serial port device for RTS PTT control (e.g., /dev/ttyUSB0).
rtsPttPort: "/dev/ttyUSB0"

@ -155,6 +155,10 @@ protocols:
ignoreAffiliationCheck: false ignoreAffiliationCheck: false
# Flag indicating the host should send a network grant demand for conventional traffic. # Flag indicating the host should send a network grant demand for conventional traffic.
convNetGrantDemand: false convNetGrantDemand: false
# Flag indicating the fallback legacy group registration for radios that do not support group affiliation.
# (Useful for alerting the FNE to affiliations to TGIDs for radios that do not properly support group
# affiliation.)
legacyGroupReg: false
# Flag indicating whether or not received RF embedded LC data only should be transmitted. # Flag indicating whether or not received RF embedded LC data only should be transmitted.
embeddedLCOnly: false embeddedLCOnly: false
# Flag indicating whether talker alias data should be dumped to the log. # Flag indicating whether talker alias data should be dumped to the log.
@ -240,12 +244,16 @@ protocols:
# (This applies only in conventional operations where channel granting is utilized and RF-only talkgroup # (This applies only in conventional operations where channel granting is utilized and RF-only talkgroup
# steering is required.) # steering is required.)
disableNetworkGrant: false disableNetworkGrant: false
# Flag indicating whether or not TGID 0 (P25 blackhole talkgroup) will be allowed.
# (Normally this should *never* be enabled, it is here to support poorly behaved systems that transmit
# TGID 0 for some reason.)
forceAllowTG0: false
# Flag indicating whether or not a TGID will be tested for affiliations before being granted. # Flag indicating whether or not a TGID will be tested for affiliations before being granted.
ignoreAffiliationCheck: false ignoreAffiliationCheck: false
# Flag indicating that the host will attempt to automatically inhibit unauthorized RIDs (those not in the # Flag indicating that the host will attempt to automatically inhibit unauthorized RIDs (those not in the
# RID ACL list). # RID ACL list).
inhibitUnauthorized: false inhibitUnauthorized: false
# Flag indicating the fallback legacy group grant for radios that do not support group affilition to # Flag indicating the fallback legacy group grant for radios that do not support group affiliation to
# have group grants transmitted. (Useful for alerting the FNE to affiliations to TGIDs for radios that # have group grants transmitted. (Useful for alerting the FNE to affiliations to TGIDs for radios that
# do not properly support group affiliation.) # do not properly support group affiliation.)
legacyGroupGrnt: true legacyGroupGrnt: true
@ -267,6 +275,8 @@ protocols:
dumpDataPacket: false dumpDataPacket: false
# Flag indicating whether or not this host will repeat P25 data traffic. # Flag indicating whether or not this host will repeat P25 data traffic.
repeatDataPacket: true repeatDataPacket: true
# Flag indicating whether or not the host will ignore CRC errors in P25 PDU data packets.
ignoreDataCRC: false
# Flag indicating whether verbose dumping of P25 TSBK data is enabled. # Flag indicating whether verbose dumping of P25 TSBK data is enabled.
dumpTsbkData: false dumpTsbkData: false
# Amount of time to hang after a voice call. # Amount of time to hang after a voice call.
@ -278,7 +288,7 @@ protocols:
# Flag indicating that unit-to-unit availiability checks should be performed for a private call. # Flag indicating that unit-to-unit availiability checks should be performed for a private call.
unitToUnitAvailCheck: false unitToUnitAvailCheck: false
# Flag indicating explicit source ID support is enabled. # Flag indicating explicit source ID support is enabled.
allowExplicitSourceId: true allowExplicitSourceId: false
# Flag indicating whether or not the host will respond to SNDCP data requests. # Flag indicating whether or not the host will respond to SNDCP data requests.
sndcpSupport: false sndcpSupport: false
# BER/Error threshold for silencing voice packets. (0 or 1233 disables) # BER/Error threshold for silencing voice packets. (0 or 1233 disables)
@ -319,6 +329,10 @@ protocols:
# Flag indicating whether or not a TGID will be tested for affiliations before being granted. # Flag indicating whether or not a TGID will be tested for affiliations before being granted.
ignoreAffiliationCheck: false ignoreAffiliationCheck: false
# Flag indicating the fallback legacy group registration for radios that do not support group affiliation.
# (Useful for alerting the FNE to affiliations to TGIDs for radios that do not properly support group
# affiliation.)
legacyGroupReg: false
# Flag indicating the host should verify group affiliation. # Flag indicating the host should verify group affiliation.
verifyAff: false verifyAff: false
# Flag indicating the host should verify unit registration. # Flag indicating the host should verify unit registration.
@ -470,6 +484,10 @@ system:
pSuperGroup: FFFE pSuperGroup: FFFE
# Announcment Talkgroup Group. # Announcment Talkgroup Group.
announcementGroup: FFFE announcementGroup: FFFE
# Default Talkgroup for idle network traffic.
# NOTE: TGID 0 is used here to indicate "promiscuous" mode, where first-come-first-serve traffic from the network
# is allowed.
defaultNetIdleTalkgroup: 0
# DMR network ID. # DMR network ID.
dmrNetId: 1 dmrNetId: 1
@ -532,8 +550,12 @@ system:
# Amount of time to wait before starting DMR transmissions after a signal is received. # Amount of time to wait before starting DMR transmissions after a signal is received.
dmrRxDelay: 7 dmrRxDelay: 7
# Amount of packet correlations that should occur before P25 data is returned from the modem to the host. # Amount of packet correlations that should occur before P25 data is returned from the modem to the host.
# (Note: Changing this value will impact P25 protocol stability, and should not be altered.) # (Note: Changing this value will impact P25 protocol stability, and should not be altered. This is set to
p25CorrCount: 8 # a default value of 2 for fast synchronization of a P25 signal, if the air modem is having difficulty
# synchronizing to a P25 signal, this value can be increased to 3 or 4, or beyond. It should be noted that
# increasing this value will increase the time it takes to synchronize to a P25 signal, and may cause
# issues with P25 voice calls (especially encrypted voice calls).)
p25CorrCount: 2
# Size (in bytes) of the DMR transmit FIFO buffer. It is not recommended to adjust this unless absolutely # Size (in bytes) of the DMR transmit FIFO buffer. It is not recommended to adjust this unless absolutely
# necessary, excessive sizes will cause delays in transmitted frames due to excessive buffering. # necessary, excessive sizes will cause delays in transmitted frames due to excessive buffering.
# Calculation Formula: (DMR Frame Size (33 bytes) * x Frames) + Pad Bytes = FIFO Buffer Size Bytes # Calculation Formula: (DMR Frame Size (33 bytes) * x Frames) + Pad Bytes = FIFO Buffer Size Bytes
@ -626,6 +648,9 @@ system:
jitter: 200 jitter: 200
# Timer which will reset local/remote call flags if frames aren't received longer than this time in ms # Timer which will reset local/remote call flags if frames aren't received longer than this time in ms
callTimeout: 200 callTimeout: 200
# Flag indicating whether or not the V.24 modem is operating in full-duplex mode.
# (This enables DVM to repeat incoming V.24 frames back out after processing.)
fullDuplex: false
# Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection. # Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection.
fsc: false fsc: false
# Sets the heartbeat interval for the FSC connection. # Sets the heartbeat interval for the FSC connection.

@ -48,6 +48,10 @@ master:
# Flag indicating whether or not verbose debug logging is enabled. # Flag indicating whether or not verbose debug logging is enabled.
debug: false debug: false
# Flag indicating whether or not denied traffic will be logged.
# (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.)
logDenials: false
# Maximum number of concurrent packet processing workers. # Maximum number of concurrent packet processing workers.
workers: 16 workers: 16
@ -70,6 +74,8 @@ master:
allowP25Traffic: true allowP25Traffic: true
# Flag indicating whether or not NXDN traffic will be passed. # Flag indicating whether or not NXDN traffic will be passed.
allowNXDNTraffic: true allowNXDNTraffic: true
# Flag indicating whether or not analog traffic will be passed.
allowAnalogTraffic: false
# Flag indicating whether packet data will be passed. # Flag indicating whether packet data will be passed.
disablePacketData: false disablePacketData: false
@ -87,6 +93,8 @@ master:
# Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating. # Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating.
restrictGrantToAffiliatedOnly: false restrictGrantToAffiliatedOnly: false
# Flag indicating whether or not a private call will only be routed to the network peers the RID registers with.
restrictPrivateCallToRegOnly: false
# Flag indicating whether or not a adjacent site broadcasts will pass to any peers. # Flag indicating whether or not a adjacent site broadcasts will pass to any peers.
disallowAdjStsBcast: false disallowAdjStsBcast: false
@ -103,9 +111,14 @@ master:
# Flag indicating whether or not a TDULC call terminations will pass to any peers. # Flag indicating whether or not a TDULC call terminations will pass to any peers.
disallowCallTerm: false disallowCallTerm: false
# Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID). # Flag indicating whether or not the FNE will mask all outbound traffic to use the FNE's own peer ID.
filterHeaders: true # (This is useful for FNEs that are public facing, and the originating traffic peer ID should be masked.)
# Flag indicating that terminators will be filtered by destination ID (i.e. valid RID or valid TGID). maskOutboundPeerID: false
# Flag indicating whether or not the FNE will mask only non-Peer-Link outbound traffic to use the FNE's own peer ID.
# (This is useful for networked FNEs that are have a mix of connections, and the originating traffic peer ID to non-Peer-Link FNEs should be masked.)
maskOutboundPeerIDForNonPeerLink: false
# Flag indicating that P25 terminators will be filtered by destination ID (i.e. valid RID or valid TGID).
filterTerminators: true filterTerminators: true
# Flag indicating the FNE will drop all inbound Unit-to-Unit calls. # Flag indicating the FNE will drop all inbound Unit-to-Unit calls.
@ -150,6 +163,15 @@ master:
# Amount of time between updates of talkgroup rules file. (minutes) # Amount of time between updates of talkgroup rules file. (minutes)
time: 30 time: 30
#
# Adj. Site Map Configuration
#
adj_site_map:
# Full path to the Adj. Site Map file.
file: adj_site_map.yml
# Amount of time between updates of Adj. Site Map file. (minutes)
time: 30
# #
# External Peers # External Peers
# #

@ -60,21 +60,62 @@ network:
sourceTGID: 1 sourceTGID: 1
# Source Slot for received/transmitted audio frames. # Source Slot for received/transmitted audio frames.
sourceSlot: 1 sourceSlot: 1
# Source Traffic Encryption
srcTek:
# Flag indicating whether or not traffic encryption is enabled.
enable: false
# Traffic Encryption Key Algorithm
# aes - AES-256 Encryption
# arc4 - ARC4/ADP Encryption
tekAlgo: "aes"
# Traffic Encryption Key ID
tekKeyId: 1
# Destination Talkgroup ID for transmitted/received audio frames. # Destination Talkgroup ID for transmitted/received audio frames.
destinationTGID: 1 destinationTGID: 1
# Destination Slot for received/transmitted audio frames. # Destination Slot for received/transmitted audio frames.
destinationSlot: 1 destinationSlot: 1
# Destination Traffic Encryption
dstTek:
# Flag indicating whether or not traffic encryption is enabled.
enable: false
# Traffic Encryption Key Algorithm
# aes - AES-256 Encryption
# arc4 - ARC4/ADP Encryption
tekAlgo: "aes"
# Traffic Encryption Key ID
tekKeyId: 1
# Flag indicating whether or not the patch is two-way. # Flag indicating whether or not the patch is two-way.
twoWay: false twoWay: false
# Hostname/IP address of MMDVM gateway to connect to.
mmdvmGatewayAddress: 127.0.0.1
# Port number to connect to.
mmdvmGatewayPort: 42020
# Local port number for gateway to connect to.
localGatewayPort: 32010
system: system:
# Textual Name # Textual Name
identity: PATCH identity: PATCH
# Network ID (WACN).
netId: BB800
# System ID.
sysId: 001
# Digital mode (1 - DMR, 2 - P25). # Digital mode (1 - DMR, 2 - P25).
digiMode: 1 digiMode: 1
# Flag indicating whether a network grant demand packet will be sent before audio.
grantDemand: false
# Flag indicating whether or not the patch is from/to a MMDVM P25 reflector.
mmdvmP25Reflector: false
# Flag indicating whether or not trace logging is enabled. # Flag indicating whether or not trace logging is enabled.
trace: false trace: false
# Flag indicating whether or not debug logging is enabled. # Flag indicating whether or not debug logging is enabled.

@ -1,9 +1,9 @@
# #
# This file sets the valid peer IDs allowed on a FNE. # This file sets the valid peer IDs allowed on a FNE.
# #
# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),<newline>" # 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>"
#1234,,0,,1, #1234,,0,,1,0,
#5678,MYSECUREPASSWORD,0,,0, #5678,MYSECUREPASSWORD,0,,0,0,
#9876,MYSECUREPASSWORD,1,,0, #9876,MYSECUREPASSWORD,1,,0,0,
#5432,MYSECUREPASSWORD,,Peer Alias 1,0, #5432,MYSECUREPASSWORD,,Peer Alias 1,0,0,
#1012,MYSECUREPASSWORD,1,Peer Alias 2,1, #1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0,

@ -42,6 +42,7 @@ if (ENABLE_SETUP_TUI)
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util)
else () else ()
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmhost PRIVATE ${dvmhost_RC})
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
else () else ()
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util)
@ -60,7 +61,7 @@ set(CPACK_PACKAGE_VENDOR "DVMProject")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors")
set(CPACK_DEBIAN_PACKAGE_VERSION "R04Hxx") set(CPACK_DEBIAN_PACKAGE_VERSION "R04Jxx")
set(CPACK_DEBIAN_PACKAGE_RELEASE "0") set(CPACK_DEBIAN_PACKAGE_RELEASE "0")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject")
@ -76,6 +77,9 @@ include(CPack)
# #
include(src/fne/CMakeLists.txt) include(src/fne/CMakeLists.txt)
add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC}) add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC})
if (COMPILE_WIN32)
target_sources(dvmfne PRIVATE ${dvmfne_RC})
endif (COMPILE_WIN32)
target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne) target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne)
@ -128,6 +132,9 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
# #
include(src/remote/CMakeLists.txt) include(src/remote/CMakeLists.txt)
add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC}) add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC})
if (COMPILE_WIN32)
target_sources(dvmcmd PRIVATE ${dvmcmd_RC})
endif (COMPILE_WIN32)
target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote)
@ -137,6 +144,7 @@ target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote)
include(src/bridge/CMakeLists.txt) include(src/bridge/CMakeLists.txt)
add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC}) add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmbridge PRIVATE ${bridge_RC})
target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
else () else ()
if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf")
@ -153,6 +161,7 @@ target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} src src/brid
include(src/patch/CMakeLists.txt) include(src/patch/CMakeLists.txt)
add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC}) add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC})
if (COMPILE_WIN32) if (COMPILE_WIN32)
target_sources(dvmpatch PRIVATE ${patch_RC})
target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
else () else ()
target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads)

@ -35,6 +35,7 @@ option(DEBUG_NXDN_SACCH "" off)
option(DEBUG_NXDN_UDCH "" off) option(DEBUG_NXDN_UDCH "" off)
option(DEBUG_NXDN_LICH "" off) option(DEBUG_NXDN_LICH "" off)
option(DEBUG_NXDN_CAC "" off) option(DEBUG_NXDN_CAC "" off)
option(DEBUG_NXDN_RTCH "" off)
option(DEBUG_P25_PDU_DATA "" off) option(DEBUG_P25_PDU_DATA "" off)
option(DEBUG_P25_HDU "" off) option(DEBUG_P25_HDU "" off)
option(DEBUG_P25_LDU1 "" off) option(DEBUG_P25_LDU1 "" off)
@ -96,6 +97,10 @@ if (DEBUG_NXDN_CAC)
message(CHECK_START "NXDN CAC Debug") message(CHECK_START "NXDN CAC Debug")
add_definitions(-DDEBUG_NXDN_CAC) add_definitions(-DDEBUG_NXDN_CAC)
endif (DEBUG_NXDN_CAC) endif (DEBUG_NXDN_CAC)
if (DEBUG_NXDN_RTCH)
message(CHECK_START "NXDN RTCH Debug")
add_definitions(-DDEBUG_NXDN_RTCH)
endif (DEBUG_NXDN_RTCH)
if (DEBUG_P25_PDU_DATA) if (DEBUG_P25_PDU_DATA)
message(CHECK_START "P25 PDU Data Debug") message(CHECK_START "P25 PDU Data Debug")
add_definitions(-DDEBUG_P25_PDU_DATA) add_definitions(-DDEBUG_P25_PDU_DATA)

@ -4,7 +4,7 @@
# * GPLv2 Open Source. Use is subject to license terms. # * GPLv2 Open Source. Use is subject to license terms.
# * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# * # *
# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL # * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL
# * # *
# */ # */
file(GLOB bridge_SRC file(GLOB bridge_SRC
@ -16,6 +16,14 @@ file(GLOB bridge_SRC
"src/bridge/network/*.h" "src/bridge/network/*.h"
"src/bridge/network/*.cpp" "src/bridge/network/*.cpp"
"src/bridge/win32/*.h"
"src/bridge/*.h" "src/bridge/*.h"
"src/bridge/*.cpp" "src/bridge/*.cpp"
) )
#
# Windows Resource Scripts
#
file(GLOB bridge_RC
"src/bridge/win32/*.rc"
)

File diff suppressed because it is too large Load Diff

@ -32,6 +32,7 @@
#include "audio/miniaudio.h" #include "audio/miniaudio.h"
#include "mdc/mdc_decode.h" #include "mdc/mdc_decode.h"
#include "network/PeerNetwork.h" #include "network/PeerNetwork.h"
#include "RtsPttController.h"
#include <string> #include <string>
#include <mutex> #include <mutex>
@ -47,7 +48,6 @@
// Constants // Constants
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#define MBE_SAMPLES_LENGTH 160
#define NO_BIT_STEAL 0 #define NO_BIT_STEAL 0
#define ECMODE_NOISE_SUPPRESS 0x40 #define ECMODE_NOISE_SUPPRESS 0x40
@ -63,6 +63,7 @@ const uint8_t HALF_RATE_MODE = 0x01U;
const uint8_t TX_MODE_DMR = 1U; const uint8_t TX_MODE_DMR = 1U;
const uint8_t TX_MODE_P25 = 2U; const uint8_t TX_MODE_P25 = 2U;
const uint8_t TX_MODE_ANALOG = 3U;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -93,31 +94,6 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32
void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unitID, void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unitID,
mdc_u8_t extra0, mdc_u8_t extra1, mdc_u8_t extra2, mdc_u8_t extra3, void* context); mdc_u8_t extra0, mdc_u8_t extra1, mdc_u8_t extra2, mdc_u8_t extra3, void* context);
/**
* @brief Helper to convert PCM into G.711 aLaw.
* @param pcm PCM value.
* @return uint8_t aLaw value.
*/
uint8_t encodeALaw(short pcm);
/**
* @brief Helper to convert G.711 aLaw into PCM.
* @param alaw aLaw value.
* @return short PCM value.
*/
short decodeALaw(uint8_t alaw);
/**
* @brief Helper to convert PCM into G.711 MuLaw.
* @param pcm PCM value.
* @return uint8_t MuLaw value.
*/
uint8_t encodeMuLaw(short pcm);
/**
* @brief Helper to convert G.711 MuLaw into PCM.
* @param ulaw MuLaw value.
* @return short PCM value.
*/
short decodeMuLaw(uint8_t ulaw);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -260,6 +236,11 @@ private:
uint32_t m_p25SeqNo; uint32_t m_p25SeqNo;
uint8_t m_p25N; uint8_t m_p25N;
uint32_t m_netId;
uint32_t m_sysId;
uint8_t m_analogN;
bool m_audioDetect; bool m_audioDetect;
bool m_trafficFromUDP; bool m_trafficFromUDP;
uint32_t m_udpSrcId; uint32_t m_udpSrcId;
@ -278,6 +259,12 @@ private:
bool m_trace; bool m_trace;
bool m_debug; bool m_debug;
// RTS PTT Control
bool m_rtsPttEnable;
std::string m_rtsPttPort;
RtsPttController* m_rtsPttController;
bool m_rtsPttActive;
uint16_t m_rtpSeqNo; uint16_t m_rtpSeqNo;
uint32_t m_rtpTimestamp; uint32_t m_rtpTimestamp;
@ -485,12 +472,18 @@ private:
void encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U); void encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U);
/** /**
* @brief Helper to generate outgoing RTP headers. * @brief Helper to process analog network traffic.
* @param msgLen Message Length. * @param buffer
* @param rtpSeq RTP Sequence. * @param length
* @returns uint8_t* Buffer containing the encoded RTP headers.
*/ */
uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); void processAnalogNetwork(uint8_t* buffer, uint32_t length);
/**
* @brief Helper to encode analog network traffic audio frames.
* @param pcm
* @param forcedSrcId
* @param forcedDstId
*/
void encodeAnalogAudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U);
/** /**
* @brief Helper to generate USRP end of transmission * @brief Helper to generate USRP end of transmission
@ -502,6 +495,14 @@ private:
*/ */
void generatePreambleTone(); void generatePreambleTone();
/**
* @brief Helper to generate outgoing RTP headers.
* @param msgLen Message Length.
* @param rtpSeq RTP Sequence.
* @returns uint8_t* Buffer containing the encoded RTP headers.
*/
uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq);
/** /**
* @brief Helper to end a local or UDP call. * @brief Helper to end a local or UDP call.
* @param srcId * @param srcId
@ -517,6 +518,20 @@ private:
*/ */
void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength);
/**
* @brief Helper to initialize RTS PTT control.
* @returns bool True, if RTS PTT was initialized successfully, otherwise false.
*/
bool initializeRtsPtt();
/**
* @brief Helper to assert RTS PTT (start transmission).
*/
void assertRtsPtt();
/**
* @brief Helper to deassert RTS PTT (stop transmission).
*/
void deassertRtsPtt();
/** /**
* @brief Entry point to audio processing thread. * @brief Entry point to audio processing thread.
* @param arg Instance of the thread_t structure. * @param arg Instance of the thread_t structure.

@ -0,0 +1,264 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Bridge
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Lorenzo L. Romero, K2LLR
*
*/
#include "Defines.h"
#include "RtsPttController.h"
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the RtsPttController class. */
RtsPttController::RtsPttController(const std::string& port) :
m_port(port),
m_isOpen(false),
#if defined(_WIN32)
m_fd(INVALID_HANDLE_VALUE)
#else
m_fd(-1)
#endif // defined(_WIN32)
{
assert(!port.empty());
}
/* Finalizes a instance of the RtsPttController class. */
RtsPttController::~RtsPttController()
{
close();
}
/* Opens the serial port for RTS control. */
bool RtsPttController::open()
{
if (m_isOpen)
return true;
#if defined(_WIN32)
assert(m_fd == INVALID_HANDLE_VALUE);
std::string deviceName = m_port;
if (deviceName.find("\\\\.\\") == std::string::npos) {
deviceName = "\\\\.\\" + m_port;
}
m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_fd == INVALID_HANDLE_VALUE) {
::LogError(LOG_HOST, "Cannot open RTS PTT device - %s, err=%04lx", m_port.c_str(), ::GetLastError());
return false;
}
DCB dcb;
if (::GetCommState(m_fd, &dcb) == 0) {
::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError());
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
return false;
}
dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.fParity = FALSE;
dcb.StopBits = ONESTOPBIT;
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDsrSensitivity = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
if (::SetCommState(m_fd, &dcb) == 0) {
::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError());
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
return false;
}
// Clear RTS initially
if (::EscapeCommFunction(m_fd, CLRRTS) == 0) {
::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_port.c_str(), ::GetLastError());
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
return false;
}
#else
assert(m_fd == -1);
m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0);
if (m_fd < 0) {
::LogError(LOG_HOST, "Cannot open RTS PTT device - %s", m_port.c_str());
return false;
}
if (::isatty(m_fd) == 0) {
::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str());
::close(m_fd);
m_fd = -1;
return false;
}
if (!setTermios()) {
::close(m_fd);
m_fd = -1;
return false;
}
#endif // defined(_WIN32)
::LogInfo(LOG_HOST, "RTS PTT Controller opened on %s", m_port.c_str());
m_isOpen = true;
return true;
}
/* Closes the serial port. */
void RtsPttController::close()
{
if (!m_isOpen)
return;
// Clear RTS before closing
clearPTT();
#if defined(_WIN32)
if (m_fd != INVALID_HANDLE_VALUE) {
::CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
}
#else
if (m_fd != -1) {
::close(m_fd);
m_fd = -1;
}
#endif // defined(_WIN32)
m_isOpen = false;
::LogInfo(LOG_HOST, "RTS PTT Controller closed");
}
/* Sets RTS signal high (asserts RTS) to trigger PTT. */
bool RtsPttController::setPTT()
{
if (!m_isOpen)
return false;
#if defined(_WIN32)
if (::EscapeCommFunction(m_fd, SETRTS) == 0) {
::LogError(LOG_HOST, "Cannot set RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError());
return false;
}
#else
uint32_t y;
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
return false;
}
y |= TIOCM_RTS;
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
::LogError(LOG_HOST, "Cannot set RTS PTT for %s", m_port.c_str());
return false;
}
#endif // defined(_WIN32)
::LogDebug(LOG_HOST, "RTS PTT asserted on %s", m_port.c_str());
return true;
}
/* Sets RTS signal low (clears RTS) to release PTT. */
bool RtsPttController::clearPTT()
{
if (!m_isOpen)
return false;
#if defined(_WIN32)
if (::EscapeCommFunction(m_fd, CLRRTS) == 0) {
::LogError(LOG_HOST, "Cannot clear RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError());
return false;
}
#else
uint32_t y;
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
return false;
}
y &= ~TIOCM_RTS;
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
::LogError(LOG_HOST, "Cannot clear RTS PTT for %s", m_port.c_str());
return false;
}
#endif // defined(_WIN32)
::LogDebug(LOG_HOST, "RTS PTT cleared on %s", m_port.c_str());
return true;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Sets the termios settings on the serial port. */
bool RtsPttController::setTermios()
{
#if !defined(_WIN32)
termios termios;
if (::tcgetattr(m_fd, &termios) < 0) {
::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str());
return false;
}
termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK);
termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL);
termios.c_iflag &= ~(IXON | IXOFF | IXANY);
termios.c_oflag &= ~(OPOST);
termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS);
termios.c_cflag |= (CS8 | CLOCAL | CREAD);
termios.c_lflag &= ~(ISIG | ICANON | IEXTEN);
termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
termios.c_cc[VMIN] = 0;
termios.c_cc[VTIME] = 10;
::cfsetospeed(&termios, B9600);
::cfsetispeed(&termios, B9600);
if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) {
::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str());
return false;
}
// Clear RTS initially
uint32_t y;
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
return false;
}
y &= ~TIOCM_RTS;
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
::LogError(LOG_HOST, "Cannot clear RTS for %s", m_port.c_str());
return false;
}
#endif // !defined(_WIN32)
return true;
}

@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Bridge
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Lorenzo L. Romero, K2LLR
*
*/
/**
* @file RtsPttController.h
* @ingroup bridge
* @file RtsPttController.cpp
* @ingroup bridge
*/
#if !defined(__RTS_PTT_CONTROLLER_H__)
#define __RTS_PTT_CONTROLLER_H__
#include "Defines.h"
#include "common/Log.h"
#include <string>
#if defined(_WIN32)
#include <windows.h>
#else
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#endif // defined(_WIN32)
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements RTS PTT control for the bridge.
* @ingroup bridge
*/
class HOST_SW_API RtsPttController {
public:
/**
* @brief Initializes a new instance of the RtsPttController class.
* @param port Serial port device (e.g., /dev/ttyUSB0).
*/
RtsPttController(const std::string& port);
/**
* @brief Finalizes a instance of the RtsPttController class.
*/
~RtsPttController();
/**
* @brief Opens the serial port for RTS control.
* @returns bool True, if port was opened successfully, otherwise false.
*/
bool open();
/**
* @brief Closes the serial port.
*/
void close();
/**
* @brief Sets RTS signal high (asserts RTS) to trigger PTT.
* @returns bool True, if RTS was set successfully, otherwise false.
*/
bool setPTT();
/**
* @brief Sets RTS signal low (clears RTS) to release PTT.
* @returns bool True, if RTS was cleared successfully, otherwise false.
*/
bool clearPTT();
private:
std::string m_port;
bool m_isOpen;
#if defined(_WIN32)
HANDLE m_fd;
#else
int m_fd;
#endif // defined(_WIN32)
/**
* @brief Sets the termios settings on the serial port.
* @returns bool True, if settings are set, otherwise false.
*/
bool setTermios();
};
#endif // __RTS_PTT_CONTROLLER_H__

@ -1,54 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Bridge
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file SampleTimeConversion.h
* @ingroup bridge
*/
#if !defined(__SAMPLE_TIME_CONVERSION_H__)
#define __SAMPLE_TIME_CONVERSION_H__
#include "Defines.h"
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief
* @ingroup bridge
*/
class HOST_SW_API SampleTimeConvert {
public:
/**
* @brief (ms) to sample count conversion
* @param sampleRate Sample rate.
* @param channels Number of audio channels.
* @param ms Number of milliseconds.
* @returns int Number of samples.
*/
static int ToSamples(uint32_t sampleRate, uint8_t channels, int ms)
{
return (int)(((long)ms) * sampleRate * channels / 1000);
}
/**
* @brief Sample count to (ms) conversion
* @param sampleRate Sample rate.
* @param channels Number of audio channels.
* @param samples Number of samples.
* @returns int Number of milliseconds.
*/
static int ToMS(uint32_t sampleRate, uint8_t channels, int samples)
{
return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000);
}
};
#endif // __SAMPLE_TIME_CONVERSION_H__

@ -28,8 +28,8 @@ using namespace network;
/* Initializes a new instance of the PeerNetwork class. */ /* Initializes a new instance of the PeerNetwork class. */
PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) :
Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, analog, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup)
{ {
assert(!address.empty()); assert(!address.empty());
assert(port > 0U); assert(port > 0U);
@ -38,7 +38,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc
/* Writes P25 LDU1 frame data to the network. */ /* Writes P25 LDU1 frame data to the network. */
bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
P25DEF::FrameType::E frameType, uint8_t controlByte)
{ {
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false; return false;
@ -50,7 +51,7 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS
} }
uint32_t messageLength = 0U; uint32_t messageLength = 0U;
UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType); UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType, controlByte);
if (message == nullptr) { if (message == nullptr) {
return false; return false;
} }
@ -60,7 +61,8 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS
/* Writes P25 LDU2 frame data to the network. */ /* Writes P25 LDU2 frame data to the network. */
bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
uint8_t controlByte)
{ {
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false; return false;
@ -72,7 +74,7 @@ bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS
} }
uint32_t messageLength = 0U; uint32_t messageLength = 0U;
UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data); UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data, controlByte);
if (message == nullptr) { if (message == nullptr) {
return false; return false;
} }
@ -211,7 +213,7 @@ bool PeerNetwork::writeConfig()
/* Creates an P25 LDU1 frame message. */ /* Creates an P25 LDU1 frame message. */
UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data, p25::defines::FrameType::E frameType) const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte)
{ {
using namespace p25::defines; using namespace p25::defines;
using namespace p25::dfsi::defines; using namespace p25::dfsi::defines;
@ -223,7 +225,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l
::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header // construct P25 message header
createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte);
// pack DFSI data // pack DFSI data
uint32_t count = MSG_HDR_SIZE; uint32_t count = MSG_HDR_SIZE;
@ -286,7 +288,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l
/* Creates an P25 LDU2 frame message. */ /* Creates an P25 LDU2 frame message. */
UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data) const uint8_t* data, uint8_t controlByte)
{ {
using namespace p25::defines; using namespace p25::defines;
using namespace p25::dfsi::defines; using namespace p25::dfsi::defines;
@ -298,7 +300,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l
::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header // construct P25 message header
createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte);
// pack DFSI data // pack DFSI data
uint32_t count = MSG_HDR_SIZE; uint32_t count = MSG_HDR_SIZE;

@ -48,6 +48,7 @@ namespace network
* @param dmr Flag indicating whether DMR is enabled. * @param dmr Flag indicating whether DMR is enabled.
* @param p25 Flag indicating whether P25 is enabled. * @param p25 Flag indicating whether P25 is enabled.
* @param nxdn Flag indicating whether NXDN is enabled. * @param nxdn Flag indicating whether NXDN is enabled.
* @param analog Flag indicating whether analog is enabled.
* @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic.
* @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic.
* @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network.
@ -55,7 +56,7 @@ namespace network
* @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network.
*/ */
PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup);
/** /**
* @brief Writes P25 LDU1 frame data to the network. * @brief Writes P25 LDU1 frame data to the network.
@ -63,18 +64,21 @@ namespace network
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] data Buffer containing P25 LDU1 data to send.
* @param[in] frameType DVM P25 frame type. * @param[in] frameType DVM P25 frame type.
* @param[in] controlByte DVM Network Control Byte.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
p25::defines::FrameType::E frameType) override; P25DEF::FrameType::E frameType, uint8_t controlByte = 0U) override;
/** /**
* @brief Writes P25 LDU2 frame data to the network. * @brief Writes P25 LDU2 frame data to the network.
* @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU2 data to send. * @param[in] data Buffer containing P25 LDU2 data to send.
* @param[in] controlByte DVM Network Control Byte.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) override; bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
uint8_t controlByte = 0U) override;
/** /**
* @brief Helper to send a DMR terminator with LC message. * @brief Helper to send a DMR terminator with LC message.
@ -104,10 +108,11 @@ namespace network
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] data Buffer containing P25 LDU1 data to send.
* @param[in] frameType DVM P25 frame type. * @param[in] frameType DVM P25 frame type.
* @param[in] controlByte DVM Network Control Byte.
* @returns UInt8Array Buffer containing the built network message. * @returns UInt8Array Buffer containing the built network message.
*/ */
UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data, p25::defines::FrameType::E frameType); const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte);
/** /**
* @brief Creates an P25 LDU2 frame message. * @brief Creates an P25 LDU2 frame message.
* *
@ -118,10 +123,11 @@ namespace network
* @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU2 data to send. * @param[in] data Buffer containing P25 LDU2 data to send.
* @param[in] controlByte DVM Network Control Byte.
* @returns UInt8Array Buffer containing the built network message. * @returns UInt8Array Buffer containing the built network message.
*/ */
UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data); const uint8_t* data, uint8_t controlByte);
}; };
} // namespace network } // namespace network

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Bridge
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#define IDS_APP_TITLE 102
#define IDI_APP_ICON 103

Binary file not shown.

@ -4,7 +4,7 @@
# * GPLv2 Open Source. Use is subject to license terms. # * GPLv2 Open Source. Use is subject to license terms.
# * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# * # *
# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL # * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL
# * # *
# */ # */
file(GLOB common_SRC file(GLOB common_SRC
@ -39,6 +39,10 @@ file(GLOB common_SRC
"src/common/nxdn/lc/*.cpp" "src/common/nxdn/lc/*.cpp"
"src/common/nxdn/lc/rcch/*.cpp" "src/common/nxdn/lc/rcch/*.cpp"
# Analog Module
"src/common/analog/*.cpp"
"src/common/analog/data/*.cpp"
# Core # Core
"src/common/edac/*.cpp" "src/common/edac/*.cpp"
"src/common/lookups/*.cpp" "src/common/lookups/*.cpp"
@ -87,6 +91,10 @@ file(GLOB common_INCLUDE
"src/common/nxdn/lc/*.h" "src/common/nxdn/lc/*.h"
"src/common/nxdn/lc/rcch/*.h" "src/common/nxdn/lc/rcch/*.h"
# Analog Module
"src/common/analog/*.h"
"src/common/analog/data/*.h"
# Core # Core
"src/common/concurrent/*.h" "src/common/concurrent/*.h"
"src/common/edac/*.h" "src/common/edac/*.h"

@ -117,8 +117,8 @@ typedef unsigned long long ulong64_t;
#define __EXE_NAME__ "" #define __EXE_NAME__ ""
#define VERSION_MAJOR "04" #define VERSION_MAJOR "04"
#define VERSION_MINOR "31" #define VERSION_MINOR "32"
#define VERSION_REV "H" #define VERSION_REV "J"
#define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR
#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" #define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")"

@ -54,6 +54,7 @@ uint32_t g_logDisplayLevel = 2U;
bool g_disableTimeDisplay = false; bool g_disableTimeDisplay = false;
bool g_useSyslog = false; bool g_useSyslog = false;
bool g_disableNetworkLog = false;
static struct tm m_tm; static struct tm m_tm;
@ -314,7 +315,7 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo,
m_outStream << buffer << std::endl; m_outStream << buffer << std::endl;
} }
if (m_network != nullptr) { if (m_network != nullptr && !g_disableNetworkLog) {
// don't transfer debug data... // don't transfer debug data...
if (level > 1U) { if (level > 1U) {
m_network->writeDiagLog(buffer); m_network->writeDiagLog(buffer);

@ -139,6 +139,10 @@ extern bool g_disableTimeDisplay;
* @brief (Global) Flag indicating whether or not logging goes to the syslog. * @brief (Global) Flag indicating whether or not logging goes to the syslog.
*/ */
extern bool g_useSyslog; extern bool g_useSyslog;
/**
* @brief (Global) Flag indicating whether or not network logging is disabled.
*/
extern bool g_disableNetworkLog;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Global Functions // Global Functions

@ -22,7 +22,7 @@ using namespace crypto;
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Initializes a new instance of the AES class. */ /* Initializes a new instance of the RC4 class. */
RC4::RC4() = default; RC4::RC4() = default;

@ -41,6 +41,12 @@ typedef std::unique_ptr<uint8_t[]> UInt8Array;
*/ */
typedef std::unique_ptr<char[]> CharArray; typedef std::unique_ptr<char[]> CharArray;
/**
* @brief Unique char array.
* @ingroup common
*/
typedef std::unique_ptr<short[]> ShortArray;
/** @} */ /** @} */
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -66,7 +72,7 @@ typedef std::unique_ptr<char[]> CharArray;
/** /**
* @brief Declares a unique char array/buffer. * @brief Declares a unique char array/buffer.
* This macro creates a unique pointer to a uint8_t array of the specified length and initializes it to zero. * This macro creates a unique pointer to a char array of the specified length and initializes it to zero.
* The resulting pointer is named after the parameter name passed to the macro. * The resulting pointer is named after the parameter name passed to the macro.
* @ingroup common * @ingroup common
* @param name Name of array/buffer. * @param name Name of array/buffer.
@ -77,6 +83,19 @@ typedef std::unique_ptr<char[]> CharArray;
char* name = __##name##__CharArray.get(); \ char* name = __##name##__CharArray.get(); \
::memset(name, 0, len); ::memset(name, 0, len);
/**
* @brief Declares a unique short array/buffer.
* This macro creates a unique pointer to a short array of the specified length and initializes it to zero.
* The resulting pointer is named after the parameter name passed to the macro.
* @ingroup common
* @param name Name of array/buffer.
* @param len Length of array/buffer.
*/
#define DECLARE_SHORT_ARRAY(name, len) \
ShortArray __##name##__ShortArray = std::make_unique<short[]>(len); \
short* name = __##name##__ShortArray.get(); \
::memset(name, 0, len);
/** @} */ /** @} */
#endif // __VARIABLE_LENGTH_ARRAY_H__ #endif // __VARIABLE_LENGTH_ARRAY_H__

@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "analog/AnalogAudio.h"
#include "Log.h"
#include "Utils.h"
using namespace analog;
#include <cassert>
#include <cstring>
#include <string>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define SIGN_BIT (0x80) // sign bit for a A-law byte
#define QUANT_MASK (0xf) // quantization field mask
#define NSEGS (8) // number of A-law segments
#define SEG_SHIFT (4) // left shift for segment number
#define SEG_MASK (0x70) // segment field mask
static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF };
static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF };
#define BIAS (0x84) // bias for linear code
#define CLIP 8159
#define SHORT_CLIP_SMPL_MIN -32767
#define SHORT_CLIP_SMPL_MAX 32767
// ---------------------------------------------------------------------------
// Public Static Class Members
// ---------------------------------------------------------------------------
/* Helper to convert PCM into G.711 aLaw. */
uint8_t AnalogAudio::encodeALaw(short pcm)
{
short mask;
uint8_t aval;
pcm = pcm >> 3;
if (pcm >= 0) {
mask = 0xD5U; // sign (7th) bit = 1
} else {
mask = 0x55U; // sign bit = 0
pcm = -pcm - 1;
}
// convert the scaled magnitude to segment number
short seg = search(pcm, seg_aend, 8);
/*
** combine the sign, segment, quantization bits
*/
if (seg >= 8) // out of range, return maximum value
return (uint8_t)(0x7F ^ mask);
else {
aval = (uint8_t) seg << SEG_SHIFT;
if (seg < 2)
aval |= (pcm >> 1) & QUANT_MASK;
else
aval |= (pcm >> seg) & QUANT_MASK;
return (aval ^ mask);
}
}
/* Helper to convert G.711 aLaw into PCM. */
short AnalogAudio::decodeALaw(uint8_t alaw)
{
alaw ^= 0x55U;
short t = (alaw & QUANT_MASK) << 4;
short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT;
switch (seg) {
case 0:
t += 8;
break;
case 1:
t += 0x108U;
break;
default:
t += 0x108U;
t <<= seg - 1;
}
return ((alaw & SIGN_BIT) ? t : -t);
}
/* Helper to convert PCM into G.711 MuLaw. */
uint8_t AnalogAudio::encodeMuLaw(short pcm)
{
short mask;
// get the sign and the magnitude of the value
pcm = pcm >> 2;
if (pcm < 0) {
pcm = -pcm;
mask = 0x7FU;
} else {
mask = 0xFFU;
}
// clip the magnitude
if (pcm > CLIP)
pcm = CLIP;
pcm += (BIAS >> 2);
// convert the scaled magnitude to segment number
short seg = search(pcm, seg_uend, 8);
/*
** combine the sign, segment, quantization bits;
** and complement the code word
*/
if (seg >= 8) // out of range, return maximum value.
return (uint8_t)(0x7F ^ mask);
else {
uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF);
return (ulaw ^ mask);
}
}
/* Helper to convert G.711 MuLaw into PCM. */
short AnalogAudio::decodeMuLaw(uint8_t ulaw)
{
// complement to obtain normal u-law value
ulaw = ~ulaw;
/*
** extract and bias the quantization bits; then
** shift up by the segment number and subtract out the bias
*/
short t = ((ulaw & QUANT_MASK) << 3) + BIAS;
t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT;
return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
}
/* Helper to apply linear gain to PCM samples. */
void AnalogAudio::gain(short* pcm, int length, float gain)
{
assert(pcm != nullptr);
assert(length > 0);
if (gain == 1.0f)
return;
for (int i = 0; i < length; i++) {
int sample = static_cast<int>(pcm[i] * gain);
if (sample > SHORT_CLIP_SMPL_MAX)
sample = SHORT_CLIP_SMPL_MAX;
else if (sample < SHORT_CLIP_SMPL_MIN)
sample = SHORT_CLIP_SMPL_MIN;
pcm[i] = static_cast<short>(sample);
}
}
// ---------------------------------------------------------------------------
// Private Static Class Members
// ---------------------------------------------------------------------------
/* Searches for the segment in the given array. */
short AnalogAudio::search(short val, short* table, short size)
{
for (short i = 0; i < size; i++) {
if (val <= *table++)
return (i);
}
return (size);
}

@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file AnalogAudio.h
* @ingroup analog
* @file AnalogAudio.cpp
* @ingroup analog
*/
#if !defined(__ANALOG_AUDIO_H__)
#define __ANALOG_AUDIO_H__
#include "common/Defines.h"
#include "common/analog/AnalogDefines.h"
namespace analog
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements aLaw and uLaw audio codecs, along with helper routines for analog audio.
* @ingroup analog
*/
class HOST_SW_API AnalogAudio {
public:
/**
* @brief Helper to convert PCM into G.711 aLaw.
* @param pcm PCM value.
* @return uint8_t aLaw value.
*/
static uint8_t encodeALaw(short pcm);
/**
* @brief Helper to convert G.711 aLaw into PCM.
* @param alaw aLaw value.
* @return short PCM value.
*/
static short decodeALaw(uint8_t alaw);
/**
* @brief Helper to convert PCM into G.711 MuLaw.
* @param pcm PCM value.
* @return uint8_t MuLaw value.
*/
static uint8_t encodeMuLaw(short pcm);
/**
* @brief Helper to convert G.711 MuLaw into PCM.
* @param ulaw MuLaw value.
* @return short PCM value.
*/
static short decodeMuLaw(uint8_t ulaw);
/**
* @brief Helper to apply linear gain to PCM samples.
* @param pcm Buffer containing PCM samples.
* @param length Length of the PCM buffer in samples.
* @param gain Gain factor to apply to the PCM samples (1.0f = no change).
*/
static void gain(short *pcm, int length, float gain);
/**
* @brief (ms) to sample count conversion.
* @param sampleRate Sample rate.
* @param channels Number of audio channels.
* @param ms Number of milliseconds.
* @returns int Number of samples.
*/
static int toSamples(uint32_t sampleRate, uint8_t channels, int ms) { return (int)(((long)ms) * sampleRate * channels / 1000); }
/**
* @brief Sample count to (ms) conversion.
* @param sampleRate Sample rate.
* @param channels Number of audio channels.
* @param samples Number of samples.
* @returns int Number of milliseconds.
*/
static int toMS(uint32_t sampleRate, uint8_t channels, int samples) { return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); }
private:
/**
* @brief Searches for the segment in the given array.
* @param val PCM value to search for.
* @param table Array of segment values.
* @param size Size of the segment array.
* @return short
*/
static short search(short val, short* table, short size);
};
} // namespace analog
#endif // __ANALOG_AUDIO_H__

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @defgroup analog Digital Mobile Radio
* @brief Defines and implements aLaw and uLaw audio codecs, along with helper routines for analog audio.
* @ingroup common
*
* @file AnalogDefines.h
* @ingroup analog
*/
#if !defined(__ANALOG_DEFINES_H__)
#define __ANALOG_DEFINES_H__
#include "common/Defines.h"
// Shorthand macro to analog::defines -- keeps source code that doesn't use "using" concise
#if !defined(ANODEF)
#define ANODEF analog::defines
#endif // DMRDEF
namespace analog
{
namespace defines
{
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/**
* @addtogroup analog
* @{
*/
const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //! Sample size for 20ms of 16-bit audio at 8kHz.
const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //! Sample size for 20ms of 16-bit audio at 8kHz in bytes.
/** @} */
/** @brief Audio Frame Type(s) */
namespace AudioFrameType {
/** @brief Audio Frame Type(s) */
enum E : uint8_t {
VOICE_START = 0x00U, //! Voice Start Frame
VOICE = 0x01U, //! Voice Continuation Frame
TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator
};
}
#define ANO_TERMINATOR "Analog, TERMINATOR (Terminator)"
#define ANO_VOICE "Analog, VOICE (Voice Data)"
} // namespace defines
} // namespace analog
#endif // __ANALOG_DEFINES_H__

@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "analog/AnalogAudio.h"
#include "analog/data/NetData.h"
using namespace analog;
using namespace analog::defines;
using namespace analog::data;
#include <cstring>
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the NetData class. */
NetData::NetData(const NetData& data) :
m_srcId(data.m_srcId),
m_dstId(data.m_dstId),
m_control(data.m_control),
m_seqNo(data.m_seqNo),
m_group(true),
m_frameType(data.m_frameType),
m_audio(nullptr)
{
m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES];
::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES);
}
/* Initializes a new instance of the NetData class. */
NetData::NetData() :
m_srcId(0U),
m_dstId(0U),
m_control(0U),
m_seqNo(0U),
m_group(true),
m_frameType(AudioFrameType::TERMINATOR),
m_audio(nullptr)
{
m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES];
}
/* Finalizes a instance of the NetData class. */
NetData::~NetData()
{
delete[] m_audio;
}
/* Equals operator. */
NetData& NetData::operator=(const NetData& data)
{
if (this != &data) {
::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES);
m_srcId = data.m_srcId;
m_dstId = data.m_dstId;
m_control = data.m_control;
m_frameType = data.m_frameType;
m_seqNo = data.m_seqNo;
m_group = data.m_group;
}
return *this;
}
/* Sets audio data. */
void NetData::setAudio(const uint8_t* buffer)
{
assert(buffer != nullptr);
::memcpy(m_audio, buffer, AUDIO_SAMPLES_LENGTH_BYTES);
}
/* Gets audio data. */
uint32_t NetData::getAudio(uint8_t* buffer) const
{
assert(buffer != nullptr);
::memcpy(buffer, m_audio, AUDIO_SAMPLES_LENGTH_BYTES);
return AUDIO_SAMPLES_LENGTH_BYTES;
}

@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file NetData.h
* @ingroup analog
* @file NetData.cpp
* @ingroup analog
*/
#if !defined(__ANALOG_DATA__NET_DATA_H__)
#define __ANALOG_DATA__NET_DATA_H__
#include "common/Defines.h"
#include "common/analog/AnalogDefines.h"
namespace analog
{
namespace data
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents network analog data. When setting audio data, it is expected to be 20ms of 16-bit
* audio at 8kHz, or 320 bytes in length.
* @ingroup analog
*/
class HOST_SW_API NetData {
public:
/**
* @brief Initializes a new instance of the NetData class.
* @param data Instance of NetData class to copy from.
*/
NetData(const NetData& data);
/**
* @brief Initializes a new instance of the NetData class.
*/
NetData();
/**
* @brief Finalizes a instance of the NetData class.
*/
~NetData();
/**
* @brief Equals operator.
* @param data Instance of NetData class to copy from.
*/
NetData& operator=(const NetData& data);
/**
* @brief Sets audio data. This data buffer should be MuLaw encoded.
* @param[in] buffer Audio data buffer.
*/
void setAudio(const uint8_t* buffer);
/**
* @brief Gets audio data.
* @param[out] buffer Audio data buffer.
*/
uint32_t getAudio(uint8_t* buffer) const;
public:
/**
* @brief Source ID.
*/
DECLARE_PROPERTY(uint32_t, srcId, SrcId);
/**
* @brief Destination ID.
*/
DECLARE_PROPERTY(uint32_t, dstId, DstId);
/**
* @brief
*/
DECLARE_PROPERTY(uint8_t, control, Control);
/**
* @brief Sequence number.
*/
DECLARE_PROPERTY(uint8_t, seqNo, SeqNo);
/**
* @brief Flag indicatin if this group audio or individual audio.
*/
DECLARE_PROPERTY(bool, group, Group);
/**
* @brief Audio frame type.
*/
DECLARE_PROPERTY(defines::AudioFrameType::E, frameType, FrameType);
private:
uint8_t* m_audio;
};
} // namespace data
} // namespace analog
#endif // __ANALOG_DATA__NET_DATA_H__

@ -200,11 +200,14 @@ namespace dmr
}; };
}; };
/** @name Feature IDs */ /** @name Feature Set IDs */
/** @brief ETSI Standard Feature Set */ /** @brief ETSI Standard Feature Set */
const uint8_t FID_ETSI = 0x00U; const uint8_t FID_ETSI = 0x00U;
/** @brief Motorola */ /** @brief Motorola */
const uint8_t FID_DMRA = 0x10U; const uint8_t FID_MOT = 0x10U;
/** @brief Kenwood */
const uint8_t FID_KENWOOD = 0x20U;
/** @brief DVM; Omaha Communication Systems, LLC ($9C) */ /** @brief DVM; Omaha Communication Systems, LLC ($9C) */
const uint8_t FID_DVM_OCS = 0x9CU; const uint8_t FID_DVM_OCS = 0x9CU;
/** @} */ /** @} */
@ -259,9 +262,9 @@ namespace dmr
}; };
} }
/** @brief FID_DMRA Extended Functions. */ /** @brief FID_MOT Extended Functions. */
namespace ExtendedFunctions { namespace ExtendedFunctions {
/** @brief FID_DMRA Extended Functions. */ /** @brief FID_MOT Extended Functions. */
enum : uint16_t { enum : uint16_t {
CHECK = 0x0000U, //! Radio Check CHECK = 0x0000U, //! Radio Check
UNINHIBIT = 0x007EU, //! Radio Uninhibit UNINHIBIT = 0x007EU, //! Radio Uninhibit

@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "dmr/DMRDefines.h"
#include "dmr/DMRUtils.h"
#include "Utils.h"
using namespace dmr;
using namespace dmr::defines;
#include <cassert>
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
/* Helper to convert a denial reason code to a string. */
std::string DMRUtils::rsnToString(uint8_t reason)
{
switch (reason) {
case ReasonCode::TS_ACK_RSN_MSG:
return std::string("TS_ACK_RSN_MSG (Message Accepted)");
case ReasonCode::TS_ACK_RSN_REG:
return std::string("TS_ACK_RSN_REG (Registration Accepted)");
case ReasonCode::TS_ACK_RSN_AUTH_RESP:
return std::string("TS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)");
case ReasonCode::TS_ACK_RSN_REG_SUB_ATTACH:
return std::string("TS_ACK_RSN_REG_SUB_ATTACH (Registration Response with subscription)");
case ReasonCode::MS_ACK_RSN_MSG:
return std::string("MS_ACK_RSN_MSG (Message Accepted)");
case ReasonCode::MS_ACK_RSN_AUTH_RESP:
return std::string("MS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)");
case ReasonCode::TS_DENY_RSN_SYS_UNSUPPORTED_SVC:
return std::string("TS_DENY_RSN_SYS_UNSUPPORTED_SVC (System Unsupported Service)");
case ReasonCode::TS_DENY_RSN_PERM_USER_REFUSED:
return std::string("TS_DENY_RSN_PERM_USER_REFUSED (User Permenantly Refused)");
case ReasonCode::TS_DENY_RSN_TEMP_USER_REFUSED:
return std::string("TS_DENY_RSN_TEMP_USER_REFUSED (User Temporarily Refused)");
case ReasonCode::TS_DENY_RSN_TRSN_SYS_REFUSED:
return std::string("TS_DENY_RSN_TRSN_SYS_REFUSED (System Refused)");
case ReasonCode::TS_DENY_RSN_TGT_NOT_REG:
return std::string("TS_DENY_RSN_TGT_NOT_REG (Target Not Registered)");
case ReasonCode::TS_DENY_RSN_TGT_UNAVAILABLE:
return std::string("TS_DENY_RSN_TGT_UNAVAILABLE (Target Unavailable)");
case ReasonCode::TS_DENY_RSN_SYS_BUSY:
return std::string("TS_DENY_RSN_SYS_BUSY (System Busy)");
case ReasonCode::TS_DENY_RSN_SYS_NOT_READY:
return std::string("TS_DENY_RSN_SYS_NOT_READY (System Not Ready)");
case ReasonCode::TS_DENY_RSN_CALL_CNCL_REFUSED:
return std::string("TS_DENY_RSN_CALL_CNCL_REFUSED (Call Cancel Refused)");
case ReasonCode::TS_DENY_RSN_REG_REFUSED:
return std::string("TS_DENY_RSN_REG_REFUSED (Registration Refused)");
case ReasonCode::TS_DENY_RSN_REG_DENIED:
return std::string("TS_DENY_RSN_REG_DENIED (Registration Denied)");
case ReasonCode::TS_DENY_RSN_MS_NOT_REG:
return std::string("TS_DENY_RSN_MS_NOT_REG (MS Not Registered)");
case ReasonCode::TS_DENY_RSN_TGT_BUSY:
return std::string("TS_DENY_RSN_TGT_BUSY (Target Busy)");
case ReasonCode::TS_DENY_RSN_TGT_GROUP_NOT_VALID:
return std::string("TS_DENY_RSN_TGT_GROUP_NOT_VALID (Group Not Valid)");
case ReasonCode::TS_QUEUED_RSN_NO_RESOURCE:
return std::string("TS_QUEUED_RSN_NO_RESOURCE (No Resources Available)");
case ReasonCode::TS_QUEUED_RSN_SYS_BUSY:
return std::string("TS_QUEUED_RSN_SYS_BUSY (System Busy)");
case ReasonCode::TS_WAIT_RSN:
return std::string("TS_WAIT_RSN (Wait)");
case ReasonCode::MS_DENY_RSN_UNSUPPORTED_SVC:
return std::string("MS_DENY_RSN_UNSUPPORTED_SVC (Service Unsupported)");
default:
return std::string();
}
}

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -136,6 +136,13 @@ namespace dmr
return id; return id;
} }
/**
* @brief Helper to convert a reason code to a string.
* @param reason Reason code.
* @returns std::string Reason code string.
*/
static std::string rsnToString(uint8_t reason);
}; };
} // namespace dmr } // namespace dmr

@ -272,7 +272,7 @@ bool CSBK::decode(const uint8_t* data, uint8_t* payload)
} }
if (m_verbose) { if (m_verbose) {
Utils::dump(2U, "Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); Utils::dump(2U, "CSBK::decode(), Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES);
} }
m_raw = new uint8_t[DMR_CSBK_LENGTH_BYTES]; m_raw = new uint8_t[DMR_CSBK_LENGTH_BYTES];
@ -340,7 +340,7 @@ void CSBK::encode(uint8_t* data, const uint8_t* payload)
} }
if (m_verbose) { if (m_verbose) {
Utils::dump(2U, "Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); Utils::dump(2U, "CSBK::encode(), Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES);
} }
// encode BPTC (196,96) FEC // encode BPTC (196,96) FEC

@ -93,10 +93,10 @@ std::unique_ptr<CSBK> CSBKFactory::createCSBK(const uint8_t* data, DataType::E d
return decode(new CSBK_UU_ANS_RSP(), data); return decode(new CSBK_UU_ANS_RSP(), data);
case CSBKO::PRECCSBK: case CSBKO::PRECCSBK:
return decode(new CSBK_PRECCSBK(), data); return decode(new CSBK_PRECCSBK(), data);
case CSBKO::RAND: // CSBKO::CALL_ALRT when FID == FID_DMRA case CSBKO::RAND: // CSBKO::CALL_ALRT when FID == FID_MOT
switch (FID) switch (FID)
{ {
case FID_DMRA: case FID_MOT:
return decode(new CSBK_CALL_ALRT(), data); return decode(new CSBK_CALL_ALRT(), data);
case FID_ETSI: case FID_ETSI:
default: default:

@ -26,7 +26,7 @@ using namespace dmr::lc::csbk;
CSBK_CALL_ALRT::CSBK_CALL_ALRT() : CSBK() CSBK_CALL_ALRT::CSBK_CALL_ALRT() : CSBK()
{ {
m_CSBKO = CSBKO::RAND; m_CSBKO = CSBKO::RAND;
m_FID = FID_DMRA; m_FID = FID_MOT;
} }
/* Decode a control signalling block. */ /* Decode a control signalling block. */

@ -27,7 +27,7 @@ CSBK_EXT_FNCT::CSBK_EXT_FNCT() : CSBK(),
m_extendedFunction(ExtendedFunctions::CHECK) m_extendedFunction(ExtendedFunctions::CHECK)
{ {
m_CSBKO = CSBKO::EXT_FNCT; m_CSBKO = CSBKO::EXT_FNCT;
m_FID = FID_DMRA; m_FID = FID_MOT;
} }
/* Decode a control signalling block. */ /* Decode a control signalling block. */

@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "lookups/AdjSiteMapLookup.h"
#include "Log.h"
#include "Timer.h"
#include "Utils.h"
using namespace lookups;
#include <string>
#include <vector>
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::mutex AdjSiteMapLookup::m_mutex;
bool AdjSiteMapLookup::m_locked = false;
// ---------------------------------------------------------------------------
// Macros
// ---------------------------------------------------------------------------
// Lock the table.
#define __LOCK_TABLE() \
std::lock_guard<std::mutex> lock(m_mutex); \
m_locked = true;
// Unlock the table.
#define __UNLOCK_TABLE() m_locked = false;
// Spinlock wait for table to be read unlocked.
#define __SPINLOCK() \
if (m_locked) { \
while (m_locked) \
Thread::sleep(2U); \
}
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the AdjSiteMapLookup class. */
AdjSiteMapLookup::AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime) : Thread(),
m_rulesFile(filename),
m_reloadTime(reloadTime),
m_rules(),
m_stop(false),
m_adjPeerMap()
{
/* stub */
}
/* Finalizes a instance of the AdjSiteMapLookup class. */
AdjSiteMapLookup::~AdjSiteMapLookup() = default;
/* Thread entry point. This function is provided to run the thread for the lookup table. */
void AdjSiteMapLookup::entry()
{
if (m_reloadTime == 0U) {
return;
}
Timer timer(1U, 60U * m_reloadTime);
timer.start();
while (!m_stop) {
sleep(1000U);
timer.clock();
if (timer.hasExpired()) {
load();
timer.start();
}
}
}
/* Stops and unloads this lookup table. */
void AdjSiteMapLookup::stop(bool noDestroy)
{
if (m_reloadTime == 0U) {
if (!noDestroy)
delete this;
return;
}
m_stop = true;
wait();
}
/* Reads the lookup table from the specified lookup table file. */
bool AdjSiteMapLookup::read()
{
bool ret = load();
if (m_reloadTime > 0U)
run();
setName("host:adj-site-map");
return ret;
}
/* Clears all entries from the lookup table. */
void AdjSiteMapLookup::clear()
{
__LOCK_TABLE();
m_adjPeerMap.clear();
__UNLOCK_TABLE();
}
/* Adds a new entry to the lookup table by the specified unique ID. */
void AdjSiteMapLookup::addEntry(AdjPeerMapEntry entry)
{
uint32_t id = entry.peerId();
__LOCK_TABLE();
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
[&](AdjPeerMapEntry x)
{
return x.peerId() == id;
});
if (it != m_adjPeerMap.end()) {
m_adjPeerMap[it - m_adjPeerMap.begin()] = entry;
}
else {
m_adjPeerMap.push_back(entry);
}
__UNLOCK_TABLE();
}
/* Erases an existing entry from the lookup table by the specified unique ID. */
void AdjSiteMapLookup::eraseEntry(uint32_t id)
{
__LOCK_TABLE();
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), [&](AdjPeerMapEntry x) { return x.peerId() == id; });
if (it != m_adjPeerMap.end()) {
m_adjPeerMap.erase(it);
}
__UNLOCK_TABLE();
}
/* Finds a table entry in this lookup table. */
AdjPeerMapEntry AdjSiteMapLookup::find(uint32_t id)
{
AdjPeerMapEntry entry;
__SPINLOCK();
std::lock_guard<std::mutex> lock(m_mutex);
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
[&](AdjPeerMapEntry x)
{
return x.peerId() == id;
});
if (it != m_adjPeerMap.end()) {
entry = *it;
} else {
entry = AdjPeerMapEntry();
}
return entry;
}
/* Saves loaded talkgroup rules. */
bool AdjSiteMapLookup::commit()
{
return save();
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Loads the table from the passed lookup table file. */
bool AdjSiteMapLookup::load()
{
if (m_rulesFile.length() <= 0) {
return false;
}
try {
bool ret = yaml::Parse(m_rules, m_rulesFile.c_str());
if (!ret) {
LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s - error parsing YML", m_rulesFile.c_str());
return false;
}
}
catch (yaml::OperationException const& e) {
LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message());
return false;
}
// clear table
clear();
__LOCK_TABLE();
yaml::Node& peerList = m_rules["peers"];
if (peerList.size() == 0U) {
::LogError(LOG_HOST, "No adj site map peer list defined!");
m_locked = false;
return false;
}
for (size_t i = 0; i < peerList.size(); i++) {
AdjPeerMapEntry entry = AdjPeerMapEntry(peerList[i]);
m_adjPeerMap.push_back(entry);
}
__UNLOCK_TABLE();
size_t size = m_adjPeerMap.size();
if (size == 0U) {
return false;
}
LogInfoEx(LOG_HOST, "Loaded %lu entries into adjacent site map table", size);
return true;
}
/* Saves the table to the passed lookup table file. */
bool AdjSiteMapLookup::save()
{
// Make sure file is valid
if (m_rulesFile.length() <= 0) {
return false;
}
std::lock_guard<std::mutex> lock(m_mutex);
// New list for our new group voice rules
yaml::Node peerList;
yaml::Node newRules;
for (auto entry : m_adjPeerMap) {
yaml::Node& gv = peerList.push_back();
entry.getYaml(gv);
}
// Set the new rules
newRules["peers"] = peerList;
// Make sure we actually did stuff right
if (newRules["peers"].size() != m_adjPeerMap.size()) {
LogError(LOG_HOST, "Generated YAML node for group lists did not match loaded map size! (%u != %u)", newRules["peers"].size(), m_adjPeerMap.size());
return false;
}
try {
LogMessage(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str());
yaml::Serialize(newRules, m_rulesFile.c_str());
LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str());
}
catch (yaml::OperationException const& e) {
LogError(LOG_HOST, "Cannot save the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message());
return false;
}
return true;
}

@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @defgroup lookups_asm Adjacent Site Map Lookups
* @brief Implementation for adjacent site map lookup tables.
* @ingroup lookups
*
* @file AdjSiteMapLookup.h
* @ingroup lookups_asm
* @file AdjSiteMapLookup.cpp
* @ingroup lookups_asm
*/
#if !defined(__ADJ_SITE_MAP_LOOKUP_H__)
#define __ADJ_SITE_MAP_LOOKUP_H__
#include "common/Defines.h"
#include "common/lookups/LookupTable.h"
#include "common/yaml/Yaml.h"
#include "common/Utils.h"
#include <string>
#include <mutex>
#include <vector>
namespace lookups
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents an adjacent peer map entry.
* @ingroup lookups_asm
*/
class HOST_SW_API AdjPeerMapEntry {
public:
/**
* @brief Initializes a new instance of the AdjPeerMapEntry class.
*/
AdjPeerMapEntry() :
m_active(false),
m_peerId(0U),
m_neighbors()
{
/* stub */
}
/**
* @brief Initializes a new instance of the AdjPeerMapEntry class.
* @param node YAML node for this configuration block.
*/
AdjPeerMapEntry(yaml::Node& node) :
AdjPeerMapEntry()
{
m_active = node["active"].as<bool>(false);
m_peerId = node["peer_id"].as<uint32_t>(0U);
yaml::Node& neighborList = node["neighbors"];
if (neighborList.size() > 0U) {
for (size_t i = 0; i < neighborList.size(); i++) {
uint32_t peerId = neighborList[i].as<uint32_t>(0U);
m_neighbors.push_back(peerId);
}
}
}
/**
* @brief Equals operator. Copies this AdjPeerMapEntry to another AdjPeerMapEntry.
* @param data Instance of AdjPeerMapEntry to copy.
*/
virtual AdjPeerMapEntry& operator= (const AdjPeerMapEntry& data)
{
if (this != &data) {
m_active = data.m_active;
m_peerId = data.m_peerId;
m_neighbors = data.m_neighbors;
}
return *this;
}
/**
* @brief Gets the count of neighbors.
* @returns uint8_t Total count of peer neighbors.
*/
uint8_t neighborSize() const { return m_neighbors.size(); }
/**
* @brief Helper to quickly determine if a entry is valid.
* @returns bool True, if entry is valid, otherwise false.
*/
bool isEmpty() const
{
if (m_neighbors.size() > 0U)
return true;
return false;
}
/**
* @brief Return the YAML structure for this TalkgroupRuleConfig.
* @param[out] node YAML node.
*/
void getYaml(yaml::Node &node)
{
// We have to convert the bools back to strings to pass to the yaml node
node["active"] = __BOOL_STR(m_active);
node["peerId"] = __INT_STR(m_peerId);
// Get the lists
yaml::Node neighborList;
if (m_neighbors.size() > 0U) {
for (auto neighbor : m_neighbors) {
yaml::Node& newNeighbor = neighborList.push_back();
newNeighbor = __INT_STR(neighbor);
}
}
}
public:
/**
* @brief Flag indicating whether the rule is active.
*/
DECLARE_PROPERTY_PLAIN(bool, active);
/**
* @brief Peer ID.
*/
DECLARE_PROPERTY_PLAIN(uint32_t, peerId);
/**
* @brief List of neighbor peers.
*/
DECLARE_PROPERTY_PLAIN(std::vector<uint32_t>, neighbors);
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements a threading lookup table class that contains adjacent site map
* information.
* @ingroup lookups_asm
*/
class HOST_SW_API AdjSiteMapLookup : public Thread {
public:
/**
* @brief Initializes a new instance of the AdjSiteMapLookup class.
* @param filename Full-path to the routing rules file.
* @param reloadTime Interval of time to reload the routing rules.
*/
AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime);
/**
* @brief Finalizes a instance of the AdjSiteMapLookup class.
*/
~AdjSiteMapLookup() override;
/**
* @brief Thread entry point. This function is provided to run the thread
* for the lookup table.
*/
void entry() override;
/**
* @brief Stops and unloads this lookup table.
* @param noDestroy Flag indicating the lookup table should remain resident in memory after stopping.
*/
void stop(bool noDestroy = false);
/**
* @brief Reads the lookup table from the specified lookup table file.
* (NOTE: If the reload time for this lookup table is set to 0, a call to stop will also delete the object.)
* @returns bool True, if lookup table was read, otherwise false.
*/
bool read();
/**
* @brief Reads the lookup table from the specified lookup table file.
* @returns bool True, if lookup table was read, otherwise false.
*/
bool reload() { return load(); }
/**
* @brief Clears all entries from the lookup table.
*/
void clear();
/**
* @brief Adds a new entry to the lookup table.
* @param entry Peer map entry.
*/
void addEntry(AdjPeerMapEntry entry);
/**
* @brief Erases an existing entry from the lookup table by the specified unique ID.
* @param id Unique ID to erase.
*/
void eraseEntry(uint32_t id);
/**
* @brief Finds a table entry in this lookup table.
* @param id Unique identifier for table entry.
* @returns AdjPeerMapEntry Table entry.
*/
virtual AdjPeerMapEntry find(uint32_t id);
/**
* @brief Saves loaded talkgroup rules.
*/
bool commit();
/**
* @brief Returns the filename used to load this lookup table.
* @return std::string Full-path to the lookup table file.
*/
const std::string filename() { return m_rulesFile; };
/**
* @brief Sets the filename used to load this lookup table.
* @param filename Full-path to the routing rules file.
*/
void filename(std::string filename) { m_rulesFile = filename; };
/**
* @brief Helper to set the reload time of this lookup table.
* @param reloadTime Lookup time in seconds.
*/
void setReloadTime(uint32_t reloadTime) { m_reloadTime = reloadTime; }
private:
std::string m_rulesFile;
uint32_t m_reloadTime;
yaml::Node m_rules;
bool m_stop;
static std::mutex m_mutex; //! Mutex used for change locking.
static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used.
/**
* @brief Loads the table from the passed lookup table file.
* @return True, if lookup table was loaded, otherwise false.
*/
bool load();
/**
* @brief Saves the table to the passed lookup table file.
* @return True, if lookup table was saved, otherwise false.
*/
bool save();
public:
/**
* @brief List of adjacent site map entries.
*/
DECLARE_PROPERTY_PLAIN(std::vector<AdjPeerMapEntry>, adjPeerMap);
};
} // namespace lookups
#endif // __ADJ_SITE_MAP_LOOKUP_H__

@ -113,22 +113,13 @@ bool IdenTableLookup::load()
continue; continue;
// tokenize line // tokenize line
std::string next;
std::vector<std::string> parsed; std::vector<std::string> parsed;
std::stringstream ss(line);
std::string field;
char delim = ','; char delim = ',';
for (auto it = line.begin(); it != line.end(); it++) { while (std::getline(ss, field, delim))
if (*it == delim) { parsed.push_back(field);
if (!next.empty()) {
parsed.push_back(next);
next.clear();
}
}
else
next += *it;
}
if (!next.empty())
parsed.push_back(next);
// ensure we have at least 5 fields // ensure we have at least 5 fields
if (parsed.size() < 5) { if (parsed.size() < 5) {

@ -69,17 +69,15 @@ void PeerListLookup::clear()
/* Adds a new entry to the list. */ /* Adds a new entry to the list. */
void PeerListLookup::addEntry(uint32_t id, const std::string& alias, const std::string& password, bool peerLink, bool canRequestKeys) void PeerListLookup::addEntry(uint32_t id, PeerId entry)
{ {
PeerId entry = PeerId(id, alias, password, peerLink, canRequestKeys, false);
__LOCK_TABLE(); __LOCK_TABLE();
try { try {
PeerId _entry = m_table.at(id); PeerId _entry = m_table.at(id);
// if either the alias or the enabled flag doesn't match, update the entry // if either the alias or the enabled flag doesn't match, update the entry
if (_entry.peerId() == id) { if (_entry.peerId() == id) {
_entry = PeerId(id, alias, password, peerLink, canRequestKeys, false); _entry = entry;
m_table[id] = _entry; m_table[id] = _entry;
} }
} catch (...) { } catch (...) {
@ -117,7 +115,7 @@ PeerId PeerListLookup::find(uint32_t id)
try { try {
entry = m_table.at(id); entry = m_table.at(id);
} catch (...) { } catch (...) {
entry = PeerId(0U, "", "", false, false, true); entry = PeerId(0U, "", "", true);
} }
return entry; return entry;
@ -208,22 +206,13 @@ bool PeerListLookup::load()
continue; continue;
// tokenize line // tokenize line
std::string next;
std::vector<std::string> parsed; std::vector<std::string> parsed;
std::stringstream ss(line);
std::string field;
char delim = ','; char delim = ',';
for (char c : line) { while (std::getline(ss, field, delim))
if (c == delim) { parsed.push_back(field);
//if (!next.empty()) {
parsed.push_back(next);
next.clear();
//}
}
else
next += c;
}
if (!next.empty())
parsed.push_back(next);
// parse tokenized line // parse tokenized line
uint32_t id = ::atoi(parsed[0].c_str()); uint32_t id = ::atoi(parsed[0].c_str());
@ -243,20 +232,31 @@ bool PeerListLookup::load()
if (parsed.size() >= 5) if (parsed.size() >= 5)
canRequestKeys = ::atoi(parsed[4].c_str()) == 1; canRequestKeys = ::atoi(parsed[4].c_str()) == 1;
// parse can issue inhibit flag
bool canIssueInhibit = false;
if (parsed.size() >= 6)
canIssueInhibit = ::atoi(parsed[5].c_str()) == 1;
// parse optional password // parse optional password
std::string password = ""; std::string password = "";
if (parsed.size() >= 2) if (parsed.size() >= 2)
password = parsed[1].c_str(); password = parsed[1].c_str();
// load into table // load into table
m_table[id] = PeerId(id, alias, password, peerLink, canRequestKeys, false); PeerId entry = PeerId(id, alias, password, false);
entry.peerLink(peerLink);
entry.canRequestKeys(canRequestKeys);
entry.canIssueInhibit(canIssueInhibit);
m_table[id] = entry;
// log depending on what was loaded // log depending on what was loaded
LogMessage(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s", id, LogMessage(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s", id,
(!alias.empty() ? (" (" + alias + ")").c_str() : ""), (!alias.empty() ? (" (" + alias + ")").c_str() : ""),
(!password.empty() ? "using unique peer password" : "using master password"), (!password.empty() ? "using unique peer password" : "using master password"),
(peerLink) ? ", Peer-Link Enabled" : "", (peerLink) ? ", Peer-Link Enabled" : "",
(canRequestKeys) ? ", Can Request Keys" : ""); (canRequestKeys) ? ", Can Request Keys" : "",
(canIssueInhibit) ? ", Can Issue Inhibit" : "");
} }
} }
@ -334,6 +334,14 @@ bool PeerListLookup::save()
line += "0,"; line += "0,";
} }
// add canIssueInhibit flag
bool canIssueInhibit = entry.second.canIssueInhibit();
if (canIssueInhibit) {
line += "1,";
} else {
line += "0,";
}
line += "\n"; line += "\n";
file << line; file << line;
lines++; lines++;

@ -51,6 +51,7 @@ namespace lookups
m_peerPassword(), m_peerPassword(),
m_peerLink(false), m_peerLink(false),
m_canRequestKeys(false), m_canRequestKeys(false),
m_canIssueInhibit(false),
m_peerDefault(false) m_peerDefault(false)
{ {
/* stub */ /* stub */
@ -61,16 +62,15 @@ namespace lookups
* @param peerAlias Peer alias * @param peerAlias Peer alias
* @param peerPassword Per Peer Password. * @param peerPassword Per Peer Password.
* @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration. * @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration.
* @param peerLink lag indicating if the peer participates in peer link and should be sent configuration.
* @param canRequestKeys Flag indicating if the peer can request encryption keys.
* @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer.
*/ */
PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool canRequestKeys, bool peerDefault) : PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerDefault) :
m_peerId(peerId), m_peerId(peerId),
m_peerAlias(peerAlias), m_peerAlias(peerAlias),
m_peerPassword(peerPassword), m_peerPassword(peerPassword),
m_peerLink(peerLink), m_peerLink(false),
m_canRequestKeys(canRequestKeys), m_canRequestKeys(false),
m_canIssueInhibit(false),
m_peerDefault(peerDefault) m_peerDefault(peerDefault)
{ {
/* stub */ /* stub */
@ -88,6 +88,7 @@ namespace lookups
m_peerPassword = data.m_peerPassword; m_peerPassword = data.m_peerPassword;
m_peerLink = data.m_peerLink; m_peerLink = data.m_peerLink;
m_canRequestKeys = data.m_canRequestKeys; m_canRequestKeys = data.m_canRequestKeys;
m_canIssueInhibit = data.m_canIssueInhibit;
m_peerDefault = data.m_peerDefault; m_peerDefault = data.m_peerDefault;
} }
@ -100,17 +101,13 @@ namespace lookups
* @param peerAlias Peer Alias * @param peerAlias Peer Alias
* @param peerPassword Per Peer Password. * @param peerPassword Per Peer Password.
* @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration. * @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration.
* @param peerLink lag indicating if the peer participates in peer link and should be sent configuration.
* @param canRequestKeys Flag indicating if the peer can request encryption keys.
* @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer.
*/ */
void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool canRequestKeys, bool peerDefault) void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerDefault)
{ {
m_peerId = peerId; m_peerId = peerId;
m_peerAlias = peerAlias; m_peerAlias = peerAlias;
m_peerPassword = peerPassword; m_peerPassword = peerPassword;
m_peerLink = peerLink;
m_canRequestKeys = canRequestKeys;
m_peerDefault = peerDefault; m_peerDefault = peerDefault;
} }
@ -135,6 +132,10 @@ namespace lookups
* @brief Flag indicating if the peer can request encryption keys. * @brief Flag indicating if the peer can request encryption keys.
*/ */
DECLARE_PROPERTY_PLAIN(bool, canRequestKeys); DECLARE_PROPERTY_PLAIN(bool, canRequestKeys);
/**
* @brief Flag indicating if the peer can issue inhibit/uninhibit packets.
*/
DECLARE_PROPERTY_PLAIN(bool, canIssueInhibit);
/** /**
* @brief Flag indicating if the peer is default. * @brief Flag indicating if the peer is default.
*/ */
@ -167,15 +168,13 @@ namespace lookups
/** /**
* @brief Adds a new entry to the list. * @brief Adds a new entry to the list.
* @param peerId Unique peer ID to add. * @param id Unique peer ID to add.
* @param password Per Peer Password. * @param entry Peer ID entry to add.
* @param peerLink Flag indicating this peer will participate in peer link and should be sent configuration.
* @param canRequestKeys Flag indicating if the peer can request encryption keys.
*/ */
void addEntry(uint32_t id, const std::string& alias = "", const std::string& password = "", bool peerLink = false, bool canRequestKeys = false); void addEntry(uint32_t id, PeerId entry);
/** /**
* @brief Removes an existing entry from the list. * @brief Removes an existing entry from the list.
* @param peerId Unique peer ID to remove. * @param id Unique peer ID to remove.
*/ */
void eraseEntry(uint32_t id); void eraseEntry(uint32_t id);
/** /**

@ -192,22 +192,13 @@ bool RadioIdLookup::load()
continue; continue;
// tokenize line // tokenize line
std::string next;
std::vector<std::string> parsed; std::vector<std::string> parsed;
std::stringstream ss(line);
std::string field;
char delim = ','; char delim = ',';
for (char c : line) { while (std::getline(ss, field, delim))
if (c == delim) { parsed.push_back(field);
if (!next.empty()) {
parsed.push_back(next);
next.clear();
}
}
else
next += c;
}
if (!next.empty())
parsed.push_back(next);
// ensure we have at least 2 fields // ensure we have at least 2 fields
if (parsed.size() < 2) { if (parsed.size() < 2) {
@ -232,6 +223,7 @@ bool RadioIdLookup::load()
} }
m_table[id] = RadioId(radioEnabled, false, alias, ipAddress); m_table[id] = RadioId(radioEnabled, false, alias, ipAddress);
//::LogInfoEx(LOG_HOST, "Radio NAME: %s RID: %u ENABLED: %u IPADDR: %s", alias.c_str(), id, radioEnabled, ipAddress.c_str());
} }
} }

@ -420,7 +420,7 @@ bool TalkgroupRulesLookup::save()
LogDebug(LOG_HOST, "Saved TGID config file to %s", m_rulesFile.c_str()); LogDebug(LOG_HOST, "Saved TGID config file to %s", m_rulesFile.c_str());
} }
catch (yaml::OperationException const& e) { catch (yaml::OperationException const& e) {
LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); LogError(LOG_HOST, "Cannot save the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message());
return false; return false;
} }

@ -10,6 +10,7 @@
* *
*/ */
#include "Defines.h" #include "Defines.h"
#include "common/analog/AnalogDefines.h"
#include "common/dmr/DMRDefines.h" #include "common/dmr/DMRDefines.h"
#include "common/p25/P25Defines.h" #include "common/p25/P25Defines.h"
#include "common/nxdn/NXDNDefines.h" #include "common/nxdn/NXDNDefines.h"
@ -53,10 +54,12 @@ BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, b
m_rxDMRData(NET_RING_BUF_SIZE, "DMR Net Buffer"), m_rxDMRData(NET_RING_BUF_SIZE, "DMR Net Buffer"),
m_rxP25Data(NET_RING_BUF_SIZE, "P25 Net Buffer"), m_rxP25Data(NET_RING_BUF_SIZE, "P25 Net Buffer"),
m_rxNXDNData(NET_RING_BUF_SIZE, "NXDN Net Buffer"), m_rxNXDNData(NET_RING_BUF_SIZE, "NXDN Net Buffer"),
m_rxAnalogData(NET_RING_BUF_SIZE, "Analog Net Buffer"),
m_random(), m_random(),
m_dmrStreamId(nullptr), m_dmrStreamId(nullptr),
m_p25StreamId(0U), m_p25StreamId(0U),
m_nxdnStreamId(0U), m_nxdnStreamId(0U),
m_analogStreamId(0U),
m_pktSeq(0U), m_pktSeq(0U),
m_audio() m_audio()
{ {
@ -74,6 +77,7 @@ BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, b
m_dmrStreamId[1U] = createStreamId(); m_dmrStreamId[1U] = createStreamId();
m_p25StreamId = createStreamId(); m_p25StreamId = createStreamId();
m_nxdnStreamId = createStreamId(); m_nxdnStreamId = createStreamId();
m_analogStreamId = createStreamId();
} }
/* Finalizes a instance of the BaseNetwork class. */ /* Finalizes a instance of the BaseNetwork class. */
@ -139,7 +143,7 @@ bool BaseNetwork::writeKeyReq(const uint16_t kId, const uint8_t algId)
modifyKeyCmd.encode(buffer + 11U); modifyKeyCmd.encode(buffer + 11U);
//Utils::dump("writeKeyReq", buffer, modifyKeyCmd.length() + 11U); //Utils::dump("BaseNetwork::writeKeyReq(), KMM Buffer", buffer, modifyKeyCmd.length() + 11U);
return writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, buffer, modifyKeyCmd.length() + 11U, RTP_END_OF_CALL_SEQ, 0U); return writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, buffer, modifyKeyCmd.length() + 11U, RTP_END_OF_CALL_SEQ, 0U);
} }
@ -346,6 +350,15 @@ void BaseNetwork::resetNXDN()
m_rxNXDNData.clear(); m_rxNXDNData.clear();
} }
/* Resets the analog ring buffer. */
void BaseNetwork::resetAnalog()
{
m_analogStreamId = createStreamId();
m_pktSeq = 0U;
m_rxAnalogData.clear();
}
/* Gets the current DMR stream ID. */ /* Gets the current DMR stream ID. */
uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const
@ -363,10 +376,12 @@ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const
/* Helper to send a data message to the master. */ /* Helper to send a data message to the master. */
bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId,
bool queueOnly, bool useAlternatePort, uint32_t peerId) bool queueOnly, bool useAlternatePort, uint32_t peerId, uint32_t ssrc)
{ {
if (peerId == 0U) if (peerId == 0U)
peerId = m_peerId; peerId = m_peerId;
if (ssrc == 0U)
ssrc = m_peerId;
if (useAlternatePort) { if (useAlternatePort) {
sockaddr_storage addr; sockaddr_storage addr;
@ -377,14 +392,14 @@ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data
if (udp::Socket::lookup(address, port, addr, addrLen) == 0) { if (udp::Socket::lookup(address, port, addr, addrLen) == 0) {
if (!queueOnly) if (!queueOnly)
return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen);
else else
m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, addr, addrLen); m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, addr, addrLen);
} }
} }
else { else {
if (!queueOnly) if (!queueOnly)
return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen);
else else
m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, m_addr, m_addrLen);
} }
@ -500,6 +515,11 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength)
return nullptr; return nullptr;
} }
if (length == 254U) {
m_rxP25Data.get(&length, 1U); // read the next byte for the actual length
length += 254U; // a packet length of 254 is a special case for P25 frames, so we need to add the 254 to the length
}
UInt8Array buffer; UInt8Array buffer;
frameLength = length; frameLength = length;
buffer = std::unique_ptr<uint8_t[]>(new uint8_t[length]); buffer = std::unique_ptr<uint8_t[]>(new uint8_t[length]);
@ -511,7 +531,8 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength)
/* Writes P25 LDU1 frame data to the network. */ /* Writes P25 LDU1 frame data to the network. */
bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
P25DEF::FrameType::E frameType, uint8_t controlByte)
{ {
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false; return false;
@ -523,7 +544,7 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS
} }
uint32_t messageLength = 0U; uint32_t messageLength = 0U;
UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType); UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType, controlByte);
if (message == nullptr) { if (message == nullptr) {
return false; return false;
} }
@ -533,7 +554,8 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS
/* Writes P25 LDU2 frame data to the network. */ /* Writes P25 LDU2 frame data to the network. */
bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
uint8_t controlByte)
{ {
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false; return false;
@ -545,7 +567,7 @@ bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS
} }
uint32_t messageLength = 0U; uint32_t messageLength = 0U;
UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data); UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data, controlByte);
if (message == nullptr) { if (message == nullptr) {
return false; return false;
} }
@ -560,9 +582,7 @@ bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSp
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false; return false;
bool resetSeq = false;
if (m_p25StreamId == 0U) { if (m_p25StreamId == 0U) {
resetSeq = true;
m_p25StreamId = createStreamId(); m_p25StreamId = createStreamId();
} }
@ -653,6 +673,108 @@ bool BaseNetwork::hasP25Data() const
return true; return true;
} }
/* Helper to validate a P25 network frame length. */
bool BaseNetwork::validateP25FrameLength(uint8_t& frameLength, uint32_t len, const P25DEF::DUID::E duid)
{
using namespace p25::defines;
// P25 network frame should never be less then 24 bytes
if (len < 24U) {
LogError(LOG_NET, "malformed P25 packet, len < 24, shouldn't happen");
return false;
}
// frame length should never be 0
if (frameLength == 0U) {
LogError(LOG_NET, "DUID $%02X, sent with frame length of 0?", duid);
return false;
}
// frame length should never be larger then the network packet length
if (frameLength > len) {
LogError(LOG_NET, "malformed P25 packet, frameLength > len (%u > %u), shouldn't happen", frameLength, len);
return false;
}
// validate frame length, because P25 has variable network frame lengths we should be validating
// the actual frame length to ensure we don't have buffer overflow vulnerabilities
switch (duid) {
case DUID::HDU:
// HDU's aren't actually ever sent over the network, they are packaged with the first LDU1 for the
// initating superframe
return false;
case DUID::TDU:
// TDUs are sent with the P25 message header only
if (frameLength != 24U) {
LogError(LOG_NET, P25_TDU_STR ", malformed TDU, discard.");
break;
}
break;
case DUID::LDU1:
// LDU1 with message header only, this shouldn't happen
if (frameLength <= 24U) {
LogError(LOG_NET, P25_LDU1_STR ", malformed LDU1, discard.");
break;
}
break;
case DUID::VSELP1:
// VSELP1 frames aren't actually sent over the network right now
return false;
case DUID::TSDU:
// oversized TSDU -- this shouldn't happen, truncate and only handle the size of the TSDU frame length
if (frameLength > P25_TSDU_FRAME_LENGTH_BYTES)
frameLength = P25_TSDU_FRAME_LENGTH_BYTES;
// TSDU with message header only, this shouldn't happen
if (frameLength <= 24U) {
LogError(LOG_NET, P25_TSDU_STR ", malformed TSDU, discard.");
break;
}
break;
case DUID::LDU2:
// LDU2 with message header only, this shouldn't happen
if (frameLength <= 24U) {
LogError(LOG_NET, P25_LDU2_STR ", malformed LDU2, discard.");
break;
}
break;
case DUID::VSELP2:
// VSELP2 frames aren't actually sent over the network right now
return false;
case DUID::PDU:
// PDU with message header only, this shouldn't happen
if (frameLength <= 24U) {
LogError(LOG_NET, P25_PDU_STR ", malformed PDU, discard.");
break;
}
break;
case DUID::TDULC:
// oversized TDULC -- this shouldn't happen, truncate and only handle the size of the TSDU frame length
if (frameLength > P25_TDULC_FRAME_LENGTH_BYTES)
frameLength = P25_TDULC_FRAME_LENGTH_BYTES;
// TDULC with message header only, this shouldn't happen
if (frameLength <= 24U) {
LogError(LOG_NET, P25_TSDU_STR ", malformed TDULC, discard.");
break;
}
break;
default:
LogError(LOG_NET, "unsupported DUID $%02X", duid);
return false;
}
return true;
}
/* Reads NXDN raw frame data from the NXDN ring buffer. */ /* Reads NXDN raw frame data from the NXDN ring buffer. */
UInt8Array BaseNetwork::readNXDN(bool& ret, uint32_t& frameLength) UInt8Array BaseNetwork::readNXDN(bool& ret, uint32_t& frameLength)
@ -725,6 +847,91 @@ bool BaseNetwork::hasNXDNData() const
return true; return true;
} }
/* Reads analog raw frame data from the analog ring buffer. */
UInt8Array BaseNetwork::readAnalog(bool& ret, uint32_t& frameLength)
{
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return nullptr;
ret = true;
if (m_rxAnalogData.isEmpty()) {
ret = false;
return nullptr;
}
uint8_t length = 0U;
m_rxAnalogData.get(&length, 1U);
if (length == 0U) {
ret = false;
return nullptr;
}
if (length < 254U) {
// if the length is less than 254, the analog packet is malformed, analog packets should never be less than 254 bytes
LogError(LOG_NET, "malformed analog packet, length < 254 (%u), shouldn't happen", length);
ret = false;
return nullptr;
}
if (length == 254U) {
m_rxAnalogData.get(&length, 1U); // read the next byte for the actual length
length += 254U; // a packet length of 254 is a special case for P25 frames, so we need to add the 254 to the length
}
UInt8Array buffer;
frameLength = length;
buffer = std::unique_ptr<uint8_t[]>(new uint8_t[length]);
::memset(buffer.get(), 0x00U, length);
m_rxAnalogData.get(buffer.get(), length);
return buffer;
}
/* Writes analog frame data to the network. */
bool BaseNetwork::writeAnalog(const analog::data::NetData& data, bool noSequence)
{
using namespace analog::defines;
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false;
AudioFrameType::E frameType = data.getFrameType();
bool resetSeq = false;
if (m_analogStreamId == 0U) {
resetSeq = true;
m_analogStreamId = createStreamId();
}
uint32_t messageLength = 0U;
UInt8Array message = createAnalog_Message(messageLength, m_analogStreamId, data);
if (message == nullptr) {
return false;
}
uint16_t seq = pktSeq(resetSeq);
if (frameType == AudioFrameType::TERMINATOR) {
seq = RTP_END_OF_CALL_SEQ;
}
if (noSequence) {
seq = RTP_END_OF_CALL_SEQ;
}
return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, message.get(), messageLength, seq, m_analogStreamId);
}
/* Helper to test if the analog ring buffer has data. */
bool BaseNetwork::hasAnalogData() const
{
if (m_rxAnalogData.isEmpty())
return false;
return true;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Protected Class Members // Protected Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -802,7 +1009,7 @@ UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t strea
data.getData(buffer + 20U); data.getData(buffer + 20U);
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, DMR", buffer, (DMR_PACKET_LENGTH + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createDMR_Message(), Message", buffer, (DMR_PACKET_LENGTH + PACKET_PAD));
length = (DMR_PACKET_LENGTH + PACKET_PAD); length = (DMR_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -811,7 +1018,7 @@ UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t strea
/* Creates an P25 frame message header. */ /* Creates an P25 frame message header. */
void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
p25::defines::FrameType::E frameType) p25::defines::FrameType::E frameType, uint8_t controlByte)
{ {
using namespace p25::defines; using namespace p25::defines;
assert(buffer != nullptr); assert(buffer != nullptr);
@ -830,7 +1037,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du
uint16_t sysId = control.getSiteData().sysId(); // System ID uint16_t sysId = control.getSiteData().sysId(); // System ID
SET_UINT16(sysId, buffer, 11U); SET_UINT16(sysId, buffer, 11U);
buffer[14U] = 0U; // Control Bits buffer[14U] = controlByte; // Control Bits
buffer[15U] = control.getMFId(); // MFId buffer[15U] = control.getMFId(); // MFId
@ -859,7 +1066,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du
control.getMI(mi); control.getMI(mi);
if (m_debug) { if (m_debug) {
Utils::dump(1U, "P25 HDU MI written to network", mi, MI_LENGTH_BYTES); Utils::dump(1U, "BaseNetwork::createP25_Message(), HDU MI", mi, MI_LENGTH_BYTES);
} }
for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) {
@ -871,7 +1078,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du
/* Creates an P25 LDU1 frame message. */ /* Creates an P25 LDU1 frame message. */
UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data, p25::defines::FrameType::E frameType) const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte)
{ {
using namespace p25::defines; using namespace p25::defines;
using namespace p25::dfsi::defines; using namespace p25::dfsi::defines;
@ -883,7 +1090,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L
::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header // construct P25 message header
createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte);
// pack DFSI data // pack DFSI data
uint32_t count = MSG_HDR_SIZE; uint32_t count = MSG_HDR_SIZE;
@ -937,7 +1144,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L
buffer[23U] = count; buffer[23U] = count;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createP25_LDU1Message(), Message, LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD));
length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD); length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -946,7 +1153,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L
/* Creates an P25 LDU2 frame message. */ /* Creates an P25 LDU2 frame message. */
UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data) const uint8_t* data, uint8_t controlByte)
{ {
using namespace p25::defines; using namespace p25::defines;
using namespace p25::dfsi::defines; using namespace p25::dfsi::defines;
@ -958,7 +1165,7 @@ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::L
::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header // construct P25 message header
createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte);
// pack DFSI data // pack DFSI data
uint32_t count = MSG_HDR_SIZE; uint32_t count = MSG_HDR_SIZE;
@ -1012,7 +1219,7 @@ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::L
buffer[23U] = count; buffer[23U] = count;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createP25_LDU2Message(), Message, LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD));
length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD); length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -1033,7 +1240,7 @@ UInt8Array BaseNetwork::createP25_TDUMessage(uint32_t& length, const p25::lc::LC
buffer[23U] = MSG_HDR_SIZE; buffer[23U] = MSG_HDR_SIZE;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, P25 TDU", buffer, (MSG_HDR_SIZE + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createP25_TDUMessage(), Message, TDU", buffer, (MSG_HDR_SIZE + PACKET_PAD));
length = (MSG_HDR_SIZE + PACKET_PAD); length = (MSG_HDR_SIZE + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -1054,15 +1261,12 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L
createP25_MessageHdr(buffer, DUID::TSDU, control, lsd, FrameType::TERMINATOR); createP25_MessageHdr(buffer, DUID::TSDU, control, lsd, FrameType::TERMINATOR);
// pack raw P25 TSDU bytes // pack raw P25 TSDU bytes
uint32_t count = MSG_HDR_SIZE;
::memcpy(buffer + 24U, data, P25_TSDU_FRAME_LENGTH_BYTES); ::memcpy(buffer + 24U, data, P25_TSDU_FRAME_LENGTH_BYTES);
count += P25_TSDU_FRAME_LENGTH_BYTES;
buffer[23U] = count; buffer[23U] = P25_TSDU_FRAME_LENGTH_BYTES;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, P25 TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createP25_TSDUMessage(), Message, TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD));
length = (P25_TSDU_PACKET_LENGTH + PACKET_PAD); length = (P25_TSDU_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -1083,15 +1287,12 @@ UInt8Array BaseNetwork::createP25_TDULCMessage(uint32_t& length, const p25::lc::
createP25_MessageHdr(buffer, DUID::TDULC, control, lsd, FrameType::TERMINATOR); createP25_MessageHdr(buffer, DUID::TDULC, control, lsd, FrameType::TERMINATOR);
// pack raw P25 TSDU bytes // pack raw P25 TSDU bytes
uint32_t count = MSG_HDR_SIZE;
::memcpy(buffer + 24U, data, P25_TDULC_FRAME_LENGTH_BYTES); ::memcpy(buffer + 24U, data, P25_TDULC_FRAME_LENGTH_BYTES);
count += P25_TDULC_FRAME_LENGTH_BYTES;
buffer[23U] = count; buffer[23U] = P25_TDULC_FRAME_LENGTH_BYTES;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, P25 TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createP25_TDULCMessage(), Message, TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD));
length = (P25_TDULC_PACKET_LENGTH + PACKET_PAD); length = (P25_TDULC_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -1139,7 +1340,7 @@ UInt8Array BaseNetwork::createP25_PDUMessage(uint32_t& length, const p25::data::
buffer[23U] = count; buffer[23U] = count;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, P25 PDU", buffer, (count + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createP25_PDUMessage(), Message, PDU", buffer, (count + PACKET_PAD));
length = (count + PACKET_PAD); length = (count + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
@ -1151,8 +1352,8 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC
{ {
assert(data != nullptr); assert(data != nullptr);
uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; uint8_t* buffer = new uint8_t[NXDN_PACKET_LENGTH + PACKET_PAD];
::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memset(buffer, 0x00U, NXDN_PACKET_LENGTH + PACKET_PAD);
// construct NXDN message header // construct NXDN message header
::memcpy(buffer + 0U, TAG_NXDN_DATA, 4U); ::memcpy(buffer + 0U, TAG_NXDN_DATA, 4U);
@ -1178,8 +1379,43 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC
buffer[23U] = count; buffer[23U] = count;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, NXDN", buffer, (count + PACKET_PAD)); Utils::dump(1U, "BaseNetwork::createNXDN_Message(), Message", buffer, (NXDN_PACKET_LENGTH + PACKET_PAD));
length = (count + PACKET_PAD); length = (NXDN_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer);
}
/* Creates an analog frame message. */
UInt8Array BaseNetwork::createAnalog_Message(uint32_t& length, const uint32_t streamId, const analog::data::NetData& data)
{
using namespace analog::defines;
uint8_t* buffer = new uint8_t[ANALOG_PACKET_LENGTH + PACKET_PAD];
::memset(buffer, 0x00U, ANALOG_PACKET_LENGTH + PACKET_PAD);
// construct analog message header
::memcpy(buffer + 0U, TAG_ANALOG_DATA, 4U);
uint32_t srcId = data.getSrcId(); // Source Address
SET_UINT24(srcId, buffer, 5U);
uint32_t dstId = data.getDstId(); // Target Address
SET_UINT24(dstId, buffer, 8U);
buffer[14U] = data.getControl(); // Control Bits
AudioFrameType::E frameType = data.getFrameType();
buffer[15U] = (uint8_t)frameType; // Audio Frame Type
buffer[15U] |= data.getGroup() ? 0x00U : 0x40U; // Group
buffer[4U] = data.getSeqNo(); // Sequence Number
// pack raw audio message bytes
data.getAudio(buffer + 20U);
if (m_debug)
Utils::dump(1U, "BaseNetwork::createAnalog_Message(), Message", buffer, (ANALOG_PACKET_LENGTH + PACKET_PAD));
length = (ANALOG_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer); return UInt8Array(buffer);
} }

@ -25,6 +25,7 @@
#define __BASE_NETWORK_H__ #define __BASE_NETWORK_H__
#include "common/Defines.h" #include "common/Defines.h"
#include "common/analog/data/NetData.h"
#include "common/dmr/data/NetData.h" #include "common/dmr/data/NetData.h"
#include "common/p25/data/DataHeader.h" #include "common/p25/data/DataHeader.h"
#include "common/p25/data/LowSpeedData.h" #include "common/p25/data/LowSpeedData.h"
@ -52,6 +53,7 @@
#define TAG_DMR_DATA "DMRD" #define TAG_DMR_DATA "DMRD"
#define TAG_P25_DATA "P25D" #define TAG_P25_DATA "P25D"
#define TAG_NXDN_DATA "NXDD" #define TAG_NXDN_DATA "NXDD"
#define TAG_ANALOG_DATA "ANOD"
#define TAG_REPEATER_LOGIN "RPTL" #define TAG_REPEATER_LOGIN "RPTL"
#define TAG_REPEATER_AUTH "RPTK" #define TAG_REPEATER_AUTH "RPTK"
@ -88,6 +90,8 @@ namespace network
const uint32_t P25_LDU2_PACKET_LENGTH = 181U; // 24 byte header + DFSI data + 1 byte frame type const uint32_t P25_LDU2_PACKET_LENGTH = 181U; // 24 byte header + DFSI data + 1 byte frame type
const uint32_t P25_TSDU_PACKET_LENGTH = 69U; // 24 byte header + TSDU data const uint32_t P25_TSDU_PACKET_LENGTH = 69U; // 24 byte header + TSDU data
const uint32_t P25_TDULC_PACKET_LENGTH = 78U; // 24 byte header + TDULC data const uint32_t P25_TDULC_PACKET_LENGTH = 78U; // 24 byte header + TDULC data
const uint32_t NXDN_PACKET_LENGTH = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer
const uint32_t ANALOG_PACKET_LENGTH = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer
/** /**
* @brief Network Peer Connection Status * @brief Network Peer Connection Status
@ -131,6 +135,19 @@ namespace network
NET_CONN_NAK_INVALID = 0xFFFF //! Invalid NET_CONN_NAK_INVALID = 0xFFFF //! Invalid
}; };
/**
* @brief Network Control Enumerations
* @note These values are used typically for terminators to specify specific DVM in-band control operations.
* @ingroup network_core
*/
enum CONTROL_BYTE {
NET_CTRL_GRANT_DEMAND = 0x80U, //! Grant Demand
NET_CTRL_GRANT_DENIAL = 0x40U, //! Grant Denial
NET_CTRL_SWITCH_OVER = 0x20U, //! Call Source RID Switch Over
NET_CTRL_GRANT_ENCRYPT = 0x08U, //! Grant Encrypt
NET_CTRL_U2U = 0x01U, //! Unit-to-Unit
};
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -297,6 +314,10 @@ namespace network
* @brief Resets the NXDN ring buffer. * @brief Resets the NXDN ring buffer.
*/ */
virtual void resetNXDN(); virtual void resetNXDN();
/**
* @brief Resets the analog ring buffer.
*/
virtual void resetAnalog();
/** /**
* @brief Gets the current DMR stream ID. * @brief Gets the current DMR stream ID.
@ -314,6 +335,11 @@ namespace network
* @return uint32_t Stream ID. * @return uint32_t Stream ID.
*/ */
uint32_t getNXDNStreamId() const { return m_nxdnStreamId; } uint32_t getNXDNStreamId() const { return m_nxdnStreamId; }
/**
* @brief Gets the current analog stream ID.
* @return uint32_t Stream ID.
*/
uint32_t getAnalogStreamId() const { return m_analogStreamId; }
/** /**
* @brief Helper to send a data message to the master. * @brief Helper to send a data message to the master.
@ -325,10 +351,12 @@ namespace network
* @param queueOnly Flag indicating this message should be queued instead of send immediately. * @param queueOnly Flag indicating this message should be queued instead of send immediately.
* @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics). * @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics).
* @param peerId If non-zero, overrides the peer ID sent in the packet to the master. * @param peerId If non-zero, overrides the peer ID sent in the packet to the master.
* @param ssrc If non-zero, overrides the RTP synchronization source ID sent in the packet to the master.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length,
uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U); uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U,
uint32_t ssrc = 0U);
// Digital Mobile Radio // Digital Mobile Radio
/** /**
@ -366,18 +394,21 @@ namespace network
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] data Buffer containing P25 LDU1 data to send.
* @param[in] frameType DVM P25 frame type. * @param[in] frameType DVM P25 frame type.
* @param[in] controlByte DVM Network Control Byte.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
p25::defines::FrameType::E frameType); P25DEF::FrameType::E frameType, uint8_t controlByte = 0U);
/** /**
* @brief Writes P25 LDU2 frame data to the network. * @brief Writes P25 LDU2 frame data to the network.
* @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU2 data to send. * @param[in] data Buffer containing P25 LDU2 data to send.
* @param[in] controlByte DVM Network Control Byte.
* @returns bool True, if message was sent, otherwise false. * @returns bool True, if message was sent, otherwise false.
*/ */
virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
uint8_t controlByte = 0U);
/** /**
* @brief Writes P25 TDU frame data to the network. * @brief Writes P25 TDU frame data to the network.
* @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] control Instance of p25::lc::LC containing link control data.
@ -418,6 +449,14 @@ namespace network
*/ */
bool hasP25Data() const; bool hasP25Data() const;
/**
* @brief Helper to validate a P25 network frame length.
* @param frameLength P25 encapsulated frame length.
* @param len Network packet length.
* @return bool True, if validated, otherwise false.
*/
bool validateP25FrameLength(uint8_t& frameLength, const uint32_t len, const P25DEF::DUID::E duid);
// Next Generation Digital Narrowband // Next Generation Digital Narrowband
/** /**
* @brief Reads NXDN raw frame data from the NXDN ring buffer. * @brief Reads NXDN raw frame data from the NXDN ring buffer.
@ -442,6 +481,28 @@ namespace network
*/ */
bool hasNXDNData() const; bool hasNXDNData() const;
// Analog Audio
/**
* @brief Reads analog MuLaw audio frame data from the analog ring buffer.
* @param[out] ret Flag indicating whether or not data was received.
* @param[out] frameLength Length in bytes of received frame.
* @returns UInt8Array Buffer containing received frame.
*/
virtual UInt8Array readAnalog(bool& ret, uint32_t& frameLength);
/**
* @brief Writes analog MuLaw audio frame data to the network.
* @param[in] data Instance of the analog::data::NetData class containing the analog message.
* @param noSequence Flag indicating the message should be sent with no RTP sequence (65535).
* @returns bool True, if message was sent, otherwise false.
*/
virtual bool writeAnalog(const analog::data::NetData& data, bool noSequence = false);
/**
* @brief Helper to test if the analog ring buffer has data.
* @returns bool True, if the network analog ring buffer has data, otherwise false.
*/
bool hasAnalogData() const;
public: public:
/** /**
* @brief Gets the peer ID of the network. * @brief Gets the peer ID of the network.
@ -489,12 +550,14 @@ namespace network
RingBuffer<uint8_t> m_rxDMRData; RingBuffer<uint8_t> m_rxDMRData;
RingBuffer<uint8_t> m_rxP25Data; RingBuffer<uint8_t> m_rxP25Data;
RingBuffer<uint8_t> m_rxNXDNData; RingBuffer<uint8_t> m_rxNXDNData;
RingBuffer<uint8_t> m_rxAnalogData;
std::mt19937 m_random; std::mt19937 m_random;
uint32_t* m_dmrStreamId; uint32_t* m_dmrStreamId;
uint32_t m_p25StreamId; uint32_t m_p25StreamId;
uint32_t m_nxdnStreamId; uint32_t m_nxdnStreamId;
uint32_t m_analogStreamId;
/** /**
* @brief Helper to update the RTP packet sequence. * @brief Helper to update the RTP packet sequence.
@ -529,7 +592,7 @@ namespace network
* | Reserved | * | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* *
* The data starting at offset 20 for 33 bytes if the raw DMR frame. * The data starting at offset 20 for 33 bytes of the raw DMR frame.
* *
* DMR frame message has 2 trailing bytes: * DMR frame message has 2 trailing bytes:
* *
@ -591,9 +654,10 @@ namespace network
* @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] frameType DVM P25 frame type. * @param[in] frameType DVM P25 frame type.
* @param[in] controlByte DVM Network Control Byte.
*/ */
void createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, void createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT); p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT, uint8_t controlByte = 0U);
/** /**
* @brief Creates an P25 LDU1 frame message. * @brief Creates an P25 LDU1 frame message.
@ -606,10 +670,11 @@ namespace network
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] data Buffer containing P25 LDU1 data to send.
* @param[in] frameType DVM P25 frame type. * @param[in] frameType DVM P25 frame type.
* @param[in] controlByte DVM Network Control Byte.
* @returns UInt8Array Buffer containing the built network message. * @returns UInt8Array Buffer containing the built network message.
*/ */
UInt8Array createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data, p25::defines::FrameType::E frameType); const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte = 0U);
/** /**
* @brief Creates an P25 LDU2 frame message. * @brief Creates an P25 LDU2 frame message.
* *
@ -620,10 +685,11 @@ namespace network
* @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data.
* @param[in] data Buffer containing P25 LDU2 data to send. * @param[in] data Buffer containing P25 LDU2 data to send.
* @param[in] controlByte DVM Network Control Byte.
* @returns UInt8Array Buffer containing the built network message. * @returns UInt8Array Buffer containing the built network message.
*/ */
UInt8Array createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, UInt8Array createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data); const uint8_t* data, uint8_t controlByte = 0U);
/** /**
* @brief Creates an P25 TDU frame message. * @brief Creates an P25 TDU frame message.
@ -700,7 +766,7 @@ namespace network
*/ */
UInt8Array createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, const uint8_t currentBlock, UInt8Array createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, const uint8_t currentBlock,
const uint8_t* data, const uint32_t len); const uint8_t* data, const uint32_t len);
/** /**
* @brief Creates an NXDN frame message. * @brief Creates an NXDN frame message.
* \code{.unparsed} * \code{.unparsed}
@ -732,7 +798,36 @@ namespace network
* @returns UInt8Array Buffer containing the built network message. * @returns UInt8Array Buffer containing the built network message.
*/ */
UInt8Array createNXDN_Message(uint32_t& length, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); UInt8Array createNXDN_Message(uint32_t& length, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len);
/**
* @brief Creates an analog frame message.
* \code{.unparsed}
* Below is the representation of the data layout for the analog frame
* message header. The header is 20 bytes in length.
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Protocol Tag (ANOD) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Seq No. | Source ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Destination ID | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved | Control Flags | R | Data Type |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* The data starting at offset 20 for 320 bytes of the raw analog frame.
* \endcode
* @param[out] length Length of network message buffer.
* @param streamId Stream ID.
* @param data Instance of the analog::data::Data class containing the analog message.
* @returns UInt8Array Buffer containing the built network message.
*/
UInt8Array createAnalog_Message(uint32_t& length, const uint32_t streamId, const analog::data::NetData& data);
private: private:
uint16_t m_pktSeq; uint16_t m_pktSeq;

@ -24,6 +24,12 @@ using namespace network::frame;
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::vector<FrameQueue::Timestamp> FrameQueue::m_streamTimestamps;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -32,10 +38,7 @@ using namespace network::frame;
FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug), FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug),
m_peerId(peerId), m_peerId(peerId),
#if defined(_WIN32) m_timestampMtx()
m_streamTSMtx(),
#endif // defined(_WIN32)
m_streamTimestamps()
{ {
assert(peerId < 999999999U); assert(peerId < 999999999U);
} }
@ -67,7 +70,7 @@ UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint3
if (length > 0) { if (length > 0) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Packet", buffer, length); Utils::dump(1U, "FrameQueue::read(), Network Packet", buffer, length);
m_failedReadCnt = 0U; m_failedReadCnt = 0U;
@ -211,6 +214,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_
void FrameQueue::clearTimestamps() void FrameQueue::clearTimestamps()
{ {
std::lock_guard<std::mutex> lock(m_timestampMtx);
m_streamTimestamps.clear(); m_streamTimestamps.clear();
} }
@ -218,6 +222,63 @@ void FrameQueue::clearTimestamps()
// Private Class Members // Private Class Members
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Search for a timestamp entry by stream ID. */
FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId)
{
std::lock_guard<std::mutex> lock(m_timestampMtx);
for (size_t i = 0; i < m_streamTimestamps.size(); i++) {
if (m_streamTimestamps[i].streamId == streamId)
return &m_streamTimestamps[i];
}
return nullptr;
}
/* Insert a timestamp for a stream ID. */
void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp)
{
std::lock_guard<std::mutex> lock(m_timestampMtx);
if (streamId == 0U || timestamp == INVALID_TS) {
LogError(LOG_NET, "FrameQueue::insertTimestamp(), invalid streamId or timestamp");
return;
}
Timestamp entry = { streamId, timestamp };
m_streamTimestamps.push_back(entry);
}
/* Update a timestamp for a stream ID. */
void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp)
{
std::lock_guard<std::mutex> lock(m_timestampMtx);
if (streamId == 0U || timestamp == INVALID_TS) {
LogError(LOG_NET, "FrameQueue::updateTimestamp(), invalid streamId or timestamp");
return;
}
// find the timestamp entry and update it
for (size_t i = 0; i < m_streamTimestamps.size(); i++) {
if (m_streamTimestamps[i].streamId == streamId) {
m_streamTimestamps[i].timestamp = timestamp;
break;
}
}
}
/* Erase a timestamp for a stream ID. */
void FrameQueue::eraseTimestamp(uint32_t streamId)
{
std::lock_guard<std::mutex> lock(m_timestampMtx);
m_streamTimestamps.erase(
std::remove_if(m_streamTimestamps.begin(), m_streamTimestamps.end(),
[streamId](const Timestamp& entry) { return entry.streamId == streamId; }),
m_streamTimestamps.end());
}
/* Generate RTP message for the frame queue. */ /* Generate RTP message for the frame queue. */
uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId,
@ -234,25 +295,17 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui
uint32_t timestamp = INVALID_TS; uint32_t timestamp = INVALID_TS;
if (streamId != 0U) { if (streamId != 0U) {
#if defined(_WIN32) auto entry = findTimestamp(streamId);
std::lock_guard<std::mutex> lock(m_streamTSMtx); if (entry != nullptr) {
#else timestamp = entry->timestamp;
m_streamTimestamps.lock(false);
#endif // defined(_WIN32)
auto entry = m_streamTimestamps.find(streamId);
if (entry != m_streamTimestamps.end()) {
timestamp = entry->second;
} }
if (timestamp != INVALID_TS) { if (timestamp != INVALID_TS) {
timestamp += (RTP_GENERIC_CLOCK_RATE / 133); timestamp += (RTP_GENERIC_CLOCK_RATE / 133);
if (m_debug) if (m_debug)
LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, previous TS = %u, TS = %u, rtpSeq = %u", streamId, m_streamTimestamps[streamId], timestamp, rtpSeq); LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, previous TS = %u, TS = %u, rtpSeq = %u", streamId, m_streamTimestamps[streamId], timestamp, rtpSeq);
m_streamTimestamps[streamId] = timestamp; updateTimestamp(streamId, timestamp);
} }
#if !defined(_WIN32)
m_streamTimestamps.unlock();
#endif // defined(_WIN32)
} }
uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES + length; uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES + length;
@ -274,34 +327,18 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui
timestamp = (uint32_t)system_clock::ntp::now(); timestamp = (uint32_t)system_clock::ntp::now();
header.setTimestamp(timestamp); header.setTimestamp(timestamp);
#if defined(_WIN32) insertTimestamp(streamId, timestamp);
std::lock_guard<std::mutex> lock(m_streamTSMtx);
m_streamTimestamps.insert({ streamId, timestamp });
#else
m_streamTimestamps.insert(streamId, timestamp);
#endif // defined(_WIN32)
} }
header.encode(buffer); header.encode(buffer);
if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) {
#if defined(_WIN32) auto entry = findTimestamp(streamId);
std::lock_guard<std::mutex> lock(m_streamTSMtx); if (entry != nullptr) {
#else
m_streamTimestamps.lock(false);
#endif // defined(_WIN32)
auto entry = m_streamTimestamps.find(streamId);
if (entry != m_streamTimestamps.end()) {
if (m_debug) if (m_debug)
LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq); LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq);
#if !defined(_WIN32) eraseTimestamp(streamId);
m_streamTimestamps.unlock();
#endif // defined(_WIN32)
m_streamTimestamps.erase(streamId);
} }
#if !defined(_WIN32)
m_streamTimestamps.unlock();
#endif // defined(_WIN32)
} }
RTPFNEHeader fneHeader = RTPFNEHeader(); RTPFNEHeader fneHeader = RTPFNEHeader();
@ -318,7 +355,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui
::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES, message, length); ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES, message, length);
if (m_debug) if (m_debug)
Utils::dump(1U, "FrameQueue::generateMessage() Buffered Message", buffer, bufferLen); Utils::dump(1U, "FrameQueue::generateMessage(), Buffered Message", buffer, bufferLen);
if (outBufferLen != nullptr) { if (outBufferLen != nullptr) {
*outBufferLen = bufferLen; *outBufferLen = bufferLen;

@ -17,17 +17,21 @@
#define __FRAME_QUEUE_H__ #define __FRAME_QUEUE_H__
#include "common/Defines.h" #include "common/Defines.h"
#include "common/concurrent/unordered_map.h"
#include "common/network/RTPHeader.h" #include "common/network/RTPHeader.h"
#include "common/network/RTPFNEHeader.h" #include "common/network/RTPFNEHeader.h"
#include "common/network/RawFrameQueue.h" #include "common/network/RawFrameQueue.h"
#include <mutex>
#include <vector>
namespace network namespace network
{ {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Constants // Constants
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U;
const uint8_t DVM_RTP_PAYLOAD_TYPE = 0x56U; const uint8_t DVM_RTP_PAYLOAD_TYPE = 0x56U;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -41,6 +45,11 @@ namespace network
class HOST_SW_API FrameQueue : public RawFrameQueue { class HOST_SW_API FrameQueue : public RawFrameQueue {
public: typedef std::pair<const NET_FUNC::ENUM, const NET_SUBFUNC::ENUM> OpcodePair; public: typedef std::pair<const NET_FUNC::ENUM, const NET_SUBFUNC::ENUM> OpcodePair;
public: public:
typedef struct {
uint32_t streamId;
uint32_t timestamp;
} Timestamp;
auto operator=(FrameQueue&) -> FrameQueue& = delete; auto operator=(FrameQueue&) -> FrameQueue& = delete;
auto operator=(FrameQueue&&) -> FrameQueue& = delete; auto operator=(FrameQueue&&) -> FrameQueue& = delete;
FrameQueue(FrameQueue&) = delete; FrameQueue(FrameQueue&) = delete;
@ -115,12 +124,33 @@ namespace network
private: private:
uint32_t m_peerId; uint32_t m_peerId;
#if defined(_WIN32) std::mutex m_timestampMtx;
std::mutex m_streamTSMtx;
std::unordered_map<uint32_t, uint32_t> m_streamTimestamps; static std::vector<Timestamp> m_streamTimestamps;
#else
concurrent::unordered_map<uint32_t, uint32_t> m_streamTimestamps; /**
#endif // defined(_WIN32) * @brief Search for a timestamp entry by stream ID.
* @param streamId Stream ID to find.
* @return Timestamp* Table entry.
*/
Timestamp* findTimestamp(uint32_t streamId);
/**
* @brief Insert a timestamp for a stream ID.
* @param streamId Stream ID.
* @param timestamp Timestamp.
*/
void insertTimestamp(uint32_t streamId, uint32_t timestamp);
/**
* @brief Update a timestamp for a stream ID.
* @param streamId Stream ID.
* @param timestamp Timestamp.
*/
void updateTimestamp(uint32_t streamId, uint32_t timestamp);
/**
* @brief Erase a timestamp for a stream ID.
* @param streamId Stream ID.
*/
void eraseTimestamp(uint32_t streamId);
/** /**
* @brief Generate RTP message for the frame queue. * @brief Generate RTP message for the frame queue.

@ -9,8 +9,6 @@
*/ */
#include "Defines.h" #include "Defines.h"
#include "common/edac/SHA256.h" #include "common/edac/SHA256.h"
#include "common/network/RTPHeader.h"
#include "common/network/RTPFNEHeader.h"
#include "common/network/json/json.h" #include "common/network/json/json.h"
#include "common/p25/kmm/KMMFactory.h" #include "common/p25/kmm/KMMFactory.h"
#include "common/Log.h" #include "common/Log.h"
@ -27,6 +25,7 @@ using namespace network;
// Constants // Constants
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#define MAX_RETRY_BEFORE_RECONNECT 4U
#define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds #define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -36,7 +35,8 @@ using namespace network;
/* Initializes a new instance of the Network class. */ /* Initializes a new instance of the Network class. */
Network::Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, Network::Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2,
bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) :
BaseNetwork(peerId, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, localPort), BaseNetwork(peerId, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, localPort),
m_pktLastSeq(0U), m_pktLastSeq(0U),
m_address(address), m_address(address),
@ -46,12 +46,14 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_dmrEnabled(dmr), m_dmrEnabled(dmr),
m_p25Enabled(p25), m_p25Enabled(p25),
m_nxdnEnabled(nxdn), m_nxdnEnabled(nxdn),
m_analogEnabled(analog),
m_updateLookup(updateLookup), m_updateLookup(updateLookup),
m_saveLookup(saveLookup), m_saveLookup(saveLookup),
m_ridLookup(nullptr), m_ridLookup(nullptr),
m_tidLookup(nullptr), m_tidLookup(nullptr),
m_salt(nullptr), m_salt(nullptr),
m_retryTimer(1000U, 10U), m_retryTimer(1000U, 10U),
m_retryCount(0U),
m_timeoutTimer(1000U, MAX_PEER_PING_TIME), m_timeoutTimer(1000U, MAX_PEER_PING_TIME),
m_pktSeq(0U), m_pktSeq(0U),
m_loginStreamId(0U), m_loginStreamId(0U),
@ -65,6 +67,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_dmrInCallCallback(nullptr), m_dmrInCallCallback(nullptr),
m_p25InCallCallback(nullptr), m_p25InCallCallback(nullptr),
m_nxdnInCallCallback(nullptr), m_nxdnInCallCallback(nullptr),
m_analogInCallCallback(nullptr),
m_keyRespCallback(nullptr) m_keyRespCallback(nullptr)
{ {
assert(!address.empty()); assert(!address.empty());
@ -78,6 +81,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_rxDMRStreamId[1U] = 0U; m_rxDMRStreamId[1U] = 0U;
m_rxP25StreamId = 0U; m_rxP25StreamId = 0U;
m_rxNXDNStreamId = 0U; m_rxNXDNStreamId = 0U;
m_rxAnalogStreamId = 0U;
m_metadata = new PeerMetadata(); m_metadata = new PeerMetadata();
} }
@ -122,6 +126,14 @@ void Network::resetNXDN()
m_rxNXDNStreamId = 0U; m_rxNXDNStreamId = 0U;
} }
/* Resets the analog ring buffer. */
void Network::resetAnalog()
{
BaseNetwork::resetAnalog();
m_rxAnalogStreamId = 0U;
}
/* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */
void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup)
@ -175,11 +187,23 @@ void Network::clock(uint32_t ms)
m_retryTimer.clock(ms); m_retryTimer.clock(ms);
if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) {
if (m_enabled) { if (m_enabled) {
if (m_retryCount > MAX_RETRY_BEFORE_RECONNECT) {
m_retryCount = 0U;
LogError(LOG_NET, "PEER %u connection to the master has timed out, retrying connection, remotePeerId = %u", m_peerId, m_remotePeerId);
close();
open();
m_retryTimer.start();
return;
}
bool ret = m_socket->open(m_addr.ss_family); bool ret = m_socket->open(m_addr.ss_family);
if (ret) { if (ret) {
ret = writeLogin(); ret = writeLogin();
if (!ret) { if (!ret) {
m_retryTimer.start(); m_retryTimer.start();
m_retryCount++;
return; return;
} }
@ -189,6 +213,7 @@ void Network::clock(uint32_t ms)
} }
m_retryTimer.start(); m_retryTimer.start();
m_retryCount++;
} }
return; return;
@ -236,16 +261,10 @@ void Network::clock(uint32_t ms)
} }
if (m_debug) { if (m_debug) {
LogDebugEx(LOG_NET, "Network::clock()", "RTP, peerId = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSequence(), LogDebugEx(LOG_NET, "Network::clock()", "RTP, peerId = %u, ssrc = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSSRC(), rtpHeader.getSequence(),
fneHeader.getStreamId(), fneHeader.getFunction(), fneHeader.getSubFunction()); fneHeader.getStreamId(), fneHeader.getFunction(), fneHeader.getSubFunction());
} }
// ensure the RTP synchronization source ID matches the FNE peer ID
if (m_remotePeerId != 0U && rtpHeader.getSSRC() != m_remotePeerId) {
LogWarning(LOG_NET, "RTP header and traffic session do not agree on remote peer ID? %u != %u", rtpHeader.getSSRC(), m_remotePeerId);
// should this be a fatal error?
}
// is this RTP packet destined for us? // is this RTP packet destined for us?
uint32_t peerId = fneHeader.getPeerId(); uint32_t peerId = fneHeader.getPeerId();
if ((m_peerId != peerId) && !m_promiscuousPeer) { if ((m_peerId != peerId) && !m_promiscuousPeer) {
@ -273,7 +292,7 @@ void Network::clock(uint32_t ms)
// are protocol messages being user handled? // are protocol messages being user handled?
if (m_userHandleProtocol) { if (m_userHandleProtocol) {
userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() },
buffer.get(), length, fneHeader.getStreamId()); buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader);
break; break;
} }
@ -322,8 +341,8 @@ void Network::clock(uint32_t ms)
} }
if (m_debug) if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer.get(), length);
if (length > 255) if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD))
LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
uint8_t len = length; uint8_t len = length;
@ -374,12 +393,21 @@ void Network::clock(uint32_t ms)
} }
if (m_debug) if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length);
if (length > 255) if (length > 512)
LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
// P25 frames can be up to 512 bytes, but we need to handle the case where the frame is larger than 255 bytes
uint8_t len = length; uint8_t len = length;
m_rxP25Data.addData(&len, 1U); if (length > 254) {
len = 254U;
m_rxP25Data.addData(&len, 1U);
len = length - 254U;
m_rxP25Data.addData(&len, 1U);
} else {
m_rxP25Data.addData(&len, 1U);
}
m_rxP25Data.addData(buffer.get(), len); m_rxP25Data.addData(buffer.get(), len);
} }
} }
@ -426,8 +454,8 @@ void Network::clock(uint32_t ms)
} }
if (m_debug) if (m_debug)
Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer.get(), length);
if (length > 255) if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD))
LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
uint8_t len = length; uint8_t len = length;
@ -437,6 +465,66 @@ void Network::clock(uint32_t ms)
} }
break; break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated Analog data frame
{
if (m_enabled && m_analogEnabled) {
if (m_debug) {
LogDebug(LOG_NET, "Analog, peer = %u, len = %u, pktSeq = %u, streamId = %u",
peerId, length, rtpHeader.getSequence(), streamId);
}
if (m_promiscuousPeer) {
m_rxAnalogStreamId = streamId;
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxAnalogStreamId == 0U) {
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxAnalogStreamId = 0U;
}
else {
m_rxAnalogStreamId = streamId;
}
m_pktLastSeq = m_pktSeq;
}
else {
if (m_rxAnalogStreamId == streamId) {
if (m_pktSeq != 0U && m_pktLastSeq != 0U) {
if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) {
LogWarning(LOG_NET, "Analog Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1);
}
}
m_pktLastSeq = m_pktSeq;
}
if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) {
m_rxAnalogStreamId = 0U;
}
}
}
if (m_debug)
Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer.get(), length);
if (length < (int)ANALOG_PACKET_LENGTH) {
LogError(LOG_NET, "Analog Stream %u, frame too short? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
} else {
if (length > 512)
LogError(LOG_NET, "Analog Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length);
// Analog frames are larger then 254 bytes, but we need to handle the case where the frame is larger than 255 bytes
uint8_t len = 254U;
m_rxAnalogData.addData(&len, 1U);
len = length - 254U;
m_rxAnalogData.addData(&len, 1U);
m_rxAnalogData.addData(buffer.get(), len);
}
}
}
break;
default: default:
Utils::dump("unknown protocol opcode from the master", buffer.get(), length); Utils::dump("unknown protocol opcode from the master", buffer.get(), length);
break; break;
@ -452,7 +540,7 @@ void Network::clock(uint32_t ms)
{ {
if (m_enabled && m_updateLookup) { if (m_enabled && m_updateLookup) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Received, WL RID", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, WL RID", buffer.get(), length);
if (m_ridLookup != nullptr) { if (m_ridLookup != nullptr) {
// update RID lists // update RID lists
@ -478,7 +566,7 @@ void Network::clock(uint32_t ms)
{ {
if (m_enabled && m_updateLookup) { if (m_enabled && m_updateLookup) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Received, BL RID", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, BL RID", buffer.get(), length);
if (m_ridLookup != nullptr) { if (m_ridLookup != nullptr) {
// update RID lists // update RID lists
@ -505,7 +593,7 @@ void Network::clock(uint32_t ms)
{ {
if (m_enabled && m_updateLookup) { if (m_enabled && m_updateLookup) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Received, ACTIVE TGS", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, ACTIVE TGS", buffer.get(), length);
if (m_tidLookup != nullptr) { if (m_tidLookup != nullptr) {
// update TGID lists // update TGID lists
@ -555,7 +643,7 @@ void Network::clock(uint32_t ms)
{ {
if (m_enabled && m_updateLookup) { if (m_enabled && m_updateLookup) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Received, DEACTIVE TGS", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, DEACTIVE TGS", buffer.get(), length);
if (m_tidLookup != nullptr) { if (m_tidLookup != nullptr) {
// update TGID lists // update TGID lists
@ -636,6 +724,19 @@ void Network::clock(uint32_t ms)
} }
} }
break; break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Analog In-Call Control
{
if (m_enabled && m_nxdnEnabled) {
NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U];
uint32_t dstId = GET_UINT24(buffer, 11U);
// fire off analog in-call callback if we have one
if (m_analogInCallCallback != nullptr) {
m_analogInCallCallback(command, dstId);
}
}
}
break;
default: default:
Utils::dump("unknown incall control opcode from the master", buffer.get(), length); Utils::dump("unknown incall control opcode from the master", buffer.get(), length);
@ -816,7 +917,7 @@ void Network::clock(uint32_t ms)
m_timeoutTimer.start(); m_timeoutTimer.start();
if (length >= 14) { if (length >= 14) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Received, PONG", buffer.get(), length); Utils::dump(1U, "Network::clock(), Network Rx, PONG", buffer.get(), length);
ulong64_t serverNow = 0U; ulong64_t serverNow = 0U;
@ -838,7 +939,7 @@ void Network::clock(uint32_t ms)
break; break;
default: default:
userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() },
buffer.get(), length, fneHeader.getStreamId()); buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader);
break; break;
} }
} }
@ -889,15 +990,16 @@ bool Network::open()
if (m_debug) if (m_debug)
LogMessage(LOG_NET, "PEER %u opening network", m_peerId); LogMessage(LOG_NET, "PEER %u opening network", m_peerId);
m_status = NET_STAT_WAITING_CONNECT;
m_timeoutTimer.start();
m_retryTimer.start();
m_retryCount = 0U;
if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) { if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) {
LogMessage(LOG_NET, "!!! Could not lookup the address of the master!"); LogMessage(LOG_NET, "!!! Could not lookup the address of the master!");
return false; return false;
} }
m_status = NET_STAT_WAITING_CONNECT;
m_timeoutTimer.start();
m_retryTimer.start();
return true; return true;
} }
@ -936,9 +1038,10 @@ void Network::enable(bool enabled)
/* User overrideable handler that allows user code to process network packets not handled by this class. */ /* User overrideable handler that allows user code to process network packets not handled by this class. */
void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId,
const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader)
{ {
Utils::dump("unknown opcode from the master", data, length); Utils::dump("Unknown opcode from the master", data, length);
} }
/* Writes login request to the network. */ /* Writes login request to the network. */
@ -954,7 +1057,7 @@ bool Network::writeLogin()
SET_UINT32(m_peerId, buffer, 4U); // Peer ID SET_UINT32(m_peerId, buffer, 4U); // Peer ID
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, Login", buffer, 8U); Utils::dump(1U, "Network::writeLogin(), Message, Login", buffer, 8U);
m_loginStreamId = createStreamId(); m_loginStreamId = createStreamId();
m_remotePeerId = 0U; m_remotePeerId = 0U;
@ -987,7 +1090,7 @@ bool Network::writeAuthorisation()
delete[] in; delete[] in;
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Message, Authorisation", out, 40U); Utils::dump(1U, "Network::writeAuthorisation(), Message, Authorisation", out, 40U);
return writeMaster({ NET_FUNC::RPTK, NET_SUBFUNC::NOP }, out, 40U, pktSeq(), m_loginStreamId); return writeMaster({ NET_FUNC::RPTK, NET_SUBFUNC::NOP }, out, 40U, pktSeq(), m_loginStreamId);
} }
@ -1048,7 +1151,7 @@ bool Network::writeConfig()
::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str());
if (m_debug) { if (m_debug) {
Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); Utils::dump(1U, "Network::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U);
} }
return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId);

@ -18,6 +18,8 @@
#include "common/Defines.h" #include "common/Defines.h"
#include "common/network/BaseNetwork.h" #include "common/network/BaseNetwork.h"
#include "common/network/RTPHeader.h"
#include "common/network/RTPFNEHeader.h"
#include "common/lookups/RadioIdLookup.h" #include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/TalkgroupRulesLookup.h"
#include "common/p25/kmm/KeysetItem.h" #include "common/p25/kmm/KeysetItem.h"
@ -90,6 +92,7 @@ namespace network
* @param dmr Flag indicating whether DMR is enabled. * @param dmr Flag indicating whether DMR is enabled.
* @param p25 Flag indicating whether P25 is enabled. * @param p25 Flag indicating whether P25 is enabled.
* @param nxdn Flag indicating whether NXDN is enabled. * @param nxdn Flag indicating whether NXDN is enabled.
* @param analog Flag indicating whether analog is enabled.
* @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic.
* @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic.
* @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network.
@ -97,7 +100,8 @@ namespace network
* @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network.
*/ */
Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2,
bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup);
/** /**
* @brief Finalizes a instance of the Network class. * @brief Finalizes a instance of the Network class.
*/ */
@ -116,7 +120,11 @@ namespace network
* @brief Resets the NXDN ring buffer. * @brief Resets the NXDN ring buffer.
*/ */
void resetNXDN() override; void resetNXDN() override;
/**
* @brief Resets the analog ring buffer.
*/
void resetAnalog() override;
/** /**
* @brief Sets the instances of the Radio ID and Talkgroup ID lookup tables. * @brief Sets the instances of the Radio ID and Talkgroup ID lookup tables.
* @param ridLookup Radio ID Lookup Table Instance * @param ridLookup Radio ID Lookup Table Instance
@ -211,6 +219,11 @@ namespace network
* @param callback * @param callback
*/ */
void setNXDNICCCallback(std::function<void(NET_ICC::ENUM, uint32_t)>&& callback) { m_nxdnInCallCallback = callback; } void setNXDNICCCallback(std::function<void(NET_ICC::ENUM, uint32_t)>&& callback) { m_nxdnInCallCallback = callback; }
/**
* @brief Helper to set the analog In-Call Control callback.
* @param callback
*/
void setAnalogICCCallback(std::function<void(NET_ICC::ENUM, uint32_t)>&& callback) { m_analogInCallCallback = callback; }
/** /**
* @brief Helper to set the enc. key response callback. * @brief Helper to set the enc. key response callback.
@ -235,6 +248,7 @@ namespace network
bool m_dmrEnabled; bool m_dmrEnabled;
bool m_p25Enabled; bool m_p25Enabled;
bool m_nxdnEnabled; bool m_nxdnEnabled;
bool m_analogEnabled;
bool m_updateLookup; bool m_updateLookup;
bool m_saveLookup; bool m_saveLookup;
@ -245,11 +259,13 @@ namespace network
uint8_t* m_salt; uint8_t* m_salt;
Timer m_retryTimer; Timer m_retryTimer;
uint8_t m_retryCount;
Timer m_timeoutTimer; Timer m_timeoutTimer;
uint32_t* m_rxDMRStreamId; uint32_t* m_rxDMRStreamId;
uint32_t m_rxP25StreamId; uint32_t m_rxP25StreamId;
uint32_t m_rxNXDNStreamId; uint32_t m_rxNXDNStreamId;
uint32_t m_rxAnalogStreamId;
uint16_t m_pktSeq; uint16_t m_pktSeq;
uint32_t m_loginStreamId; uint32_t m_loginStreamId;
@ -298,6 +314,11 @@ namespace network
* (This is called once the master sends a In-Call Control request.) * (This is called once the master sends a In-Call Control request.)
*/ */
std::function<void(NET_ICC::ENUM command, uint32_t dstId)> m_nxdnInCallCallback; std::function<void(NET_ICC::ENUM command, uint32_t dstId)> m_nxdnInCallCallback;
/**
* @brief Analog In-Call Control Function Callback.
* (This is called once the master sends a In-Call Control request.)
*/
std::function<void(NET_ICC::ENUM command, uint32_t dstId)> m_analogInCallCallback;
/** /**
* @brief Encryption Key Response Function Callback. * @brief Encryption Key Response Function Callback.
@ -312,9 +333,11 @@ namespace network
* @param[in] data Buffer containing message to send to peer. * @param[in] data Buffer containing message to send to peer.
* @param length Length of buffer. * @param length Length of buffer.
* @param streamId Stream ID. * @param streamId Stream ID.
* @param fneHeader RTP FNE Header.
* @param rtpHeader RTP Header.
*/ */
virtual void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, virtual void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U,
uint32_t streamId = 0U); uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader());
/** /**
* @brief Writes login request to the network. * @brief Writes login request to the network.

@ -72,7 +72,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL
frag->data = new uint8_t[frag->size + 1U]; frag->data = new uint8_t[frag->size + 1U];
::memcpy(frag->data, data + FRAG_HDR_SIZE, FRAG_BLOCK_SIZE); ::memcpy(frag->data, data + FRAG_HDR_SIZE, FRAG_BLOCK_SIZE);
// Utils::dump(1U, "Block Payload", frag->data, FRAG_BLOCK_SIZE); // Utils::dump(1U, "PacketBuffer::decode(), Block Payload", frag->data, FRAG_BLOCK_SIZE);
fragments.insert(curBlock, frag); fragments.insert(curBlock, frag);
} }
@ -106,7 +106,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL
uint32_t compressedLen = fragments[0]->compressedSize; uint32_t compressedLen = fragments[0]->compressedSize;
uint32_t len = fragments[0]->size; uint32_t len = fragments[0]->size;
buffer = new uint8_t[len +1U]; buffer = new uint8_t[len + 1U];
::memset(buffer, 0x00U, len + 1U); ::memset(buffer, 0x00U, len + 1U);
if (fragments.size() == 1U) { if (fragments.size() == 1U) {
::memcpy(buffer, fragments[0U]->data, len); ::memcpy(buffer, fragments[0U]->data, len);
@ -119,7 +119,10 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL
if (m_compression) { if (m_compression) {
uint32_t decompressedLen = 0U; uint32_t decompressedLen = 0U;
*message = Compression::decompress(buffer, compressedLen, &decompressedLen); UInt8Array decompressed = Compression::decompress(buffer, compressedLen, &decompressedLen);
*message = new uint8_t[decompressedLen];
::memset(*message, 0x00U, decompressedLen);
::memcpy(*message, decompressed.get(), decompressedLen);
// check that we got the appropriate data // check that we got the appropriate data
if (decompressedLen == len && message != nullptr) { if (decompressedLen == len && message != nullptr) {
@ -161,13 +164,13 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length)
// create temporary buffer // create temporary buffer
uint32_t compressedLen = 0U; uint32_t compressedLen = 0U;
uint8_t* buffer = nullptr; UInt8Array buffer = nullptr;
if (m_compression) { if (m_compression) {
buffer = Compression::compress(data, length, &compressedLen); buffer = Compression::compress(data, length, &compressedLen);
} else { } else {
buffer = new uint8_t[length]; buffer = std::make_unique<uint8_t[]>(length + 1U);
::memset(buffer, 0x00U, length); ::memset(buffer.get(), 0x00U, length + 1U);
::memcpy(buffer, data, length); ::memcpy(buffer.get(), data, length);
compressedLen = length; compressedLen = length;
} }
@ -198,15 +201,13 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length)
if (offs + FRAG_BLOCK_SIZE > compressedLen) if (offs + FRAG_BLOCK_SIZE > compressedLen)
blockSize = FRAG_BLOCK_SIZE - ((offs + FRAG_BLOCK_SIZE) - compressedLen); blockSize = FRAG_BLOCK_SIZE - ((offs + FRAG_BLOCK_SIZE) - compressedLen);
::memcpy(frag->data + FRAG_HDR_SIZE, buffer + offs, blockSize); ::memcpy(frag->data + FRAG_HDR_SIZE, buffer.get() + offs, blockSize);
offs += FRAG_BLOCK_SIZE; offs += FRAG_BLOCK_SIZE;
fragments.insert(i, frag); fragments.insert(i, frag);
LogInfoEx(LOG_NET, "%s, Outbound Packet Fragment, block %u of %u, txFragments = %u", m_name, i, blockCnt - 1U, fragments.size()); LogInfoEx(LOG_NET, "%s, Outbound Packet Fragment, block %u of %u, txFragments = %u", m_name, i, blockCnt - 1U, fragments.size());
} }
delete[] buffer;
} }
/* Helper to clear currently buffered fragments. */ /* Helper to clear currently buffered fragments. */

@ -87,6 +87,7 @@ namespace network
PROTOCOL_SUBFUNC_DMR = 0x00U, //! DMR PROTOCOL_SUBFUNC_DMR = 0x00U, //! DMR
PROTOCOL_SUBFUNC_P25 = 0x01U, //! P25 PROTOCOL_SUBFUNC_P25 = 0x01U, //! P25
PROTOCOL_SUBFUNC_NXDN = 0x02U, //! NXDN PROTOCOL_SUBFUNC_NXDN = 0x02U, //! NXDN
PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //! Analog
MASTER_SUBFUNC_WL_RID = 0x00U, //! Whitelist RIDs MASTER_SUBFUNC_WL_RID = 0x00U, //! Whitelist RIDs
MASTER_SUBFUNC_BL_RID = 0x01U, //! Blacklist RIDs MASTER_SUBFUNC_BL_RID = 0x01U, //! Blacklist RIDs

@ -71,7 +71,7 @@ UInt8Array RawFrameQueue::read(int& messageLength, sockaddr_storage& address, ui
if (length > 0) { if (length > 0) {
if (m_debug) if (m_debug)
Utils::dump(1U, "Network Packet", buffer, length); Utils::dump(1U, "RawFrameQueue::read(), Network Packet", buffer, length);
m_failedReadCnt = 0U; m_failedReadCnt = 0U;
@ -104,7 +104,7 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor
::memcpy(buffer, message, length); ::memcpy(buffer, message, length);
if (m_debug) if (m_debug)
Utils::dump(1U, "RawFrameQueue::write() Message", buffer, length); Utils::dump(1U, "RawFrameQueue::write(), Message", buffer, length);
// bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about // bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about
// this message // this message
@ -153,7 +153,7 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock
::memcpy(buffer, message, length); ::memcpy(buffer, message, length);
if (m_debug) if (m_debug)
Utils::dump(1U, "RawFrameQueue::enqueueMessage() Buffered Message", buffer, length); Utils::dump(1U, "RawFrameQueue::enqueueMessage(), Buffered Message", buffer, length);
udp::UDPDatagram* dgram = new udp::UDPDatagram; udp::UDPDatagram* dgram = new udp::UDPDatagram;
dgram->buffer = buffer; dgram->buffer = buffer;
@ -169,29 +169,34 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock
bool RawFrameQueue::flushQueue() bool RawFrameQueue::flushQueue()
{ {
bool ret = true; bool ret = true;
std::lock_guard<std::mutex> lock(m_queueMutex);
m_queueFlushing = true;
if (m_buffers.empty()) { // scope is intentional
return false; {
} std::lock_guard<std::mutex> lock(m_queueMutex);
m_queueFlushing = true;
// bryanb: this is the same as above -- but for some assinine reason prevents if (m_buffers.empty()) {
// weirdness return false;
if (m_buffers.size() == 0U) { }
return false;
}
// LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); // bryanb: this is the same as above -- but for some assinine reason prevents
// weirdness
if (m_buffers.size() == 0U) {
return false;
}
ret = true; // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size());
if (!m_socket->write(m_buffers)) {
// LogError(LOG_NET, "Failed writing data to the network"); ret = true;
ret = false; if (!m_socket->write(m_buffers)) {
// LogError(LOG_NET, "Failed writing data to the network");
ret = false;
}
m_queueFlushing = false;
} }
deleteBuffers(); deleteBuffers();
m_queueFlushing = false;
return ret; return ret;
} }
@ -203,6 +208,9 @@ bool RawFrameQueue::flushQueue()
void RawFrameQueue::deleteBuffers() void RawFrameQueue::deleteBuffers()
{ {
std::lock_guard<std::mutex> lock(m_queueMutex);
m_queueFlushing = true;
for (auto& buffer : m_buffers) { for (auto& buffer : m_buffers) {
if (buffer != nullptr) { if (buffer != nullptr) {
// LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length); // LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length);
@ -217,4 +225,5 @@ void RawFrameQueue::deleteBuffers()
} }
} }
m_buffers.clear(); m_buffers.clear();
m_queueFlushing = false;
} }

@ -311,7 +311,7 @@ std::vector<asio::const_buffer> HTTPPayload::toBuffers()
#if DEBUG_HTTP_PAYLOAD #if DEBUG_HTTP_PAYLOAD
::LogDebugEx(LOG_REST, "HTTPPayload::toBuffers()", "content = %s", content.c_str()); ::LogDebugEx(LOG_REST, "HTTPPayload::toBuffers()", "content = %s", content.c_str());
for (auto buffer : buffers) for (auto buffer : buffers)
Utils::dump("[HTTPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); Utils::dump("HTTPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size());
#endif #endif
return buffers; return buffers;

@ -154,7 +154,7 @@ namespace network
((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) {
if (m_debug) { if (m_debug) {
LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result);
Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength);
} }
result = HTTPLexer::INDETERMINATE; result = HTTPLexer::INDETERMINATE;
@ -163,7 +163,7 @@ namespace network
} else { } else {
if (m_debug) { if (m_debug) {
LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result);
Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength);
} }
if (m_contResult == HTTPLexer::INDETERMINATE) { if (m_contResult == HTTPLexer::INDETERMINATE) {
@ -180,7 +180,7 @@ namespace network
if (result == HTTPLexer::GOOD) { if (result == HTTPLexer::GOOD) {
if (m_debug) { if (m_debug) {
Utils::dump(1U, "HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); Utils::dump(1U, "SecureServerConnection::read(), HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length());
} }
m_continue = false; m_continue = false;
@ -188,7 +188,7 @@ namespace network
m_requestHandler.handleRequest(m_request, m_reply); m_requestHandler.handleRequest(m_request, m_reply);
if (m_debug) { if (m_debug) {
Utils::dump(1U, "HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); Utils::dump(1U, "SecureServerConnection::read(), HTTPS Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length());
} }
write(); write();

@ -135,7 +135,7 @@ namespace network
((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) {
if (m_debug) { if (m_debug) {
LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result);
Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength);
} }
m_contResult = result = HTTPLexer::INDETERMINATE; m_contResult = result = HTTPLexer::INDETERMINATE;
@ -144,7 +144,7 @@ namespace network
} else { } else {
if (m_debug) { if (m_debug) {
LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result);
Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength);
} }
if (m_contResult == HTTPLexer::INDETERMINATE) { if (m_contResult == HTTPLexer::INDETERMINATE) {
@ -161,7 +161,7 @@ namespace network
if (result == HTTPLexer::GOOD) { if (result == HTTPLexer::GOOD) {
if (m_debug) { if (m_debug) {
Utils::dump(1U, "HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); Utils::dump(1U, "ServerConnection::read(), HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length());
} }
m_continue = false; m_continue = false;
@ -169,7 +169,7 @@ namespace network
m_requestHandler.handleRequest(m_request, m_reply); m_requestHandler.handleRequest(m_request, m_reply);
if (m_debug) { if (m_debug) {
Utils::dump(1U, "HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); Utils::dump(1U, "ServerConnection::read(), HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length());
} }
write(); write();

@ -139,7 +139,7 @@ std::vector<asio::const_buffer> SIPPayload::toBuffers()
#if DEBUG_SIP_PAYLOAD #if DEBUG_SIP_PAYLOAD
::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "content = %s", content.c_str()); ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "content = %s", content.c_str());
for (auto buffer : buffers) for (auto buffer : buffers)
Utils::dump("[SIPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); Utils::dump("SIPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size());
#endif #endif
return buffers; return buffers;

@ -70,12 +70,12 @@ namespace network
// setup socket for non-blocking operations // setup socket for non-blocking operations
int flags = fcntl(fd, F_GETFL, 0); int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) { if (flags < 0) {
LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot accept SSL client"); throw std::runtime_error("Cannot accept SSL client");
} }
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot accept SSL client"); throw std::runtime_error("Cannot accept SSL client");
} }
@ -139,12 +139,12 @@ namespace network
if (!nonBlocking) { if (!nonBlocking) {
flags = fcntl(fd, F_GETFL, 0); flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) { if (flags < 0) {
LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot accept SSL client"); throw std::runtime_error("Cannot accept SSL client");
} }
if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) {
LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot accept SSL client"); throw std::runtime_error("Cannot accept SSL client");
} }
} }
@ -176,7 +176,7 @@ namespace network
ssize_t ret = ::connect(m_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)); ssize_t ret = ::connect(m_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
if (ret < 0) { if (ret < 0) {
LogError(LOG_NET, "Failed to connect to server, err: %d", errno); LogError(LOG_NET, "Failed to connect to server, err: %d (%s)", errno, strerror(errno));
} }
initSsl(m_pSSLCtx); initSsl(m_pSSLCtx);
@ -189,12 +189,12 @@ namespace network
if (nonBlocking) { if (nonBlocking) {
int flags = fcntl(m_fd, F_GETFL, 0); int flags = fcntl(m_fd, F_GETFL, 0);
if (flags < 0) { if (flags < 0) {
LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Failed to set SSL server connection to non-blocking"); throw std::runtime_error("Failed to set SSL server connection to non-blocking");
} }
if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) < 0) { if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Failed to set SSL server connection to non-blocking"); throw std::runtime_error("Failed to set SSL server connection to non-blocking");
} }
} }
@ -260,13 +260,13 @@ namespace network
{ {
m_fd = ::socket(AF_INET, SOCK_STREAM, 0); m_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (m_fd < 0) { if (m_fd < 0) {
LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot create the TCP socket"); throw std::runtime_error("Cannot create the TCP socket");
} }
int reuse = 1; int reuse = 1;
if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char*)& reuse, sizeof(reuse)) != 0) { if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char*)& reuse, sizeof(reuse)) != 0) {
LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot set the TCP socket option"); throw std::runtime_error("Cannot set the TCP socket option");
} }
} }

@ -72,13 +72,13 @@ namespace network
m_fd = ::socket(AF_INET, SOCK_STREAM, 0); m_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (m_fd < 0) { if (m_fd < 0) {
LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot create the TCP socket"); throw std::runtime_error("Cannot create the TCP socket");
} }
int reuse = 1; int reuse = 1;
if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) {
LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot set the TCP socket option"); throw std::runtime_error("Cannot set the TCP socket option");
} }
@ -94,7 +94,7 @@ namespace network
SecureTcpListener(const std::string& keyFile, const std::string& certFile, const uint16_t port, const std::string& address = "0.0.0.0") : SecureTcpListener(keyFile, certFile) SecureTcpListener(const std::string& keyFile, const std::string& certFile, const uint16_t port, const std::string& address = "0.0.0.0") : SecureTcpListener(keyFile, certFile)
{ {
if (!bind(address, port)) { if (!bind(address, port)) {
LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d", errno); LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot to bind secure TCP server"); throw std::runtime_error("Cannot to bind secure TCP server");
} }
} }

@ -110,7 +110,7 @@ int Socket::accept(sockaddr* address, socklen_t* addrlen) noexcept
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError()); LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Error returned from TCP poll, err: %d", errno); LogError(LOG_NET, "Error returned from TCP poll, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
return -1; return -1;
} }
@ -180,7 +180,7 @@ ssize_t Socket::listen(const std::string& ipAddr, const uint16_t port, int backl
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError()); LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Error returned from TCP poll, err: %d", errno); LogError(LOG_NET, "Error returned from TCP poll, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
return -1; return -1;
} }
@ -316,7 +316,7 @@ bool Socket::initSocket(const int domain, const int type, const int protocol)
} }
#else #else
if (m_fd < 0) { if (m_fd < 0) {
LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno));
return false; return false;
} }
#endif // defined(_WIN32) #endif // defined(_WIN32)
@ -340,7 +340,7 @@ bool Socket::bind(const std::string& ipAddr, const uint16_t port)
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Cannot bind the TCP address, err: %lu", ::GetLastError()); LogError(LOG_NET, "Cannot bind the TCP address, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Cannot bind the TCP address, err: %d", errno); LogError(LOG_NET, "Cannot bind the TCP address, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
retval = false; retval = false;
} }

@ -82,7 +82,7 @@ namespace network
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Failed to connect to server, err: %lu", ::GetLastError()); LogError(LOG_NET, "Failed to connect to server, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Failed to connect to server, err: %d", errno); LogError(LOG_NET, "Failed to connect to server, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
} }
} }
@ -106,7 +106,7 @@ namespace network
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Cannot set the TCP socket option, err: %lu", ::GetLastError()); LogError(LOG_NET, "Cannot set the TCP socket option, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
throw std::runtime_error("Cannot set the TCP socket option"); throw std::runtime_error("Cannot set the TCP socket option");
} }

@ -52,7 +52,7 @@ namespace network
} }
#else #else
if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) {
LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno));
throw std::runtime_error("Cannot set the TCP socket option"); throw std::runtime_error("Cannot set the TCP socket option");
} }
#endif // defined(_WIN32) #endif // defined(_WIN32)
@ -68,7 +68,7 @@ namespace network
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Cannot to bind TCP server, err: %lu", ::GetLastError()); LogError(LOG_NET, "Cannot to bind TCP server, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Cannot to bind TCP server, err: %d", errno); LogError(LOG_NET, "Cannot to bind TCP server, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
throw std::runtime_error("Cannot to bind TCP server"); throw std::runtime_error("Cannot to bind TCP server");
} }
@ -85,7 +85,7 @@ namespace network
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Failed to listen on TCP server, err: %lu", ::GetLastError()); LogError(LOG_NET, "Failed to listen on TCP server, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Failed to listen on TCP server, err: %d", errno); LogError(LOG_NET, "Failed to listen on TCP server, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
throw std::runtime_error("Failed to listen on TCP server."); throw std::runtime_error("Failed to listen on TCP server.");
} }

@ -145,7 +145,11 @@ bool Socket::open(const uint32_t af, const std::string& address, const uint16_t
if (port > 0U) { if (port > 0U) {
int reuse = 1; int reuse = 1;
if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char*)& reuse, sizeof(reuse)) == -1) { if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char*)& reuse, sizeof(reuse)) == -1) {
LogError(LOG_NET, "Cannot set the UDP socket option, err: %d", errno); #if defined(_WIN32)
LogError(LOG_NET, "Cannot bind the UDP socket option, err: %lu", ::GetLastError());
#else
LogError(LOG_NET, "Cannot set the UDP socket option, err: %d (%s)", errno, strerror(errno));
#endif // _WIN32
return false; return false;
} }
@ -205,7 +209,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Error returned from UDP poll, err: %lu", ::GetLastError()); LogError(LOG_NET, "Error returned from UDP poll, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Error returned from UDP poll, err: %d", errno); LogError(LOG_NET, "Error returned from UDP poll, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
return -1; return -1;
} }
@ -219,7 +223,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Error returned from recvfrom, err: %lu", ::GetLastError()); LogError(LOG_NET, "Error returned from recvfrom, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Error returned from recvfrom, err: %d", errno); LogError(LOG_NET, "Error returned from recvfrom, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
if (len == -1 && errno == ENOTSOCK) { if (len == -1 && errno == ENOTSOCK) {
@ -255,12 +259,12 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address
::memcpy(cryptoBuffer, buffer + 2U, len - 2U); ::memcpy(cryptoBuffer, buffer + 2U, len - 2U);
} }
// Utils::dump(1U, "Socket::read() crypted", cryptoBuffer, cryptedLen); // Utils::dump(1U, "Socket::read(), crypted", cryptoBuffer, cryptedLen);
// decrypt // decrypt
uint8_t* decrypted = m_aes->decryptECB(cryptoBuffer, cryptedLen, m_presharedKey); uint8_t* decrypted = m_aes->decryptECB(cryptoBuffer, cryptedLen, m_presharedKey);
// Utils::dump(1U, "Socket::read() decrypted", decrypted, cryptedLen); // Utils::dump(1U, "Socket::read(), decrypted", decrypted, cryptedLen);
// finalize, cleanup buffers and replace with new // finalize, cleanup buffers and replace with new
if (decrypted != nullptr) { if (decrypted != nullptr) {
@ -340,7 +344,7 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag
// encrypt // encrypt
uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey);
// Utils::dump(1U, "Socket::write() crypted", crypted, cryptedLen); // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen);
// finalize, cleanup buffers and replace with new // finalize, cleanup buffers and replace with new
out = std::unique_ptr<uint8_t[]>(new uint8_t[cryptedLen + 2U]); out = std::unique_ptr<uint8_t[]>(new uint8_t[cryptedLen + 2U]);
@ -365,10 +369,16 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag
ssize_t sent = ::sendto(m_fd, (char*)out.get(), length, 0, (sockaddr*)& address, addrLen); ssize_t sent = ::sendto(m_fd, (char*)out.get(), length, 0, (sockaddr*)& address, addrLen);
if (sent < 0) { if (sent < 0) {
if (errno == ENETUNREACH || errno == EHOSTUNREACH) {
// if we were not able to send a frame and the network logging is enabled -- disable network logging
if (!g_disableNetworkLog)
g_disableNetworkLog = true;
}
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError()); LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Error returned from sendto, err: %d", errno); LogError(LOG_NET, "Error returned from sendto, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
if (lenWritten != nullptr) { if (lenWritten != nullptr) {
@ -376,6 +386,10 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag
} }
} }
else { else {
// if we were able to send a frame and the network logging is disabled -- reenable network logging
if (g_disableNetworkLog)
g_disableNetworkLog = false;
if (sent == ssize_t(length)) if (sent == ssize_t(length))
result = true; result = true;
@ -491,7 +505,7 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept
continue; continue;
} }
// Utils::dump(1U, "Socket::write() crypted", crypted, cryptedLen); // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen);
// finalize // finalize
DECLARE_UINT8_ARRAY(out, cryptedLen + 2U); DECLARE_UINT8_ARRAY(out, cryptedLen + 2U);
@ -553,14 +567,22 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept
} }
if (sendmmsg(m_fd, headers, size, 0) < 0) { if (sendmmsg(m_fd, headers, size, 0) < 0) {
LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); #if defined(_WIN32)
LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError());
#else
LogError(LOG_NET, "Error returned from sendmmsg, err: %d (%s)", errno, strerror(errno));
#endif // _WIN32
if (lenWritten != nullptr) { if (lenWritten != nullptr) {
*lenWritten = -1; *lenWritten = -1;
} }
} }
if (sent < 0) { if (sent < 0) {
LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); #if defined(_WIN32)
LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError());
#else
LogError(LOG_NET, "Error returned from sendmmsg, err: %d (%s)", errno, strerror(errno));
#endif // _WIN32
if (lenWritten != nullptr) { if (lenWritten != nullptr) {
*lenWritten = -1; *lenWritten = -1;
} }
@ -655,7 +677,7 @@ std::string Socket::getLocalAddress()
err = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), err = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (err != 0) { if (err != 0) {
LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d", errno); LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d (%s)", errno, strerror(errno));
break; break;
} }
@ -800,7 +822,7 @@ bool Socket::initSocket(const int domain, const int type, const int protocol) no
} }
#else #else
if (m_fd < 0) { if (m_fd < 0) {
LogError(LOG_NET, "Cannot create the UDP socket, err: %d", errno); LogError(LOG_NET, "Cannot create the UDP socket, err: %d (%s)", errno, strerror(errno));
return false; return false;
} }
#endif // defined(_WIN32) #endif // defined(_WIN32)
@ -825,7 +847,7 @@ bool Socket::bind(const std::string& ipAddr, const uint16_t port) noexcept(false
#if defined(_WIN32) #if defined(_WIN32)
LogError(LOG_NET, "Cannot bind the UDP address, err: %lu", ::GetLastError()); LogError(LOG_NET, "Cannot bind the UDP address, err: %lu", ::GetLastError());
#else #else
LogError(LOG_NET, "Cannot bind the UDP address, err: %d", errno); LogError(LOG_NET, "Cannot bind the UDP address, err: %d (%s)", errno, strerror(errno));
#endif // defined(_WIN32) #endif // defined(_WIN32)
retval = false; retval = false;
} }

@ -91,7 +91,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues)
// creates the socket // creates the socket
fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (fd < 0) { if (fd < 0) {
LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
@ -102,7 +102,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues)
// obtains the network index number // obtains the network index number
if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) {
LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
@ -115,7 +115,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues)
// binds the socket to the 'socket_addr' address // binds the socket to the 'socket_addr' address
if (bind(fd, (struct sockaddr*) &socket_addr, sizeof(socket_addr)) != 0) { if (bind(fd, (struct sockaddr*) &socket_addr, sizeof(socket_addr)) != 0) {
LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
@ -128,7 +128,7 @@ hookErr:
// Rollback close file descriptors // Rollback close file descriptors
for (--i; i >= 0; i--) { for (--i; i >= 0; i--) {
if (close(((int *)queues)[i]) < 0) { if (close(((int *)queues)[i]) < 0) {
LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
} }
} }
@ -171,15 +171,15 @@ std::string allocateVirtualInterface(std::string name, bool tap, struct viface_q
// open TUN/TAP device // open TUN/TAP device
fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
if (fd < 0) { if (fd < 0) {
LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
// register a network device with the kernel // register a network device with the kernel
if (ioctl(fd, TUNSETIFF, (void *)&ifr) != 0) { if (ioctl(fd, TUNSETIFF, (void *)&ifr) != 0) {
LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
if (close(fd) < 0) { if (close(fd) < 0) {
LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
} }
goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
@ -194,7 +194,7 @@ allocErr:
// rollback close file descriptors // rollback close file descriptors
for (--i; i >= 0; i--) { for (--i; i >= 0; i--) {
if (close(((int *)queues)[i]) < 0) { if (close(((int *)queues)[i]) < 0) {
LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno));
} }
} }
@ -217,7 +217,7 @@ void readVIFlags(int sockfd, std::string name, struct ifreq& ifr)
// read interface flags // read interface flags
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) { if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) {
LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
} }
} }
@ -235,7 +235,7 @@ uint32_t readMTU(std::string name, size_t size)
// opens MTU file // opens MTU file
fd = open(("/sys/class/net/" + name + "/mtu").c_str(), O_RDONLY | O_NONBLOCK); fd = open(("/sys/class/net/" + name + "/mtu").c_str(), O_RDONLY | O_NONBLOCK);
if (fd < 0) { if (fd < 0) {
LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
@ -245,12 +245,12 @@ uint32_t readMTU(std::string name, size_t size)
// Handles errors // Handles errors
if (nread == -1) { if (nread == -1) {
LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
if (close(fd) < 0) { if (close(fd) < 0) {
LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly...
} }
@ -307,7 +307,7 @@ VIFace::VIFace(std::string name, bool tap, int id) :
// epoll create // epoll create
m_epollFd = epoll_create1(0); m_epollFd = epoll_create1(0);
if (m_epollFd == -1) { if (m_epollFd == -1) {
LogError(LOG_NET, "Unable to initialize epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to initialize epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
throw std::runtime_error("Unable to initialize epoll."); throw std::runtime_error("Unable to initialize epoll.");
} }
@ -317,20 +317,20 @@ VIFace::VIFace(std::string name, bool tap, int id) :
}; };
if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) {
LogError(LOG_NET, "Unable to configure epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to configure epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
throw std::runtime_error("Unable to configure epoll."); throw std::runtime_error("Unable to configure epoll.");
} }
ev.data.fd = m_queues.txFd; ev.data.fd = m_queues.txFd;
if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) {
LogError(LOG_NET, "Unable to configure epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to configure epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
throw std::runtime_error("Unable to configure epoll."); throw std::runtime_error("Unable to configure epoll.");
} }
// create socket channels to the NET kernel for later ioctl // create socket channels to the NET kernel for later ioctl
m_ksFd = -1; m_ksFd = -1;
m_ksFd = socket(AF_INET, SOCK_STREAM, 0); m_ksFd = socket(AF_INET, SOCK_STREAM, 0);
if (m_ksFd < 0) { if (m_ksFd < 0) {
LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d (%s)", name.c_str(), errno, strerror(errno));
throw std::runtime_error("Unable to create IPv4 socket channel to the NET kernel."); throw std::runtime_error("Unable to create IPv4 socket channel to the NET kernel.");
} }
@ -378,7 +378,7 @@ void VIFace::up()
} }
if (ioctl(m_ksFd, SIOCSIFHWADDR, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFHWADDR, &ifr) != 0) {
LogError(LOG_NET, "Unable to set MAC address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to set MAC address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
} }
@ -390,12 +390,12 @@ void VIFace::up()
// address // address
if (!m_ipv4Address.empty()) { if (!m_ipv4Address.empty()) {
if (!inet_pton(AF_INET, m_ipv4Address.c_str(), &addr->sin_addr)) { if (!inet_pton(AF_INET, m_ipv4Address.c_str(), &addr->sin_addr)) {
LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
if (ioctl(m_ksFd, SIOCSIFADDR, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFADDR, &ifr) != 0) {
LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
} }
@ -403,12 +403,12 @@ void VIFace::up()
// netmask // netmask
if (!m_ipv4Netmask.empty()) { if (!m_ipv4Netmask.empty()) {
if (!inet_pton(AF_INET, m_ipv4Netmask.c_str(), &addr->sin_addr)) { if (!inet_pton(AF_INET, m_ipv4Netmask.c_str(), &addr->sin_addr)) {
LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
if (ioctl(m_ksFd, SIOCSIFNETMASK, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFNETMASK, &ifr) != 0) {
LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
} }
@ -416,12 +416,12 @@ void VIFace::up()
// broadcast // broadcast
if (!m_ipv4Broadcast.empty()) { if (!m_ipv4Broadcast.empty()) {
if (!inet_pton(AF_INET, m_ipv4Broadcast.c_str(), &addr->sin_addr)) { if (!inet_pton(AF_INET, m_ipv4Broadcast.c_str(), &addr->sin_addr)) {
LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
if (ioctl(m_ksFd, SIOCSIFBRDADDR, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFBRDADDR, &ifr) != 0) {
LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
} }
@ -429,14 +429,14 @@ void VIFace::up()
// MTU // MTU
ifr.ifr_mtu = m_mtu; ifr.ifr_mtu = m_mtu;
if (ioctl(m_ksFd, SIOCSIFMTU, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFMTU, &ifr) != 0) {
LogError(LOG_NET, "Unable to set MTU %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to set MTU %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
// bring-up interface // bring-up interface
ifr.ifr_flags |= IFF_UP; ifr.ifr_flags |= IFF_UP;
if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) {
LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
} }
} }
@ -451,7 +451,7 @@ void VIFace::down() const
// bring-down interface // bring-down interface
ifr.ifr_flags &= ~IFF_UP; ifr.ifr_flags &= ~IFF_UP;
if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) {
LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
} }
} }
@ -477,7 +477,7 @@ ssize_t VIFace::read(uint8_t* buffer)
int ret = epoll_wait(m_epollFd, &wait_event, 1, 0); int ret = epoll_wait(m_epollFd, &wait_event, 1, 0);
if ((ret < 0) && (errno != EINTR)) { if ((ret < 0) && (errno != EINTR)) {
LogError(LOG_NET, "Error returned from epoll_wait, err: %d, error: %s", errno, strerror(errno)); LogError(LOG_NET, "Error returned from epoll_wait, err: %d (%s)", errno, strerror(errno));
return -1; return -1;
} }
@ -490,7 +490,7 @@ ssize_t VIFace::read(uint8_t* buffer)
// Read packet into our buffer // Read packet into our buffer
ssize_t len = ::read(wait_event.data.fd, buffer, m_mtu); ssize_t len = ::read(wait_event.data.fd, buffer, m_mtu);
if (len == -1) { if (len == -1) {
LogError(LOG_NET, "Error returned from read, err: %d, error: %s", errno, strerror(errno)); LogError(LOG_NET, "Error returned from read, err: %d (%s)", errno, strerror(errno));
} }
return len; return len;
@ -510,7 +510,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten)
*lenWritten = -1; *lenWritten = -1;
} }
LogError(LOG_NET, "Packet is too small, err: %d, error: %s", errno, strerror(errno)); LogError(LOG_NET, "Packet is too small, err: %d (%s)", errno, strerror(errno));
return false; return false;
} }
@ -519,7 +519,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten)
*lenWritten = -1; *lenWritten = -1;
} }
LogError(LOG_NET, "Packet is too large, err: %d, error: %s", errno, strerror(errno)); LogError(LOG_NET, "Packet is too large, err: %d (%s)", errno, strerror(errno));
return false; return false;
} }
@ -527,7 +527,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten)
bool result = false; bool result = false;
ssize_t sent = ::write(m_queues.txFd, buffer, length); ssize_t sent = ::write(m_queues.txFd, buffer, length);
if (sent < 0) { if (sent < 0) {
LogError(LOG_NET, "Error returned from write, err: %d, error: %s", errno, strerror(errno)); LogError(LOG_NET, "Error returned from write, err: %d (%s)", errno, strerror(errno));
if (lenWritten != nullptr) { if (lenWritten != nullptr) {
*lenWritten = -1; *lenWritten = -1;
@ -585,7 +585,7 @@ std::string VIFace::getMAC() const
readVIFlags(m_ksFd, m_name, ifr); readVIFlags(m_ksFd, m_name, ifr);
if (ioctl(m_ksFd, SIOCGIFHWADDR, &ifr) != 0) { if (ioctl(m_ksFd, SIOCGIFHWADDR, &ifr) != 0) {
LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return std::string(); return std::string();
} }
@ -608,7 +608,7 @@ void VIFace::setIPv4(std::string address)
{ {
struct in_addr addr; struct in_addr addr;
if (!inet_pton(AF_INET, address.c_str(), &addr)) { if (!inet_pton(AF_INET, address.c_str(), &addr)) {
LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
@ -628,7 +628,7 @@ void VIFace::setIPv4Netmask(std::string netmask)
{ {
struct in_addr addr; struct in_addr addr;
if (!inet_pton(AF_INET, netmask.c_str(), &addr)) { if (!inet_pton(AF_INET, netmask.c_str(), &addr)) {
LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
@ -648,7 +648,7 @@ void VIFace::setIPv4Broadcast(std::string broadcast)
{ {
struct in_addr addr; struct in_addr addr;
if (!inet_pton(AF_INET, broadcast.c_str(), &addr)) { if (!inet_pton(AF_INET, broadcast.c_str(), &addr)) {
LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return; return;
} }
@ -667,14 +667,14 @@ std::string VIFace::getIPv4Broadcast() const
void VIFace::setMTU(uint32_t mtu) void VIFace::setMTU(uint32_t mtu)
{ {
if (mtu < ETH_HLEN) { if (mtu < ETH_HLEN) {
LogError(LOG_NET, "MTU %d is too small %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "MTU %d is too small %s, err: %d (%s)", mtu, m_name.c_str(), errno, strerror(errno));
return; return;
} }
// are we sure about this upper validation? // are we sure about this upper validation?
// lo interface reports this number for its MTU // lo interface reports this number for its MTU
if (mtu > 65536) { if (mtu > 65536) {
LogError(LOG_NET, "MTU %d is too large %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "MTU %d is too large %s, err: %d (%s)", mtu, m_name.c_str(), errno, strerror(errno));
return; return;
} }
@ -690,7 +690,7 @@ uint32_t VIFace::getMTU() const
readVIFlags(m_ksFd, m_name, ifr); readVIFlags(m_ksFd, m_name, ifr);
if (ioctl(m_ksFd, SIOCGIFMTU, &ifr) != 0) { if (ioctl(m_ksFd, SIOCGIFMTU, &ifr) != 0) {
LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return 0U; return 0U;
} }
@ -710,7 +710,7 @@ std::string VIFace::ioctlGetIPv4(uint64_t request) const
readVIFlags(m_ksFd, m_name, ifr); readVIFlags(m_ksFd, m_name, ifr);
if (ioctl(m_ksFd, request, &ifr) != 0) { if (ioctl(m_ksFd, request, &ifr) != 0) {
LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return std::string(); return std::string();
} }
@ -720,7 +720,7 @@ std::string VIFace::ioctlGetIPv4(uint64_t request) const
struct sockaddr_in* ipaddr = (struct sockaddr_in*) &ifr.ifr_addr; struct sockaddr_in* ipaddr = (struct sockaddr_in*) &ifr.ifr_addr;
if (inet_ntop(AF_INET, &(ipaddr->sin_addr), addr, sizeof(addr)) == NULL) { if (inet_ntop(AF_INET, &(ipaddr->sin_addr), addr, sizeof(addr)) == NULL) {
LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno));
return std::string(); return std::string();
} }

@ -284,6 +284,17 @@ namespace nxdn
}; };
} }
/** @brief Remote Control Commands */
namespace RemoteControlCommand {
/** @brief Remote Control Commands */
enum : uint8_t {
STUN = 0x00U, //! Stun
REVIVE = 0x01U, //! Revive
KILL = 0x02U, //! Kill
REMOTE_MONITOR = 0x04U //! Remote Monitor
};
}
/** @brief Channel Access - Step */ /** @brief Channel Access - Step */
namespace ChAccessStep { namespace ChAccessStep {
/** @brief Channel Access - Step */ /** @brief Channel Access - Step */
@ -340,6 +351,8 @@ namespace nxdn
SRV_INFO = 0x19U, //! SRV_INFO - Service Information SRV_INFO = 0x19U, //! SRV_INFO - Service Information
CCH_INFO = 0x1AU, //! CCH_INFO - Control Channel Information CCH_INFO = 0x1AU, //! CCH_INFO - Control Channel Information
ADJ_SITE_INFO = 0x1BU, //! ADJ_SITE_INFO - Adjacent Site Information ADJ_SITE_INFO = 0x1BU, //! ADJ_SITE_INFO - Adjacent Site Information
REM_CON_REQ = 0x34U, //! REM_CON_REQ - Remote Control Request
REM_CON_RESP = 0x35U, //! REM_CON_RESP - Remote Control Response
// Traffic Channel Message Types // Traffic Channel Message Types
RTCH_VCALL = 0x01U, //! VCALL - Voice Call RTCH_VCALL = 0x01U, //! VCALL - Voice Call

@ -55,3 +55,46 @@ void NXDNUtils::addPostBits(uint8_t* data)
WRITE_BIT(data, n, b); WRITE_BIT(data, n, b);
} }
} }
/* Helper to convert a cause code to a string. */
std::string NXDNUtils::causeToString(uint8_t cause)
{
switch (cause) {
case CauseResponse::RSRC_NOT_AVAIL_NETWORK:
return std::string("RSRC_NOT_AVAIL_NETWORK (Resource Not Available - Network)");
case CauseResponse::RSRC_NOT_AVAIL_TEMP:
return std::string("RSRC_NOT_AVAIL_TEMP (Resource Not Available - Temporary)");
case CauseResponse::RSRC_NOT_AVAIL_QUEUED:
return std::string("RSRC_NOT_AVAIL_QUEUED (Resource Not Available - Queued)");
case CauseResponse::SVC_UNAVAILABLE:
return std::string("SVC_UNAVAILABLE (Service Unavailable)");
case CauseResponse::PROC_ERROR:
return std::string("PROC_ERROR (Procedure Error - Lack of packet data)");
case CauseResponse::PROC_ERROR_UNDEF:
return std::string("PROC_ERROR_UNDEF (Procedure Error - Invalid packet data)");
case CauseResponse::VD_GRP_NOT_PERM:
return std::string("VD_GRP_NOT_PERM (Voice Group Not Permitted)");
case CauseResponse::VD_REQ_UNIT_NOT_PERM:
return std::string("VD_REQ_UNIT_NOT_PERM (Voice Requesting Unit Not Permitted)");
case CauseResponse::VD_TGT_UNIT_NOT_PERM:
return std::string("VD_TGT_UNIT_NOT_PERM (Voice Target Unit Not Permitted)");
case CauseResponse::VD_REQ_UNIT_NOT_REG:
return std::string("VD_REQ_UNIT_NOT_REG (Voice Requesting Unit Not Registered)");
case CauseResponse::VD_QUE_CHN_RESOURCE_NOT_AVAIL:
return std::string("VD_QUE_CHN_RESOURCE_NOT_AVAIL (Voice Channel Resources Unavailable)");
case CauseResponse::VD_QUE_TGT_UNIT_BUSY:
return std::string("VD_QUE_TGT_UNIT_BUSY (Voice Target Unit Busy)");
case CauseResponse::VD_QUE_GRP_BUSY:
return std::string("VD_QUE_GRP_BUSY (Voice Group Busy)");
case CauseResponse::DISC_USER:
return std::string("DISC_USER (Disconnect by User)");
case CauseResponse::DISC_OTHER:
return std::string("DISC_OTHER (Other Disconnect)");
default:
return std::string();
}
}

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2020 Jonathan Naylor, G4KLX * Copyright (C) 2020 Jonathan Naylor, G4KLX
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL * Copyright (C) 2022,2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -41,6 +41,13 @@ namespace nxdn
* @param data Buffer to apply post field bits to. * @param data Buffer to apply post field bits to.
*/ */
static void addPostBits(uint8_t* data); static void addPostBits(uint8_t* data);
/**
* @brief Helper to convert a cause code to a string.
* @param cause Cause code.
* @returns std::string Cause code string.
*/
static std::string causeToString(uint8_t cause);
}; };
} // namespace nxdn } // namespace nxdn

@ -170,7 +170,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound)
} }
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "CAC::decode(), CAC Raw", buffer, NXDN_CAC_IN_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, CAC::decode(), CAC Raw", buffer, NXDN_CAC_IN_FEC_LENGTH_BYTES);
#endif #endif
if (longInbound) { if (longInbound) {
@ -213,7 +213,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound)
conv.chainback(m_data, NXDN_CAC_LONG_CRC_LENGTH_BITS); conv.chainback(m_data, NXDN_CAC_LONG_CRC_LENGTH_BITS);
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "Decoded Long CAC", m_data, (NXDN_CAC_LONG_CRC_LENGTH_BITS / 8U) + 1U); Utils::dump(2U, "NXDN, CAC::decode(), Decoded Long CAC", m_data, (NXDN_CAC_LONG_CRC_LENGTH_BITS / 8U) + 1U);
#endif #endif
// check CRC-16 // check CRC-16
@ -271,7 +271,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound)
conv.chainback(m_data, NXDN_CAC_SHORT_CRC_LENGTH_BITS); conv.chainback(m_data, NXDN_CAC_SHORT_CRC_LENGTH_BITS);
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "Decoded CAC", m_data, (NXDN_CAC_SHORT_CRC_LENGTH_BITS / 8U) + 1U); Utils::dump(2U, "NXDN, CAC::decode(), Decoded CAC", m_data, (NXDN_CAC_SHORT_CRC_LENGTH_BITS / 8U) + 1U);
#endif #endif
// check CRC-16 // check CRC-16
@ -298,7 +298,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound)
} }
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "Raw CAC Buffer", m_data, NXDN_CAC_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, CAC::decode(), Raw CAC Buffer", m_data, NXDN_CAC_FEC_LENGTH_BYTES);
#endif #endif
return true; return true;
@ -324,7 +324,7 @@ void CAC::encode(uint8_t* data) const
uint16_t crc = edac::CRC::addCRC16(buffer, NXDN_CAC_LENGTH_BITS); uint16_t crc = edac::CRC::addCRC16(buffer, NXDN_CAC_LENGTH_BITS);
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "Encoded CAC", buffer, NXDN_CAC_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, CAC::encode(), Encoded CAC", buffer, NXDN_CAC_FEC_LENGTH_BYTES);
#endif #endif
// encode convolution // encode convolution
@ -357,7 +357,7 @@ void CAC::encode(uint8_t* data) const
} }
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "CAC::encode(), CAC Puncture and Interleave", data, NXDN_FRAME_LENGTH_BYTES); Utils::dump(2U, "NXDN, CAC::encode(), CAC Puncture and Interleave", data, NXDN_FRAME_LENGTH_BYTES);
#endif #endif
// apply control field // apply control field
@ -380,7 +380,7 @@ void CAC::encode(uint8_t* data) const
} }
#if DEBUG_NXDN_CAC #if DEBUG_NXDN_CAC
Utils::dump(2U, "CAC::encode(), CAC + Control", data, NXDN_FRAME_LENGTH_BYTES); Utils::dump(2U, "NXDN, CAC::encode(), CAC + Control", data, NXDN_FRAME_LENGTH_BYTES);
#endif #endif
} }

@ -99,7 +99,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset)
} }
#if DEBUG_NXDN_FACCH1 #if DEBUG_NXDN_FACCH1
Utils::dump(2U, "FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_FEC_LENGTH_BYTES);
#endif #endif
// depuncture // depuncture
@ -137,7 +137,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset)
conv.chainback(m_data, NXDN_FACCH1_CRC_LENGTH_BITS); conv.chainback(m_data, NXDN_FACCH1_CRC_LENGTH_BITS);
#if DEBUG_NXDN_FACCH1 #if DEBUG_NXDN_FACCH1
Utils::dump(2U, "Decoded FACCH1", m_data, NXDN_FACCH1_CRC_LENGTH_BYTES); Utils::dump(2U, "NXDN, FACCH1::decode(), Decoded FACCH1", m_data, NXDN_FACCH1_CRC_LENGTH_BYTES);
#endif #endif
// check CRC-12 // check CRC-12
@ -163,7 +163,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const
edac::CRC::addCRC12(buffer, NXDN_FACCH1_LENGTH_BITS); edac::CRC::addCRC12(buffer, NXDN_FACCH1_LENGTH_BITS);
#if DEBUG_NXDN_FACCH1 #if DEBUG_NXDN_FACCH1
Utils::dump(2U, "Encoded FACCH1", buffer, NXDN_FACCH1_CRC_LENGTH_BYTES); Utils::dump(2U, "NXDN, FACCH1::encode(), Encoded FACCH1", buffer, NXDN_FACCH1_CRC_LENGTH_BYTES);
#endif #endif
// encode convolution // encode convolution
@ -196,7 +196,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const
} }
#if DEBUG_NXDN_SACCH #if DEBUG_NXDN_SACCH
Utils::dump(2U, "FACCH1::encode(), FACCH1 Puncture and Interleave", data, NXDN_FACCH1_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, FACCH1::encode(), FACCH1 Puncture and Interleave", data, NXDN_FACCH1_FEC_LENGTH_BYTES);
#endif #endif
} }

@ -139,7 +139,7 @@ bool SACCH::decode(const uint8_t* data)
conv.chainback(m_data, NXDN_SACCH_CRC_LENGTH_BITS); conv.chainback(m_data, NXDN_SACCH_CRC_LENGTH_BITS);
#if DEBUG_NXDN_SACCH #if DEBUG_NXDN_SACCH
Utils::dump(2U, "Decoded SACCH", m_data, NXDN_SACCH_CRC_LENGTH_BYTES); Utils::dump(2U, "SACCH::decode(), Decoded SACCH", m_data, NXDN_SACCH_CRC_LENGTH_BYTES);
#endif #endif
// check CRC-6 // check CRC-6
@ -178,7 +178,7 @@ void SACCH::encode(uint8_t* data) const
edac::CRC::addCRC6(buffer, NXDN_SACCH_LENGTH_BITS); edac::CRC::addCRC6(buffer, NXDN_SACCH_LENGTH_BITS);
#if DEBUG_NXDN_SACCH #if DEBUG_NXDN_SACCH
Utils::dump(2U, "Encoded SACCH", buffer, NXDN_SACCH_CRC_LENGTH_BYTES); Utils::dump(2U, "SACCH::encode(), Encoded SACCH", buffer, NXDN_SACCH_CRC_LENGTH_BYTES);
#endif #endif
// encode convolution // encode convolution

@ -125,7 +125,7 @@ bool UDCH::decode(const uint8_t* data)
} }
#if DEBUG_NXDN_UDCH #if DEBUG_NXDN_UDCH
Utils::dump(2U, "UDCH::decode(), UDCH Raw", buffer, NXDN_UDCH_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, UDCH::decode(), UDCH Raw", buffer, NXDN_UDCH_FEC_LENGTH_BYTES);
#endif #endif
// depuncture // depuncture
@ -163,7 +163,7 @@ bool UDCH::decode(const uint8_t* data)
conv.chainback(m_data, NXDN_UDCH_CRC_LENGTH_BITS); conv.chainback(m_data, NXDN_UDCH_CRC_LENGTH_BITS);
#if DEBUG_NXDN_UDCH #if DEBUG_NXDN_UDCH
Utils::dump(2U, "Decoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); Utils::dump(2U, "NXDN, UDCH::decode(), Decoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES);
#endif #endif
// check CRC-15 // check CRC-15
@ -193,7 +193,7 @@ void UDCH::encode(uint8_t* data) const
edac::CRC::addCRC15(buffer, NXDN_UDCH_LENGTH_BITS); edac::CRC::addCRC15(buffer, NXDN_UDCH_LENGTH_BITS);
#if DEBUG_NXDN_UDCH #if DEBUG_NXDN_UDCH
Utils::dump(2U, "Encoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); Utils::dump(2U, "NXDN, UDCH::encode(), Encoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES);
#endif #endif
// encode convolution // encode convolution
@ -226,7 +226,7 @@ void UDCH::encode(uint8_t* data) const
} }
#if DEBUG_NXDN_UDCH #if DEBUG_NXDN_UDCH
Utils::dump(2U, "UDCH::encode(), UDCH Puncture and Interleave", data, NXDN_UDCH_FEC_LENGTH_BYTES); Utils::dump(2U, "NXDN, UDCH::encode(), UDCH Puncture and Interleave", data, NXDN_UDCH_FEC_LENGTH_BYTES);
#endif #endif
} }

@ -115,7 +115,7 @@ void RCCH::decode(const uint8_t* data, uint8_t* rcch, uint32_t length, uint32_t
} }
if (m_verbose) { if (m_verbose) {
Utils::dump(2U, "Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES); Utils::dump(2U, "NXDN, RCCH::decode(), Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES);
} }
m_messageType = data[0U] & 0x3FU; // Message Type m_messageType = data[0U] & 0x3FU; // Message Type
@ -138,7 +138,7 @@ void RCCH::encode(uint8_t* data, const uint8_t* rcch, uint32_t length, uint32_t
} }
if (m_verbose) { if (m_verbose) {
Utils::dump(2U, "Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES); Utils::dump(2U, "NXDN, RCCH::encode(), Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES);
} }
} }

@ -114,7 +114,7 @@ void RTCH::decode(const uint8_t* data, uint32_t length, uint32_t offset)
} }
if (m_verbose) { if (m_verbose) {
Utils::dump(2U, "Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES); Utils::dump(2U, "NXDN, RTCH::decode(), Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES);
} }
decodeLC(rtch); decodeLC(rtch);
@ -137,7 +137,7 @@ void RTCH::encode(uint8_t* data, uint32_t length, uint32_t offset)
} }
if (m_verbose) { if (m_verbose) {
Utils::dump(2U, "Encoded RTCH Data", data, length); Utils::dump(2U, "NXDN, RTCH::encode(), Encoded RTCH Data", data, length);
} }
} }
@ -181,6 +181,10 @@ bool RTCH::decodeLC(const uint8_t* data)
{ {
assert(data != nullptr); assert(data != nullptr);
#if DEBUG_NXDN_RTCH
Utils::dump(2U, "NXDN, RTCH::decodeLC(), RTCH", data, NXDN_RTCH_LC_LENGTH_BYTES);
#endif
m_messageType = data[0U] & 0x3FU; // Message Type m_messageType = data[0U] & 0x3FU; // Message Type
// message type opcodes // message type opcodes
@ -286,6 +290,12 @@ bool RTCH::decodeLC(const uint8_t* data)
return false; return false;
} }
// is this a private call?
if (m_callType == CallType::INDIVIDUAL)
m_group = false;
else
m_group = true;
return true; return true;
} }
@ -405,6 +415,10 @@ void RTCH::encodeLC(uint8_t* data)
LogError(LOG_NXDN, "RTCH::encodeRTCH(), unknown RTCH value, messageType = $%02X", m_messageType); LogError(LOG_NXDN, "RTCH::encodeRTCH(), unknown RTCH value, messageType = $%02X", m_messageType);
return; return;
} }
#if DEBUG_NXDN_RTCH
Utils::dump(2U, "NXDN, RTCH::encodeLC(), RTCH", data, NXDN_RTCH_LC_LENGTH_BYTES);
#endif
} }
/* Internal helper to copy the the class. */ /* Internal helper to copy the the class. */

@ -22,12 +22,14 @@ using namespace nxdn::lc::rcch;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/* Initializes a new instance of the MESSAGE_TYPE_REG class. */ /* Initializes a new instance of the MESSAGE_TYPE_REG class. */
MESSAGE_TYPE_REG::MESSAGE_TYPE_REG() : RCCH() MESSAGE_TYPE_REG::MESSAGE_TYPE_REG() : RCCH()
{ {
m_messageType = MessageType::RCCH_REG; m_messageType = MessageType::RCCH_REG;
} }
/* Decode RCCH data. */ /* Decode RCCH data. */
void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t offset) void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t offset)
{ {
assert(data != nullptr); assert(data != nullptr);
@ -49,6 +51,7 @@ void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t off
} }
/* Encode RCCH data. */ /* Encode RCCH data. */
void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset)
{ {
assert(data != nullptr); assert(data != nullptr);
@ -73,6 +76,7 @@ void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset)
} }
/* Returns a string that represents the current RCCH. */ /* Returns a string that represents the current RCCH. */
std::string MESSAGE_TYPE_REG::toString(bool isp) std::string MESSAGE_TYPE_REG::toString(bool isp)
{ {
return (isp) ? std::string("RCCH_REG (Registration Request)") : return (isp) ? std::string("RCCH_REG (Registration Request)") :

@ -112,6 +112,7 @@ namespace p25
const uint8_t ENCRYPTED_NULL_IMBE[] = { 0xFCU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; const uint8_t ENCRYPTED_NULL_IMBE[] = { 0xFCU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U };
const uint8_t MOT_CALLSIGN_LENGTH_BYTES = 8U; const uint8_t MOT_CALLSIGN_LENGTH_BYTES = 8U;
const uint8_t HARRIS_USER_ALIAS_LENGTH_BYTES = 14U;
const uint8_t AUTH_RES_LENGTH_BYTES = 4U; const uint8_t AUTH_RES_LENGTH_BYTES = 4U;
const uint8_t AUTH_RAND_SEED_LENGTH_BYTES = 10U; const uint8_t AUTH_RAND_SEED_LENGTH_BYTES = 10U;
@ -138,6 +139,8 @@ namespace p25
/** @brief Motorola MFId */ /** @brief Motorola MFId */
const uint8_t MFG_MOT = 0x90U; const uint8_t MFG_MOT = 0x90U;
/** @brief L3Harris MFId */
const uint8_t MFG_HARRIS = 0xA4U;
/** @brief DVM; Omaha Communication Systems, LLC ($9C) */ /** @brief DVM; Omaha Communication Systems, LLC ($9C) */
const uint8_t MFG_DVM_OCS = 0x9CU; const uint8_t MFG_DVM_OCS = 0x9CU;
/** @} */ /** @} */
@ -683,6 +686,7 @@ namespace p25
TEL_INT_VCH_USER = 0x06U, //! TEL INT VCH USER - Telephone Interconnect Voice Channel User / MOT GPS DATA - Motorola In-Band GPS Data TEL_INT_VCH_USER = 0x06U, //! TEL INT VCH USER - Telephone Interconnect Voice Channel User / MOT GPS DATA - Motorola In-Band GPS Data
TEL_INT_ANS_RQST = 0x07U, //! TEL INT ANS RQST - Telephone Interconnect Answer Request TEL_INT_ANS_RQST = 0x07U, //! TEL INT ANS RQST - Telephone Interconnect Answer Request
EXPLICIT_SOURCE_ID = 0x09U, //! EXPLICIT SOURCE ID - Explicit Source ID EXPLICIT_SOURCE_ID = 0x09U, //! EXPLICIT SOURCE ID - Explicit Source ID
PRIVATE_EXT = 0x0AU, //! UU VCH USER EXT - Unit-to-Unit Voice Channel User Extended
CALL_TERM = 0x0FU, //! CALL TERM - Call Termination or Cancellation CALL_TERM = 0x0FU, //! CALL TERM - Call Termination or Cancellation
IDEN_UP = 0x18U, //! IDEN UP - Channel Identifier Update IDEN_UP = 0x18U, //! IDEN UP - Channel Identifier Update
SYS_SRV_BCAST = 0x20U, //! SYS SRV BCAST - System Service Broadcast SYS_SRV_BCAST = 0x20U, //! SYS SRV BCAST - System Service Broadcast
@ -692,7 +696,21 @@ namespace p25
CONV_FALLBACK = 0x2AU, //! CONV FALLBACK - Conventional Fallback CONV_FALLBACK = 0x2AU, //! CONV FALLBACK - Conventional Fallback
// LDUx/TDULC Motorola Link Control Opcode(s) // LDUx/TDULC Motorola Link Control Opcode(s)
FAILSOFT = 0x02U //! FAILSOFT - Failsoft FAILSOFT = 0x02U, //! FAILSOFT - Failsoft
MOT_PTT_LOC_HEADER = 0x29U, //! MOT PTT LOC HEADER - Motorola PTT Location Header
MOT_PTT_LOC_PAYLOAD = 0x2AU, //! MOT PTT LOC PAYLOAD - Motorola PTT Location Payload
// LDUx/TDULC Harris Link Control Opcode(s)
HARRIS_PTT_PA_ODD = 0x2AU, //! HARRIS PTT PA ODD - Harris PTT Position and Altitude Odd
HARRIS_PTT_PB_ODD = 0x2BU, //! HARRIS PTT PB ODD - Harris PTT Position and Bearing Odd
HARRIS_PTT_PA_EVEN = 0x2CU, //! HARRIS PTT PA EVEN - Harris PTT Position and Altitude Even
HARRIS_PTT_PB_EVEN = 0x2DU, //! HARRIS PTT PB EVEN - Harris PTT Position and Bearing Even
HARRIS_USER_ALIAS_PA_ODD = 0x32U, //! HARRIS USER ALIAS PA ODD - Harris User Alias Position and Altitude Odd
HARRIS_USER_ALIAS_PB_ODD = 0x33U, //! HARRIS USER ALIAS PB ODD - Harris User Alias Position and Bearing Odd
HARRIS_USER_ALIAS_PA_EVEN = 0x34U, //! HARRIS USER ALIAS PA EVEN - Harris User Alias Position and Altitude Even
HARRIS_USER_ALIAS_PB_EVEN = 0x35U, //! HARRIS USER ALIAS PB EVEN - Harris User Alias Position and Bearing Even
}; };
} }

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2016 Jonathan Naylor, G4KLX * Copyright (C) 2016 Jonathan Naylor, G4KLX
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "Defines.h" #include "Defines.h"
@ -242,3 +242,71 @@ uint32_t P25Utils::compare(const uint8_t* data1, const uint8_t* data2, uint32_t
return errs; return errs;
} }
/* Helper to convert a denial reason code to a string. */
std::string P25Utils::denyRsnToString(uint8_t reason)
{
switch (reason) {
case ReasonCode::DENY_REQ_UNIT_NOT_VALID:
return std::string("DENY_REQ_UNIT_NOT_VALID (Requesting Unit Not Valid)");
case ReasonCode::DENY_REQ_UNIT_NOT_AUTH:
return std::string("DENY_REQ_UNIT_NOT_AUTH (Requesting Unit Not Authenticated)");
case ReasonCode::DENY_TGT_UNIT_NOT_VALID:
return std::string("DENY_TGT_UNIT_NOT_VALID (Target Unit Not Valid)");
case ReasonCode::DENY_TGT_UNIT_NOT_AUTH:
return std::string("DENY_TGT_UNIT_NOT_AUTH (Target Unit Not Authenticated)");
case ReasonCode::DENY_SU_FAILED_AUTH:
return std::string("DENY_SU_FAILED_AUTH (Target Unit Failed Authentication)");
case ReasonCode::DENY_TGT_UNIT_REFUSED:
return std::string("DENY_TGT_UNIT_REFUSED (Target Unit Refused)");
case ReasonCode::DENY_TGT_GROUP_NOT_VALID:
return std::string("DENY_TGT_GROUP_NOT_VALID (Target Group Not Valid)");
case ReasonCode::DENY_TGT_GROUP_NOT_AUTH:
return std::string("DENY_TGT_GROUP_NOT_AUTH (Target Group Not Authenticated)");
case ReasonCode::DENY_NO_NET_RSRC_AVAIL:
return std::string("DENY_NO_NET_RSRC_AVAIL (Requested Network Resources Not Available)");
case ReasonCode::DENY_NO_RF_RSRC_AVAIL:
return std::string("DENY_NO_RF_RSRC_AVAIL (Requested RF Resources Not Available)");
case ReasonCode::DENY_SVC_IN_USE:
return std::string("DENY_SVC_IN_USE (Service In Use)");
case ReasonCode::DENY_SITE_ACCESS_DENIAL:
return std::string("DENY_SITE_ACCESS_DENIAL (Site Access Denial)");
case ReasonCode::DENY_PTT_COLLIDE:
return std::string("DENY_PTT_COLLIDE (Push-to-Talk Collision)");
case ReasonCode::DENY_PTT_BONK:
return std::string("DENY_PTT_BONK (Push-to-Talk Denial/Bonk)");
case ReasonCode::DENY_SYS_UNSUPPORTED_SVC:
return std::string("DENY_SYS_UNSUPPORTED_SVC (Service Unsupported)");
default:
return std::string();
}
}
/* Helper to convert a queue reason code to a string. */
std::string P25Utils::queueRsnToString(uint8_t reason)
{
switch (reason) {
case ReasonCode::QUE_REQ_ACTIVE_SERVICE:
return std::string("QUE_REQ_ACTIVE_SERVICE (Requested Service Active)");
case ReasonCode::QUE_TGT_ACTIVE_SERVICE:
return std::string("QUE_TGT_ACTIVE_SERVICE (Target Service Active)");
case ReasonCode::QUE_TGT_UNIT_QUEUED:
return std::string("QUE_TGT_UNIT_QUEUED (Target Unit Queued)");
case ReasonCode::QUE_CHN_RESOURCE_NOT_AVAIL:
return std::string("QUE_CHN_RESOURCE_NOT_AVAIL (Channel Resource Not Available)");
default:
return std::string();
}
}

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2016 Jonathan Naylor, G4KLX * Copyright (C) 2016 Jonathan Naylor, G4KLX
* Copyright (C) 2021 Bryan Biedenkapp, N2PLL * Copyright (C) 2021-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -198,6 +198,19 @@ namespace p25
* @returns uint32_t * @returns uint32_t
*/ */
static uint32_t compare(const uint8_t* data1, const uint8_t* data2, uint32_t length); static uint32_t compare(const uint8_t* data1, const uint8_t* data2, uint32_t length);
/**
* @brief Helper to convert a denial reason code to a string.
* @param reason Reason code.
* @returns std::string Reason code string.
*/
static std::string denyRsnToString(uint8_t reason);
/**
* @brief Helper to convert a queue reason code to a string.
* @param reason Reason code.
* @returns std::string Reason code string.
*/
static std::string queueRsnToString(uint8_t reason);
}; };
} // namespace p25 } // namespace p25

@ -53,12 +53,16 @@ bool AccessControl::validateSrcId(uint32_t id)
/* Helper to validate a talkgroup ID. */ /* Helper to validate a talkgroup ID. */
bool AccessControl::validateTGId(uint32_t id) bool AccessControl::validateTGId(uint32_t id, bool allowZero)
{ {
// TG0 is never valid // TG0 is never valid
if (id == 0U) if (id == 0U && !allowZero)
return false; return false;
// TG0 is always valid if allow zero is set
if (id == 0U && allowZero)
return true;
// check if TID ACLs are enabled // check if TID ACLs are enabled
if (!m_tidLookup->getACL()) { if (!m_tidLookup->getACL()) {
return true; return true;

@ -54,9 +54,10 @@ namespace p25
/** /**
* @brief Helper to validate a talkgroup ID. * @brief Helper to validate a talkgroup ID.
* @param id Talkgroup ID (TGID). * @param id Talkgroup ID (TGID).
* @param allowZero Flag indicating whether TGID 0 is allowed or not.
* @returns bool True, if talkgroup ID is valid, otherwise false. * @returns bool True, if talkgroup ID is valid, otherwise false.
*/ */
static bool validateTGId(uint32_t id); static bool validateTGId(uint32_t id, bool allowZero = false);
/** /**
* @brief Helper to determine if a talkgroup ID is non-preferred. * @brief Helper to determine if a talkgroup ID is non-preferred.

@ -128,7 +128,7 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header)
::memcpy(m_data, buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data ::memcpy(m_data, buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data
} }
catch (...) { catch (...) {
Utils::dump(2U, "P25, decoding excepted with input data", data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); Utils::dump(2U, "P25, DataBlock::decode(), decoding excepted with input data", data, P25_PDU_UNCONFIRMED_LENGTH_BYTES);
return false; return false;
} }
} }

@ -225,6 +225,9 @@ void DataHeader::encode(uint8_t* data, bool noTrellis)
header[1U] = ((m_rspClass & 0x03U) << 6) + // Response Class header[1U] = ((m_rspClass & 0x03U) << 6) + // Response Class
((m_rspType & 0x07U) << 3) + // Response Type ((m_rspType & 0x07U) << 3) + // Response Type
((m_rspStatus & 0x07U)); // Response Status ((m_rspStatus & 0x07U)); // Response Status
// the "full message" flag in a response PDU indicates whether or not the response is to an
// extended addressing PDU
if (!m_F) { if (!m_F) {
header[7U] = (m_srcLlId >> 16) & 0xFFU; // Source Logical Link ID header[7U] = (m_srcLlId >> 16) & 0xFFU; // Source Logical Link ID
header[8U] = (m_srcLlId >> 8) & 0xFFU; header[8U] = (m_srcLlId >> 8) & 0xFFU;

@ -160,6 +160,8 @@ namespace p25
DECLARE_PROPERTY(uint8_t, padLength, PadLength); DECLARE_PROPERTY(uint8_t, padLength, PadLength);
/** /**
* @brief Flag indicating whether or not this data packet is a full message. * @brief Flag indicating whether or not this data packet is a full message.
* @note This is used on extended addressing response packets to indicate whether or not
* the response is for a extended addressing request.
*/ */
DECLARE_PROPERTY(bool, F, FullMessage); DECLARE_PROPERTY(bool, F, FullMessage);
/** /**
@ -183,7 +185,7 @@ namespace p25
*/ */
DECLARE_PROPERTY(uint8_t, headerOffset, HeaderOffset); DECLARE_PROPERTY(uint8_t, headerOffset, HeaderOffset);
// Extended Addressing Data /** @name Symmetric Addressing Data */
/** /**
* @brief Service access point. * @brief Service access point.
*/ */
@ -192,8 +194,9 @@ namespace p25
* @brief Source Logical link ID. * @brief Source Logical link ID.
*/ */
DECLARE_PROPERTY(uint32_t, srcLlId, SrcLLId); DECLARE_PROPERTY(uint32_t, srcLlId, SrcLLId);
/** @} */
// Response Data /** @name Response Packet Data */
/** /**
* @brief Response class. * @brief Response class.
*/ */
@ -206,8 +209,9 @@ namespace p25
* @brief Response status. * @brief Response status.
*/ */
DECLARE_PROPERTY(uint8_t, rspStatus, ResponseStatus); DECLARE_PROPERTY(uint8_t, rspStatus, ResponseStatus);
/** @} */
// AMBT Data /** @name AMBT Packet Data */
/** /**
* @brief Alternate Trunking Block Opcode * @brief Alternate Trunking Block Opcode
*/ */
@ -220,6 +224,7 @@ namespace p25
* @brief Alternate Trunking Block Field 9 * @brief Alternate Trunking Block Field 9
*/ */
DECLARE_PROPERTY(uint8_t, ambtField9, AMBTField9); DECLARE_PROPERTY(uint8_t, ambtField9, AMBTField9);
/** @} */
private: private:
edac::Trellis m_trellis; edac::Trellis m_trellis;

@ -106,7 +106,7 @@ void LowSpeedData::process(uint8_t* data)
} }
} }
// Utils::dump(1U, "P25 Low Speed Data", lsd, 4U); // Utils::dump(1U, "P25, LowSpeedData::process(), Low Speed Data", lsd, 4U);
m_lsd1 = lsd[0U]; m_lsd1 = lsd[0U];
m_lsd2 = lsd[2U]; m_lsd2 = lsd[2U];

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -43,6 +43,16 @@ namespace p25
const uint32_t DFSI_VHDR_RAW_LEN = 36U; const uint32_t DFSI_VHDR_RAW_LEN = 36U;
const uint32_t DFSI_VHDR_LEN = 27U; const uint32_t DFSI_VHDR_LEN = 27U;
const uint32_t DFSI_MOT_START_LEN = 9U;
const uint32_t DFSI_MOT_VHDR_1_LEN = 30U;
const uint32_t DFSI_MOT_VHDR_2_LEN = 22U;
const uint32_t DFSI_MOT_TSBK_LEN = 24U;
const uint32_t DFSI_MOT_TDULC_LEN = 21U;
const uint32_t DFSI_TIA_VHDR_LEN = 22U;
const uint32_t DFSI_MOT_ICW_LENGTH = 6U;
const uint32_t DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES = 22U; const uint32_t DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES = 22U;
const uint32_t DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES = 14U; const uint32_t DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES = 14U;
const uint32_t DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES = 17U; const uint32_t DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES = 17U;
@ -68,31 +78,35 @@ namespace p25
* @{ * @{
*/ */
const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //!
const uint8_t DFSI_RTP_MOT_PAYLOAD_TYPE = 0x5DU; //!
const uint8_t DFSI_STATUS_NO_ERROR = 0x00U; //!
const uint8_t DFSI_STATUS_ERASE = 0x02U; //!
const uint8_t DFSI_RT_ENABLED = 0x02U; //! const uint8_t DFSI_RTP_SEQ_HANDSHAKE = 0x00U; //!
const uint8_t DFSI_RT_DISABLED = 0x04U; //! const uint8_t DFSI_RTP_SEQ_STARTSTOP = 0x01U; //!
const uint8_t DFSI_START_FLAG = 0x0CU; //! const uint8_t DFSI_MOT_ICW_FMT_TYPE3 = 0x02U; //!
const uint8_t DFSI_STOP_FLAG = 0x25U; //!
const uint8_t DFSI_TYPE_DATA_PAYLOAD = 0x06U; //! const uint8_t DFSI_MOT_ICW_PARM_NOP = 0x00U; //! No Operation
const uint8_t DFSI_TYPE_VOICE = 0x0BU; //! const uint8_t DSFI_MOT_ICW_PARM_PAYLOAD = 0x0CU; //! Stream Payload
const uint8_t DFSI_MOT_ICW_PARM_RSSI1 = 0x1AU; //! RSSI Data
const uint8_t DFSI_MOT_ICW_PARM_RSSI2 = 0x1BU; //! RSSI Data
const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //! Stop Stream
const uint8_t DFSI_MOT_ICW_TX_ADDRESS = 0x2CU; //! Tx Device Address
const uint8_t DFSI_MOT_ICW_RX_ADDRESS = 0x35U; //! Rx Device Address
const uint8_t DFSI_DEF_ICW_SOURCE = 0x00U; //! Infrastructure Source - Default Source const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //! Talkaround
const uint8_t DFSI_DEF_SOURCE = 0x00U; //! const uint8_t DFSI_BUSY_BITS_BUSY = 0x01U; //! Busy
const uint8_t DFSI_BUSY_BITS_INBOUND = 0x02U; //! Inbound
const uint8_t DFSI_BUSY_BITS_IDLE = 0x03U; //! Idle
/** @brief DFSI Frame Type */ /** @brief DFSI Frame Type */
namespace DFSIFrameType { namespace DFSIFrameType {
/** @brief DFSI Frame Type */ /** @brief DFSI Frame Type */
enum E : uint8_t { enum E : uint8_t {
MOT_START_STOP = 0x00U, // Motorola Start/Stop MOT_START_STOP = 0x00U, // Motorola/V.24 Start/Stop Stream
MOT_VHDR_1 = 0x60U, // Motorola Voice Header 1 MOT_VHDR_1 = 0x60U, // Motorola/V.24 Voice Header 1
MOT_VHDR_2 = 0x61U, // Motorola Voice Header 2 MOT_VHDR_2 = 0x61U, // Motorola/V.24 Voice Header 2
LDU1_VOICE1 = 0x62U, // IMBE LDU1 - Voice 1 LDU1_VOICE1 = 0x62U, // IMBE LDU1 - Voice 1
LDU1_VOICE2 = 0x63U, // IMBE LDU1 - Voice 2 LDU1_VOICE2 = 0x63U, // IMBE LDU1 - Voice 2
@ -114,8 +128,9 @@ namespace p25
LDU2_VOICE17 = 0x72U, // IMBE LDU2 - Voice 17 + Encryption Sync LDU2_VOICE17 = 0x72U, // IMBE LDU2 - Voice 17 + Encryption Sync
LDU2_VOICE18 = 0x73U, // IMBE LDU2 - Voice 18 + Low Speed Data LDU2_VOICE18 = 0x73U, // IMBE LDU2 - Voice 18 + Low Speed Data
PDU = 0x87U, // PDU MOT_TDULC = 0x74U, // Motorola/V.24 TDULC
TSBK = 0xA1U // TSBK MOT_PDU_SINGLE = 0x87U, // Motorola/V.24 PDU (Single Block)
MOT_TSBK = 0xA1U // Motorola/V.24 TSBK (Single Block)
}; };
} }

@ -404,8 +404,8 @@ void LC::encodeLDU1(uint8_t* data, const uint8_t* imbe)
} }
#if DEBUG_P25_DFSI #if DEBUG_P25_DFSI
LogDebugEx(LOG_P25, "LC::encodeLDU1()", "frameType = $%02X", m_frameType); LogDebugEx(LOG_P25, "dfsi::LC::encodeLDU1()", "frameType = $%02X", m_frameType);
Utils::dump(2U, "[LC::encodeLDU1()] DFSI LDU1 Frame", dfsiFrame, frameLength); Utils::dump(2U, "P25, dfsi::LC::encodeLDU1(), DFSI LDU1 Frame", dfsiFrame, frameLength);
#endif #endif
::memcpy(data, dfsiFrame, frameLength); ::memcpy(data, dfsiFrame, frameLength);
@ -644,8 +644,8 @@ void LC::encodeLDU2(uint8_t* data, const uint8_t* imbe)
} }
#if DEBUG_P25_DFSI #if DEBUG_P25_DFSI
LogDebugEx(LOG_P25, "LC::encodeLDU2()", "frameType = $%02X", m_frameType); LogDebugEx(LOG_P25, "dfsi::LC::encodeLDU2()", "frameType = $%02X", m_frameType);
Utils::dump(2U, "[LC::encodeLDU2()] DFSI LDU2 Frame", dfsiFrame, frameLength); Utils::dump(2U, "P25, dfsi::LC::encodeLDU2(), DFSI LDU2 Frame", dfsiFrame, frameLength);
#endif #endif
::memcpy(data, dfsiFrame, frameLength); ::memcpy(data, dfsiFrame, frameLength);

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -91,57 +91,23 @@ namespace p25
}; };
} }
/** @brief RT/RT Flag */ /** @brief Motorola Start of Stream Operation */
namespace RTFlag { namespace MotStartStreamOpcode {
/** @brief RT/RT Flag */ /** @brief Motorola Start of Stream Operation */
enum E : uint8_t { enum E : uint8_t {
ENABLED = 0x02U, //! RT/RT Enabled TRANSMIT = 0x02U, //! Transmit
DISABLED = 0x04U //! RT/RT Disabled RECEIVE = 0x04U, //! Receive
}; };
} }
/** @brief Start/Stop Flag */ /** @brief Motorola Stream Payload */
namespace StartStopFlag { namespace MotStreamPayload {
/** @brief Start/Stop Flag */ /** @brief Motorola Stream Payload */
enum E : uint8_t { enum E : uint8_t {
START = 0x0CU, //! Start VOICE = 0x0BU, //! P25 Voice
STOP = 0x25U //! Stop DATA = 0x0CU, //! P25 Data
}; TERM_LC = 0x0EU, //! P25 Termination Link Control
} TSBK = 0x0FU //! P25 TSBK
/** @brief V.24 Data Stream Type */
namespace StreamTypeFlag {
/** @brief V.24 Data Stream Type */
enum E : uint8_t {
VOICE = 0x0BU, //! Voice
TSBK = 0x0FU //! TSBK
};
}
/** @brief RSSI Data Validity */
namespace RssiValidityFlag {
/** @brief RSSI Data Validity */
enum E : uint8_t {
INVALID = 0x00U, //! Invalid
VALID = 0x1A //! Valid
};
}
/** @brief V.24 Data Source */
namespace SourceFlag {
/** @brief V.24 Data Source */
enum E : uint8_t {
DIU = 0x00U, //! DIU
QUANTAR = 0x02U //! Quantar
};
}
/** @brief */
namespace ICWFlag {
/** @brief */
enum E : uint8_t {
DIU = 0x00U, //! DIU
QUANTAR = 0x1B //! Quantar
}; };
} }
/** @} */ /** @} */

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#if !defined(__DFSI_FRAMES_H__) #if !defined(__DFSI_FRAMES_H__)
@ -20,13 +20,11 @@
#include "common/p25/dfsi/frames/FullRateVoice.h" #include "common/p25/dfsi/frames/FullRateVoice.h"
// "The" Manufacturer // "The" Manufacturer
#include "common/p25/dfsi/frames/MotFullRateVoice.h"
#include "common/p25/dfsi/frames/MotStartOfStream.h" #include "common/p25/dfsi/frames/MotStartOfStream.h"
#include "common/p25/dfsi/frames/MotStartVoiceFrame.h" #include "common/p25/dfsi/frames/MotStartVoiceFrame.h"
#include "common/p25/dfsi/frames/MotVoiceHeader1.h" #include "common/p25/dfsi/frames/MotFullRateVoice.h"
#include "common/p25/dfsi/frames/MotVoiceHeader2.h" #include "common/p25/dfsi/frames/MotTDULCFrame.h"
#include "common/p25/dfsi/frames/MotTSBKFrame.h" #include "common/p25/dfsi/frames/MotTSBKFrame.h"
#include "common/p25/dfsi/frames/MotPDUFrame.h"
// FSC // FSC
#include "common/p25/dfsi/frames/fsc/FSCMessage.h" #include "common/p25/dfsi/frames/fsc/FSCMessage.h"

@ -34,7 +34,7 @@ FullRateVoice::FullRateVoice() :
m_muteFrame(false), m_muteFrame(false),
m_lostFrame(false), m_lostFrame(false),
m_superframeCnt(0U), m_superframeCnt(0U),
m_busy(0U) m_busy(DFSI_BUSY_BITS_TALKAROUND)
{ {
imbeData = new uint8_t[IMBE_BUF_LEN]; imbeData = new uint8_t[IMBE_BUF_LEN];
::memset(imbeData, 0x00U, IMBE_BUF_LEN); ::memset(imbeData, 0x00U, IMBE_BUF_LEN);
@ -52,7 +52,7 @@ FullRateVoice::FullRateVoice(uint8_t* data) :
m_muteFrame(false), m_muteFrame(false),
m_lostFrame(false), m_lostFrame(false),
m_superframeCnt(0U), m_superframeCnt(0U),
m_busy(0U) m_busy(DFSI_BUSY_BITS_TALKAROUND)
{ {
decode(data); decode(data);
} }

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "common/p25/dfsi/frames/MotFullRateVoice.h" #include "common/p25/dfsi/frames/MotFullRateVoice.h"
@ -33,7 +33,8 @@ MotFullRateVoice::MotFullRateVoice() :
imbeData(nullptr), imbeData(nullptr),
additionalData(nullptr), additionalData(nullptr),
m_frameType(DFSIFrameType::LDU1_VOICE1), m_frameType(DFSIFrameType::LDU1_VOICE1),
m_source(SourceFlag::QUANTAR) m_totalErrors(0U),
m_busy(DFSI_BUSY_BITS_TALKAROUND)
{ {
imbeData = new uint8_t[RAW_IMBE_LENGTH_BYTES]; imbeData = new uint8_t[RAW_IMBE_LENGTH_BYTES];
::memset(imbeData, 0x00U, RAW_IMBE_LENGTH_BYTES); ::memset(imbeData, 0x00U, RAW_IMBE_LENGTH_BYTES);
@ -41,11 +42,17 @@ MotFullRateVoice::MotFullRateVoice() :
/* Initializes a instance of the MotFullRateVoice class. */ /* Initializes a instance of the MotFullRateVoice class. */
MotFullRateVoice::MotFullRateVoice(uint8_t* data) MotFullRateVoice::MotFullRateVoice(uint8_t* data) :
imbeData(nullptr),
additionalData(nullptr),
m_frameType(DFSIFrameType::LDU1_VOICE1),
m_totalErrors(0U),
m_busy(DFSI_BUSY_BITS_TALKAROUND)
{ {
// set our pointers to null since it doesn't get initialized otherwise // set our pointers to null since it doesn't get initialized otherwise
imbeData = nullptr; imbeData = nullptr;
additionalData = nullptr; additionalData = nullptr;
// decode // decode
decode(data); decode(data);
} }
@ -100,11 +107,14 @@ bool MotFullRateVoice::decode(const uint8_t* data, bool shortened)
if (shortened) { if (shortened) {
::memcpy(imbeData, data + 1U, RAW_IMBE_LENGTH_BYTES); ::memcpy(imbeData, data + 1U, RAW_IMBE_LENGTH_BYTES);
m_source = (SourceFlag::E)data[12U];
// Forgot to set this originally and left additionalData uninitialized, whoops! m_totalErrors = (uint8_t)((data[12U] >> 3) & 0x0FU); // Total Errors
m_busy = (uint8_t)(data[13U] & 0x03U); // Busy Status
// forgot to set this originally and left additionalData uninitialized, whoops!
additionalData = nullptr; additionalData = nullptr;
} else { } else {
// Frames 0x6A and 0x73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier // frames $6A and $73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier
uint8_t imbeStart = 5U; uint8_t imbeStart = 5U;
if (isVoice9or18()) { if (isVoice9or18()) {
imbeStart = 4U; imbeStart = 4U;
@ -119,7 +129,13 @@ bool MotFullRateVoice::decode(const uint8_t* data, bool shortened)
// copy IMBE data based on our imbe start position // copy IMBE data based on our imbe start position
::memcpy(imbeData, data + imbeStart, RAW_IMBE_LENGTH_BYTES); ::memcpy(imbeData, data + imbeStart, RAW_IMBE_LENGTH_BYTES);
m_source = (SourceFlag::E)data[RAW_IMBE_LENGTH_BYTES + imbeStart]; if (isVoice9or18()) {
m_totalErrors = 0U; // these frames don't have total errors
m_busy = (uint8_t)(data[3U] & 0x03U); // Busy Status
} else {
m_totalErrors = (uint8_t)((data[imbeStart + RAW_IMBE_LENGTH_BYTES] >> 2) & 0x0FU); // Total Errors
m_busy = (uint8_t)(data[imbeStart + RAW_IMBE_LENGTH_BYTES] & 0x03U); // Busy Status
}
} }
return true; return true;
@ -141,26 +157,28 @@ void MotFullRateVoice::encode(uint8_t* data, bool shortened)
// copy based on shortened frame or not // copy based on shortened frame or not
if (shortened) { if (shortened) {
::memcpy(data + 1U, imbeData, RAW_IMBE_LENGTH_BYTES); ::memcpy(data + 1U, imbeData, RAW_IMBE_LENGTH_BYTES);
data[12U] = (uint8_t)m_source; data[13U] = (uint8_t)(m_busy & 0x03U); // Busy Status
} }
// if not shortened, our IMBE data start position depends on frame type // if not shortened, our IMBE data start position depends on frame type
else { else {
// Starting index for the IMBE data // starting index for the IMBE data
uint8_t imbeStart = 5U; uint8_t imbeStart = 5U;
if (isVoice9or18()) { if (isVoice9or18()) {
imbeStart = 4U; imbeStart = 4U;
} }
// Check if we have additional data // check if we have additional data
if (additionalData != nullptr) { if (additionalData != nullptr) {
::memcpy(data + 1U, additionalData, ADDITIONAL_LENGTH); ::memcpy(data + 1U, additionalData, ADDITIONAL_LENGTH);
} }
// Copy rest of data
::memcpy(data + imbeStart, imbeData, RAW_IMBE_LENGTH_BYTES); ::memcpy(data + imbeStart, imbeData, RAW_IMBE_LENGTH_BYTES);
// Source byte at the end if (isVoice9or18()) {
data[11U + imbeStart] = (uint8_t)m_source; data[3U] = (uint8_t)(m_busy & 0x03U); // Busy Status
} else {
data[imbeStart + RAW_IMBE_LENGTH_BYTES] = (uint8_t)(m_busy & 0x03U); // Busy Status
}
} }
} }

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -35,7 +35,7 @@ namespace p25
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** /**
* @brief Implements a P25 Motorola full rate voice packet. * @brief Implements a P25 Motorola/V.24 full rate voice packet.
* \code{.unparsed} * \code{.unparsed}
* Byte 0 1 2 3 * Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -48,7 +48,7 @@ namespace p25
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 | * | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Src Flag | * | Busy Bits |
* +=+=+=+=+=+=+=+=+ * +=+=+=+=+=+=+=+=+
* \endcode * \endcode
* @ingroup dfsi_frames * @ingroup dfsi_frames
@ -99,9 +99,13 @@ namespace p25
*/ */
DECLARE_PROPERTY(defines::DFSIFrameType::E, frameType, FrameType); DECLARE_PROPERTY(defines::DFSIFrameType::E, frameType, FrameType);
/** /**
* @brief V.24 Data Source. * @brief Total errors detected in the frame.
*/ */
DECLARE_PROPERTY(SourceFlag::E, source, Source); DECLARE_PROPERTY(uint8_t, totalErrors, TotalErrors);
/**
* @brief Busy Status.
*/
DECLARE_PROPERTY(uint8_t, busy, Busy);
private: private:
/** /**

@ -1,108 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/P25Defines.h"
#include "common/p25/dfsi/frames/MotPDUFrame.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace p25::defines;
using namespace p25::dfsi;
using namespace p25::dfsi::defines;
using namespace p25::dfsi::frames;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the MotPDUFrame class. */
MotPDUFrame::MotPDUFrame() :
startOfStream(nullptr),
pduHeaderData(nullptr)
{
pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES];
::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES);
startOfStream = new MotStartOfStream();
}
/* Initializes a instance of the MotPDUFrame class. */
MotPDUFrame::MotPDUFrame(uint8_t* data) :
startOfStream(nullptr),
pduHeaderData(nullptr)
{
pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES];
::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES);
decode(data);
}
/* Finalizes a instance of the MotPDUFrame class. */
MotPDUFrame::~MotPDUFrame()
{
if (startOfStream != nullptr)
delete startOfStream;
if (pduHeaderData != nullptr)
delete[] pduHeaderData;
}
/* Decode a PDU frame. (only the PDU data header...) */
bool MotPDUFrame::decode(const uint8_t* data)
{
assert(data != nullptr);
// create a new start of stream
if (startOfStream != nullptr)
delete startOfStream;
startOfStream = new MotStartOfStream();
// create a buffer to decode the start record skipping the 10th byte (adjMM)
uint8_t startBuffer[MotStartOfStream::LENGTH];
::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH);
::memcpy(startBuffer + 1U, data, 4U);
// decode start of stream
startOfStream->decode(startBuffer);
::memcpy(pduHeaderData, data + 9U, P25_PDU_HEADER_LENGTH_BYTES);
return true;
}
/* Encode a PDU frame. (only the PDU data header...) */
void MotPDUFrame::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
// encode start of stream - scope is intentional
{
uint8_t buffer[MotStartOfStream::LENGTH];
startOfStream->encode(buffer);
// copy to data array (skipping first and last bytes)
::memcpy(data + 1U, buffer + 1U, 4U);
}
// encode PDU - scope is intentional
{
data[0U] = DFSIFrameType::PDU;
::memcpy(data + 9U, pduHeaderData, P25_PDU_HEADER_LENGTH_BYTES);
}
}

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "common/p25/dfsi/frames/MotStartOfStream.h" #include "common/p25/dfsi/frames/MotStartOfStream.h"
@ -28,34 +28,44 @@ using namespace p25::dfsi::frames;
/* Initializes a instance of the MotStartOfStream class. */ /* Initializes a instance of the MotStartOfStream class. */
MotStartOfStream::MotStartOfStream() : MotStartOfStream::MotStartOfStream() :
m_marker(FIXED_MARKER), m_format(DFSI_MOT_ICW_FMT_TYPE3),
m_rt(RTFlag::DISABLED), m_opcode(MotStartStreamOpcode::TRANSMIT),
m_startStop(StartStopFlag::START), icw(nullptr)
m_streamType(StreamTypeFlag::VOICE)
{ {
/* stub */ icw = new uint8_t[DFSI_MOT_ICW_LENGTH];
::memset(icw, 0x00U, DFSI_MOT_ICW_LENGTH);
} }
/* Initializes a instance of the MotStartOfStream class. */ /* Initializes a instance of the MotStartOfStream class. */
MotStartOfStream::MotStartOfStream(uint8_t* data) : MotStartOfStream::MotStartOfStream(uint8_t* data) :
m_marker(FIXED_MARKER), m_format(DFSI_MOT_ICW_FMT_TYPE3),
m_rt(RTFlag::DISABLED), m_opcode(MotStartStreamOpcode::TRANSMIT),
m_startStop(StartStopFlag::START), icw(nullptr)
m_streamType(StreamTypeFlag::VOICE)
{ {
icw = new uint8_t[DFSI_MOT_ICW_LENGTH];
::memset(icw, 0x00U, DFSI_MOT_ICW_LENGTH);
decode(data); decode(data);
} }
/* Finalizes a instance of the MotStartOfStream class. */
MotStartOfStream::~MotStartOfStream()
{
if (icw != nullptr)
delete icw;
}
/* Decode a start of stream frame. */ /* Decode a start of stream frame. */
bool MotStartOfStream::decode(const uint8_t* data) bool MotStartOfStream::decode(const uint8_t* data)
{ {
assert(data != nullptr); assert(data != nullptr);
m_rt = (RTFlag::E)data[2U]; m_format = data[1U] & 0x3FU;
m_startStop = (StartStopFlag::E)data[3U]; m_opcode = (MotStartStreamOpcode::E)data[2U];
m_streamType = (StreamTypeFlag::E)data[4U]; ::memcpy(icw, data + 3U, DFSI_MOT_ICW_LENGTH);
return true; return true;
} }
@ -67,8 +77,7 @@ void MotStartOfStream::encode(uint8_t* data)
assert(data != nullptr); assert(data != nullptr);
data[0U] = DFSIFrameType::MOT_START_STOP; data[0U] = DFSIFrameType::MOT_START_STOP;
data[1U] = FIXED_MARKER; data[1U] = m_format & 0x3FU;
data[2U] = m_rt; data[2U] = m_opcode;
data[3U] = m_startStop; ::memcpy(data + 3U, icw, DFSI_MOT_ICW_LENGTH);
data[4U] = m_streamType;
} }

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -34,25 +34,22 @@ namespace p25
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** /**
* @brief Implements a P25 Motorola start of stream packet. * @brief Implements a P25 Motorola/V.24 start of stream packet.
* \code{.unparsed} * \code{.unparsed}
* Byte 0 1 2 3 * Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Fixed Mark | RT Mode Flag | Start/Stop | Type Flag | * | FT | ICW Format | Opcode | Param 1 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved | * | Argment 1 | Param 2 | Argument 2 | Param 3 |
* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | * | Argment 3 |
* +-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+
* \endcode * \endcode
* @ingroup dfsi_frames * @ingroup dfsi_frames
*/ */
class HOST_SW_API MotStartOfStream { class HOST_SW_API MotStartOfStream {
public: public:
static const uint8_t LENGTH = 10U;
static const uint8_t FIXED_MARKER = 0x02U;
/** /**
* @brief Initializes a copy instance of the MotStartOfStream class. * @brief Initializes a copy instance of the MotStartOfStream class.
*/ */
@ -62,6 +59,10 @@ namespace p25
* @param data Buffer to containing MotStartOfStream to decode. * @param data Buffer to containing MotStartOfStream to decode.
*/ */
MotStartOfStream(uint8_t* data); MotStartOfStream(uint8_t* data);
/**
* @brief Finalizes a instance of the MotStartOfStream class.
*/
~MotStartOfStream();
/** /**
* @brief Decode a start of stream frame. * @brief Decode a start of stream frame.
@ -73,24 +74,92 @@ namespace p25
* @param[out] data Buffer to encode a MotStartOfStream. * @param[out] data Buffer to encode a MotStartOfStream.
*/ */
void encode(uint8_t* data); void encode(uint8_t* data);
public: /** @name Start of Stream Type 3 control word parameters. */
/**
* @brief Helper to get parameter 1 of the control word.
* @return uint8_t Parameter 1 of the control word.
*/
uint8_t getParam1() const { return icw[0U]; }
/** /**
* @brief * @brief Helper to get the argument for parameter 1 of the control word.
* @return uint8_t Argument 1 for parameter 1 of the control word.
*/ */
DECLARE_PROPERTY(uint8_t, marker, Marker); uint8_t getArgument1() const { return icw[1U]; }
/** /**
* @brief RT/RT Flag. * @brief Helper to set parameter 1 of the control word.
* @param value Parameter 1 of the control word.
*/ */
DECLARE_PROPERTY(RTFlag::E, rt, RT); void setParam1(uint8_t value) { icw[0U] = value; }
/** /**
* @brief Start/Stop. * @brief Helper to set the argument for parameter 1 of the control word.
* @param value Argument 1 for parameter 1 of the control word.
*/ */
DECLARE_PROPERTY(StartStopFlag::E, startStop, StartStop); void setArgument1(uint8_t value) { icw[1U] = value; }
/** /**
* @brief Stream Type. * @brief Helper to get parameter 2 of the control word.
* @return uint8_t Parameter 2 of the control word.
*/ */
DECLARE_PROPERTY(StreamTypeFlag::E, streamType, StreamType); uint8_t getParam2() const { return icw[2U]; }
/**
* @brief Helper to get the argument for parameter 2 of the control word.
* @return uint8_t Argument 2 for parameter 2 of the control word.
*/
uint8_t getArgument2() const { return icw[3U]; }
/**
* @brief Helper to set parameter 1 of the control word.
* @param value Parameter 1 of the control word.
*/
void setParam2(uint8_t value) { icw[2U] = value; }
/**
* @brief Helper to set the argument for parameter 2 of the control word.
* @param value Argument for parameter 2 of the control word.
*/
void setArgument2(uint8_t value) { icw[3U] = value; }
/**
* @brief Helper to get parameter 3 of the control word.
* @return uint8_t Parameter 3 of the control word.
*/
uint8_t getParam3() const { return icw[4U]; }
/**
* @brief Helper to get argument 3 for parameter 3 of the control word.
* @return uint8_t Argument for parameter 3 of the control word.
*/
uint8_t getArgument3() const { return icw[5U]; }
/**
* @brief Helper to set parameter 3 of the control word.
* @param value Parameter 3 of the control word.
*/
void setParam3(uint8_t value) { icw[4U] = value; }
/**
* @brief Helper to set the argument for parameter 3 of the control word.
* @param value Argument for parameter 3 of the control word.
*/
void setArgument3(uint8_t value) { icw[5U] = value; }
/** @} */
/**
* @brief Get the raw ICW parameter/argument buffer.
* @return uint8_t* Raw ICW buffer.
* @note The buffer is 6 bytes long and contains the parameters and arguments for the
* start of stream control word.
*/
uint8_t* getICW() const { return icw; }
public:
/**
* @brief Format.
*/
DECLARE_PROPERTY(uint8_t, format, Format);
/**
* @brief Opcode.
*/
DECLARE_PROPERTY(MotStartStreamOpcode::E, opcode, Opcode);
private:
uint8_t* icw;
}; };
} // namespace frames } // namespace frames
} // namespace dfsi } // namespace dfsi

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "common/p25/dfsi/frames/MotStartVoiceFrame.h" #include "common/p25/dfsi/frames/MotStartVoiceFrame.h"
@ -30,11 +30,7 @@ using namespace p25::dfsi::frames;
MotStartVoiceFrame::MotStartVoiceFrame() : MotStartVoiceFrame::MotStartVoiceFrame() :
startOfStream(nullptr), startOfStream(nullptr),
fullRateVoice(nullptr), fullRateVoice(nullptr),
m_icw(ICWFlag::DIU), m_totalErrors(0U)
m_rssi(0U),
m_rssiValidity(RssiValidityFlag::INVALID),
m_nRssi(0U),
m_adjMM(0U)
{ {
startOfStream = new MotStartOfStream(); startOfStream = new MotStartOfStream();
fullRateVoice = new MotFullRateVoice(); fullRateVoice = new MotFullRateVoice();
@ -45,11 +41,7 @@ MotStartVoiceFrame::MotStartVoiceFrame() :
MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data) : MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data) :
startOfStream(nullptr), startOfStream(nullptr),
fullRateVoice(nullptr), fullRateVoice(nullptr),
m_icw(ICWFlag::DIU), m_totalErrors(0U)
m_rssi(0U),
m_rssiValidity(RssiValidityFlag::INVALID),
m_nRssi(0U),
m_adjMM(0U)
{ {
decode(data); decode(data);
} }
@ -76,9 +68,9 @@ bool MotStartVoiceFrame::decode(const uint8_t* data)
startOfStream = new MotStartOfStream(); startOfStream = new MotStartOfStream();
// create a buffer to decode the start record skipping the 10th byte (adjMM) // create a buffer to decode the start record skipping the 10th byte (adjMM)
uint8_t startBuffer[MotStartOfStream::LENGTH]; uint8_t startBuffer[DFSI_MOT_START_LEN];
::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN);
::memcpy(startBuffer, data, 9U); ::memcpy(startBuffer, data, DFSI_MOT_START_LEN);
// decode start of stream // decode start of stream
startOfStream->decode(startBuffer); startOfStream->decode(startBuffer);
@ -94,13 +86,6 @@ bool MotStartVoiceFrame::decode(const uint8_t* data)
::memcpy(voiceBuffer + 1U, data + 10U, MotFullRateVoice::SHORTENED_LENGTH - 1); ::memcpy(voiceBuffer + 1U, data + 10U, MotFullRateVoice::SHORTENED_LENGTH - 1);
fullRateVoice->decode(voiceBuffer, true); fullRateVoice->decode(voiceBuffer, true);
// get rest of data
m_icw = (ICWFlag::E)data[5U];
m_rssi = data[6U];
m_rssiValidity = (RssiValidityFlag::E)data[7U];
m_nRssi = data[8U];
m_adjMM = data[9U];
return true; return true;
} }
@ -114,11 +99,11 @@ void MotStartVoiceFrame::encode(uint8_t* data)
// encode start of stream - scope is intentional // encode start of stream - scope is intentional
{ {
uint8_t buffer[MotStartOfStream::LENGTH]; uint8_t buffer[DFSI_MOT_START_LEN];
startOfStream->encode(buffer); startOfStream->encode(buffer);
// copy to data array (skipping first and last bytes) // copy to data array (skipping first byte which is frame type)
::memcpy(data + 1U, buffer + 1U, MotStartOfStream::LENGTH - 2); ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U);
} }
// encode full rate voice - scope is intentional // encode full rate voice - scope is intentional
@ -129,11 +114,4 @@ void MotStartVoiceFrame::encode(uint8_t* data)
data[0U] = fullRateVoice->getFrameType(); data[0U] = fullRateVoice->getFrameType();
::memcpy(data + 10U, buffer + 1U, MotFullRateVoice::SHORTENED_LENGTH - 1); ::memcpy(data + 10U, buffer + 1U, MotFullRateVoice::SHORTENED_LENGTH - 1);
} }
// Copy the rest
data[5U] = m_icw;
data[6U] = m_rssi;
data[7U] = m_rssiValidity;
data[8U] = m_nRssi;
data[9U] = m_adjMM;
} }

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Patrick McDonnell, W3AXL * Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -36,21 +36,21 @@ namespace p25
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** /**
* @brief Implements a P25 Motorola voice frame 1/10 start. * @brief Implements a P25 Motorola/V.24 voice frame 1/10 start.
* \code{.unparsed} * \code{.unparsed}
* Byte 0 1 2 3 * Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Encoded Motorola Start of Stream | * | FT | Encoded V.24 Start of Stream |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | ICW Flag ? | RSSI | RSSI Valid | RSSI | * | | Full Rate Voice Frame |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Adj MM ? | Full Rate Voice Frame |
* +-+-+-+-+-+-+-+-+ +
* | | * | |
* + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | * | |
* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | * | |
* +=+=+=+=+=+=+=+=+ * +=+=+=+=+=+=+=+=+
* \endcode * \endcode
@ -90,25 +90,9 @@ namespace p25
MotFullRateVoice* fullRateVoice; // ?? - this should probably be private with getters/setters MotFullRateVoice* fullRateVoice; // ?? - this should probably be private with getters/setters
/** /**
* @brief * @brief Total errors detected in the frame.
*/
DECLARE_PROPERTY(ICWFlag::E, icw, ICW);
/**
* @brief RSSI Value.
*/
DECLARE_PROPERTY(uint8_t, rssi, RSSI);
/**
* @brief Flag indicating whether or not the RSSI field is valid.
*/
DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity);
/**
* @brief
*/
DECLARE_PROPERTY(uint8_t, nRssi, NRSSI);
/**
* @brief
*/ */
DECLARE_PROPERTY(uint8_t, adjMM, AdjMM); DECLARE_PROPERTY(uint8_t, totalErrors, TotalErrors);
}; };
} // namespace frames } // namespace frames
} // namespace dfsi } // namespace dfsi

@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/P25Defines.h"
#include "common/p25/dfsi/frames/MotTDULCFrame.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace p25::defines;
using namespace p25::dfsi;
using namespace p25::dfsi::defines;
using namespace p25::dfsi::frames;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the MotTDULCFrame class. */
MotTDULCFrame::MotTDULCFrame() :
startOfStream(nullptr),
tdulcData(nullptr)
{
tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES];
::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
startOfStream = new MotStartOfStream();
}
/* Initializes a instance of the MotTDULCFrame class. */
MotTDULCFrame::MotTDULCFrame(uint8_t* data) :
startOfStream(nullptr),
tdulcData(nullptr)
{
tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES];
::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
decode(data);
}
/* Finalizes a instance of the MotTDULCFrame class. */
MotTDULCFrame::~MotTDULCFrame()
{
if (startOfStream != nullptr)
delete startOfStream;
if (tdulcData != nullptr)
delete[] tdulcData;
}
/* Decode a TDULC frame. */
bool MotTDULCFrame::decode(const uint8_t* data)
{
assert(data != nullptr);
// create a new start of stream
if (startOfStream != nullptr)
delete startOfStream;
startOfStream = new MotStartOfStream();
// create a buffer to decode the start record
uint8_t startBuffer[DFSI_MOT_START_LEN];
::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN);
::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U);
// decode start of stream
startOfStream->decode(startBuffer);
uint8_t tdulcBuffer[9U];
::memcpy(tdulcBuffer, data + DFSI_MOT_START_LEN, 9U);
::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
tdulcData[0U] = (uint8_t)((tdulcBuffer[0U] >> 3) & 0x3FU);
tdulcData[1U] = (uint8_t)(((tdulcBuffer[0U] & 0x07U) << 3) | ((tdulcBuffer[1U] >> 4) & 0x07U));
tdulcData[2U] = (uint8_t)(((tdulcBuffer[1U] & 0x0FU) << 2) | ((tdulcBuffer[2U] >> 5) & 0x03U));
tdulcData[3U] = (uint8_t)(tdulcBuffer[2U] & 0x1FU);
tdulcData[4U] = (uint8_t)((tdulcBuffer[3U] >> 3) & 0x3FU);
tdulcData[5U] = (uint8_t)(((tdulcBuffer[3U] & 0x07U) << 3) | ((tdulcBuffer[4U] >> 4) & 0x07U));
tdulcData[6U] = (uint8_t)(((tdulcBuffer[4U] & 0x0FU) << 2) | ((tdulcBuffer[5U] >> 5) & 0x03U));
tdulcData[7U] = (uint8_t)(tdulcBuffer[5U] & 0x1FU);
tdulcData[8U] = (uint8_t)((tdulcBuffer[6U] >> 3) & 0x3FU);
tdulcData[9U] = (uint8_t)(((tdulcBuffer[6U] & 0x07U) << 3) | ((tdulcBuffer[7U] >> 4) & 0x07U));
tdulcData[10U] = (uint8_t)(((tdulcBuffer[7U] & 0x0FU) << 2) | ((tdulcBuffer[8U] >> 5) & 0x03U));
tdulcData[11U] = (uint8_t)(tdulcBuffer[8U] & 0x1FU);
return true;
}
/* Encode a TDULC frame. */
void MotTDULCFrame::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
// encode start of stream - scope is intentional
{
uint8_t buffer[DFSI_MOT_START_LEN];
startOfStream->encode(buffer);
// copy to data array
::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U);
}
// encode TDULC - scope is intentional
{
data[0U] = DFSIFrameType::MOT_TDULC;
data[DFSI_MOT_START_LEN + 1U] = (uint8_t)((tdulcData[0U] & 0x3FU) << 3) | ((tdulcData[1U] >> 3) & 0x07U);
data[DFSI_MOT_START_LEN + 2U] = (uint8_t)((tdulcData[1U] & 0x0FU) << 4) | ((tdulcData[2U] >> 2) & 0x0FU);
data[DFSI_MOT_START_LEN + 3U] = (uint8_t)((tdulcData[2U] & 0x03U)) | (tdulcData[3U] & 0x3FU);
data[DFSI_MOT_START_LEN + 4U] = (uint8_t)((tdulcData[4U] & 0x3FU) << 3) | ((tdulcData[5U] >> 3) & 0x07U);
data[DFSI_MOT_START_LEN + 5U] = (uint8_t)((tdulcData[5U] & 0x0FU) << 4) | ((tdulcData[6U] >> 2) & 0x0FU);
data[DFSI_MOT_START_LEN + 6U] = (uint8_t)((tdulcData[6U] & 0x03U)) | (tdulcData[7U] & 0x3FU);
data[DFSI_MOT_START_LEN + 7U] = (uint8_t)((tdulcData[8U] & 0x3FU) << 3) | ((tdulcData[9U] >> 3) & 0x07U);
data[DFSI_MOT_START_LEN + 8U] = (uint8_t)((tdulcData[9U] & 0x0FU) << 4) | ((tdulcData[10U] >> 2) & 0x0FU);
data[DFSI_MOT_START_LEN + 9U] = (uint8_t)((tdulcData[10U] & 0x03U)) | (tdulcData[11U] & 0x3FU);
data[DFSI_MOT_START_LEN + 11U] = DFSI_BUSY_BITS_IDLE;
}
}

@ -4,17 +4,17 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
* @file MotPDUFrame.h * @file MotTDULCFrame.h
* @ingroup dfsi_frames * @ingroup dfsi_frames
* @file MotPDUFrame.cpp * @file MotTDULCFrame.cpp
* @ingroup dfsi_frames * @ingroup dfsi_frames
*/ */
#if !defined(__MOT_PDU_FRAME_H__) #if !defined(__MOT_TDULC_FRAME_H__)
#define __MOT_PDU_FRAME_H__ #define __MOT_TDULC_FRAME_H__
#include "Defines.h" #include "Defines.h"
#include "common/Defines.h" #include "common/Defines.h"
@ -35,59 +35,57 @@ namespace p25
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** /**
* @brief Implements a P25 Motorola PDU frame. * @brief Implements a P25 Motorola/V.24 TDULC frame.
* \code{.unparsed} * \code{.unparsed}
* Byte 0 1 2 3 * Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Encoded Motorola Start of Stream | * | FT | Encoded V.24 Start of Stream |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved ? |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PDU Header |
* + +
* | | * | |
* + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | TDULC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | * | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode * \endcode
* @ingroup dfsi_frames * @ingroup dfsi_frames
*/ */
class HOST_SW_API MotPDUFrame { class HOST_SW_API MotTDULCFrame {
public: public:
static const uint8_t LENGTH = 20U;
/** /**
* @brief Initializes a copy instance of the MotPDUFrame class. * @brief Initializes a copy instance of the MotTDULCFrame class.
*/ */
MotPDUFrame(); MotTDULCFrame();
/** /**
* @brief Initializes a copy instance of the MotPDUFrame class. * @brief Initializes a copy instance of the MotTDULCFrame class.
* @param data Buffer to containing MotPDUFrame to decode. * @param data Buffer to containing MotTDULCFrame to decode.
*/ */
MotPDUFrame(uint8_t* data); MotTDULCFrame(uint8_t* data);
/** /**
* @brief Finalizes a instance of the MotPDUFrame class. * @brief Finalizes a instance of the MotTDULCFrame class.
*/ */
~MotPDUFrame(); ~MotTDULCFrame();
/** /**
* @brief Decode a PDU frame. (only the PDU data header...) * @brief Decode a TDULC frame.
* @param[in] data Buffer to containing MotPDUFrame to decode. * @param[in] data Buffer to containing MotTDULCFrame to decode.
*/ */
bool decode(const uint8_t* data); bool decode(const uint8_t* data);
/** /**
* @brief Encode a PDU frame. (only the PDU data header...) * @brief Encode a TDULC frame.
* @param[out] data Buffer to encode a MotPDUFrame. * @param[out] data Buffer to encode a MotTDULCFrame.
*/ */
void encode(uint8_t* data); void encode(uint8_t* data);
public: public:
MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters
uint8_t* pduHeaderData; // ?? - this should probably be private with getters/setters uint8_t* tdulcData; // ?? - this should probably be private with getters/setters
}; };
} // namespace frames } // namespace frames
} // namespace dfsi } // namespace dfsi
} // namespace p25 } // namespace p25
#endif // __MOT_PDU_FRAME_H__ #endif // __MOT_TDULC_FRAME_H__

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
#include "common/p25/P25Defines.h" #include "common/p25/P25Defines.h"
@ -71,15 +71,15 @@ bool MotTSBKFrame::decode(const uint8_t* data)
delete startOfStream; delete startOfStream;
startOfStream = new MotStartOfStream(); startOfStream = new MotStartOfStream();
// create a buffer to decode the start record skipping the 10th byte (adjMM) // create a buffer to decode the start record
uint8_t startBuffer[MotStartOfStream::LENGTH]; uint8_t startBuffer[DFSI_MOT_START_LEN];
::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN);
::memcpy(startBuffer + 1U, data, 4U); ::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U);
// decode start of stream // decode start of stream
startOfStream->decode(startBuffer); startOfStream->decode(startBuffer);
::memcpy(tsbkData, data + 9U, P25_TSBK_LENGTH_BYTES); ::memcpy(tsbkData, data + DFSI_MOT_START_LEN, P25_TSBK_LENGTH_BYTES);
return true; return true;
} }
@ -93,16 +93,16 @@ void MotTSBKFrame::encode(uint8_t* data)
// encode start of stream - scope is intentional // encode start of stream - scope is intentional
{ {
uint8_t buffer[MotStartOfStream::LENGTH]; uint8_t buffer[DFSI_MOT_START_LEN];
startOfStream->encode(buffer); startOfStream->encode(buffer);
// copy to data array (skipping first and last bytes) // copy to data array
::memcpy(data + 1U, buffer + 1U, 4U); ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U);
} }
// encode TSBK - scope is intentional // encode TSBK - scope is intentional
{ {
data[0U] = DFSIFrameType::TSBK; data[0U] = DFSIFrameType::MOT_TSBK;
::memcpy(data + 9U, tsbkData, P25_TSBK_LENGTH_BYTES); ::memcpy(data + DFSI_MOT_START_LEN, tsbkData, P25_TSBK_LENGTH_BYTES);
} }
} }

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms. * GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* *
*/ */
/** /**
@ -35,30 +35,28 @@ namespace p25
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** /**
* @brief Implements a P25 Motorola TSBK frame. * @brief Implements a P25 Motorola/V.24 TSBK frame.
* \code{.unparsed} * \code{.unparsed}
* Byte 0 1 2 3 * Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Encoded Motorola Start of Stream | * | FT | Encoded V.24 Start of Stream |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved ? |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | TSBK |
* + +
* | | * | |
* + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | TSBK |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | * | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Unknown ? | * | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode * \endcode
* @ingroup dfsi_frames * @ingroup dfsi_frames
*/ */
class HOST_SW_API MotTSBKFrame { class HOST_SW_API MotTSBKFrame {
public: public:
static const uint8_t LENGTH = 24U;
/** /**
* @brief Initializes a copy instance of the MotTSBKFrame class. * @brief Initializes a copy instance of the MotTSBKFrame class.
*/ */

@ -1,129 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/dfsi/frames/MotVoiceHeader1.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace p25::dfsi;
using namespace p25::dfsi::defines;
using namespace p25::dfsi::frames;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the MotVoiceHeader1 class. */
MotVoiceHeader1::MotVoiceHeader1() :
header(nullptr),
startOfStream(nullptr),
m_icw(ICWFlag::DIU),
m_rssi(0U),
m_rssiValidity(RssiValidityFlag::INVALID),
m_nRssi(0U)
{
startOfStream = new MotStartOfStream();
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
}
/* Initializes a instance of the MotVoiceHeader1 class. */
MotVoiceHeader1::MotVoiceHeader1(uint8_t* data) :
header(nullptr),
startOfStream(nullptr),
m_icw(ICWFlag::DIU),
m_rssi(0U),
m_rssiValidity(RssiValidityFlag::INVALID),
m_nRssi(0U)
{
decode(data);
}
/* Finalizes a instance of the MotVoiceHeader1 class. */
MotVoiceHeader1::~MotVoiceHeader1()
{
if (startOfStream != nullptr)
delete startOfStream;
if (header != nullptr)
delete[] header;
}
/* Decode a voice header 1 frame. */
bool MotVoiceHeader1::decode(const uint8_t* data)
{
assert(data != nullptr);
// create a start of stream
if (startOfStream != nullptr)
delete startOfStream;
startOfStream = new MotStartOfStream();
uint8_t buffer[MotStartOfStream::LENGTH];
::memset(buffer, 0x00U, MotStartOfStream::LENGTH);
// we copy the bytes from [1:4]
::memcpy(buffer + 1U, data + 1U, 4);
startOfStream->decode(buffer);
// decode the other stuff
m_icw = (ICWFlag::E)data[5U];
m_rssi = data[6U];
m_rssiValidity = (RssiValidityFlag::E)data[7U];
m_nRssi = data[8U];
// our header includes the trailing source and check bytes
if (header != nullptr)
delete[] header;
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
::memcpy(header, data + 9U, HCW_LENGTH);
return true;
}
/* Encode a voice header 1 frame. */
void MotVoiceHeader1::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
data[0U] = DFSIFrameType::MOT_VHDR_1;
// scope is intentional
{
uint8_t buffer[MotStartOfStream::LENGTH];
::memset(buffer, 0x00U, MotStartOfStream::LENGTH);
startOfStream->encode(buffer);
// copy the 4 start record bytes from the start of stream frame
::memcpy(data + 1U, buffer + 1U, 4U);
}
data[5U] = m_icw;
data[6U] = m_rssi;
data[7U] = m_rssiValidity;
data[8U] = m_nRssi;
// our header includes the trailing source and check bytes
if (header != nullptr) {
::memcpy(data + 9U, header, HCW_LENGTH);
}
}

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

Loading…
Cancel
Save

Powered by TurnKey Linux.