Merge branch 'master' into incall_ctrl

pull/86/head
Bryan Biedenkapp 12 months ago
commit da5cb6eb7d

@ -543,7 +543,7 @@ add_custom_target(old_install
COMMAND install -m 644 ../configs/rid_acl.example.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/rid_acl.dat
COMMAND install -m 644 ../configs/talkgroup_rules.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/talkgroup_rules.example.yml
COMMAND install -m 644 ../configs/bridge-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/bridge-config.example.yml
COMMAND install -m 644 ../configs/talkgroup_rules.yaml_schema.json ${CMAKE_LEGACY_INSTALL_PREFIX}/schema/talkgroup_rules.yaml_schema.json
COMMAND install -m 644 ../configs/schema/talkgroup_rules.yaml_schema.json ${CMAKE_LEGACY_INSTALL_PREFIX}/schema/talkgroup_rules.yaml_schema.json
COMMAND install -m 755 ../tools/start-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX}
COMMAND install -m 755 ../tools/stop-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX}
COMMAND install -m 755 ../tools/dvm-watchdog.sh ${CMAKE_LEGACY_INSTALL_PREFIX}

@ -154,6 +154,21 @@ The following setups assume the host is compiled with the setup TUI mode (if ava
- Unusually high BER >10% and other various receive problems may be due to the radio/hotspot being off frequency and requiring some adjustment. Even a slight frequency drift can be catastrophic for proper digital modulation. The recommendation is to ensure the interfaced radio does not have an overall reference frequency drift > +/- 150hz. An unusually high BER can also be explained by DC level offsets in the signal paths, or issues with the FM deviation levels on the interfaced radio being too high or too low.
- For hotspot operation, it may be necessary to enable/disable the AFC (automatic frequency correction) or change the gain mode. Both of these options can be altered using the setup TUI or directly in the `config.yml` file. In some cases when operating in trunking mode, for example, it may be necessary to change the orientation of the transmit antenna by using a 90 degree adapter as well as changing the gain mode to "Low" to prevent Rx desense.
### (Hotspot) Calibration Steps (using a capable service monitor)
1. Zero any frequency offsets, both Rx and Tx. Ensure the Tx Deviation level is nominal (50).
2. Using spectrum analyzer mode, and using "z" or "P25 1200 Hz Tone Mode", and begin transmitting. You want to adjust the transmit deviation (T/t) and zero null the center carrier on the spectra seen on the spectrum analyzer (you should maintain the side lobes to the left and right of the center) as much as possible and maintain a clean 1200hz sine tone.
3. Switch to a mode on your service monitor where you can observe the *analog* FM deviation (if you have low and high pass settings like on an HP monitor, set 50hz Low Pass and 3khz High Pass), use "P" or "P25 1011 Hz Test Pattern" and begin transmitting. Observe the FM deviation, you want to adjust the transmit deviation to get the average deviation as close to 2.83khz as possible (a little high is okay, 2.9khz or so will increase BER but it will still be acceptable).
4. Switch to a mode on your service monitor where you can observe the frequency error, its best to view this error in Hz if possible, like step 3, use "P" or "P25 1011 Hz Test Pattern" and begin transmitting. Note the average frequency error. Use the Tx Frequency Adjustment accordingly to set an adjustment. (For example, if the observed frequency error is +200hz from center, you want to enter a -200hz adjustment in calibration/setup.)
### (Hotspot) Calibration Notes
- The Rx Frequency adjustment usually follows the Tx Frequency Adjustment, so if you've set a -200hz adjustment for Tx the Rx Frequency Adjustment should be -200hz or around -200hz.
- After calibration use a digital capable radio, with a front panel BER test or a radio with Tuner software capable of BER test. Evaluate Rx and Tx BER. Make fine adjustments if necessary to dial in BER.
- Calibrating a hotspot without a service monitor is possible, however a bit more tricky and mostly trial and error. The steps are essentially the same as the steps above, with the cavet being *step 2 should be skipped* and a radio capable of measuring Rx/Tx BER using
either front panel controls or tuning software is mandatory. In steps 3 and 4 while observing the BER via whatever available means, you want to essentially vary transmit deviation and frequency offset to find the lowest possible BER. (A very stable SDR *might* be usable
for step 4 to observe frequency error.)
## dvmfne Configuration
This source repository contains configuration example files within the configs folder, please review `fne-config.example.yml` for the `dvmfne` for details on various configurable options. When first setting up a FNE instance, it is important to properly configure a `talkgroup_rules.example.yml` file, this file defines all the various rules for valid talkgroups and other settings.

@ -58,7 +58,7 @@ network:
# Enable PCM audio over UDP.
udpAudio: false
# Enable meta data such as dstId and srcId in the UDP data
# Enable meta data such as dstId and srcId in the UDP data.
udpMetadata: false
# PCM over UDP send port.
udpSendPort: 34001
@ -68,6 +68,11 @@ network:
udpReceivePort: 32001
# PCM over UDP receive address.
udpReceiveAddress: "127.0.0.1"
# Flag indicating UDP audio should be encoded using G.711 uLaw.
udpUseULaw: false
# Flag indicating UDP audio should be transmitted without the length leader.
# NOTE: This flag is only applicable when encoding G.711 uLaw.
udpNoIncludeLength: false
# Source "Radio ID" for transmitted audio frames.
sourceId: 1234567

@ -590,6 +590,14 @@ system:
jitter: 200
# Timer which will reset local/remote call flags if frames aren't received longer than this time in ms
callTimeout: 200
# Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection.
fsc: false
# Sets the heartbeat interval for the FSC connection.
fscHeartbeat: 5
# Flag indicating when operating in V.24 UDP mode, this instance should initiate the FSC protocol handshake.
initiator: false
# Flag indicating DFSI frames should be formatted in TIA-102 standard instead of serial standard format.
dfsiTIAMode: false
# Sets received the signal offset from DC.
rxDCOffset: 0 # Valid values between -128 and 128
@ -637,6 +645,8 @@ system:
# Full path to the RID ACL file.
file: rid_acl.dat
# Amount of time between updates of RID ACL file. (minutes)
# NOTE: If utilizing purely FNE pushed RID ACL rules, this update time should be set to 0 to prevent
# FNE rules from becoming erased.
time: 2
# Flag indicating whether or not RID ACLs are enforced.
acl: false
@ -648,6 +658,8 @@ system:
# Full path to the talkgroup rules file.
file: talkgroup_rules.yml
# Amount of time between updates of talkgroup rules file. (minutes)
# NOTE: If utilizing purely FNE pushed talkgroup rules, this update time should be set to 0 to prevent
# FNE rules from becoming erased.
time: 2
# Flag indicating whether or not TGID ACLs are enforced.
acl: false

@ -95,12 +95,16 @@ master:
# Flag indicating whether or not unknown/undefined RIDs will be rejected by the FNE.
# (This is a strict rejection, any unknown or undefined RID not in the RID ACL list will be hard rejected.)
rejectUnknownRID: false
# Flag indicating whether or not a TDULC call terminations will pass to any peers.
disallowCallTerm: false
# Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID).
filterHeaders: true
# Flag indicating that terminators will be filtered by destination ID (i.e. valid RID or valid TGID).
filterTerminators: true
# Flag indicating the FNE will drop all inbound Unit-to-Unit calls.
disallowAllUnitToUnit: false
# List of peers that unit to unit calls are dropped for.
dropUnitToUnit: []

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

@ -37,23 +37,6 @@
"moduleLoad": true,
"trace": true
}
},
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": "Debug Monitor",
"type": "cppdbg",
"request": "launch",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/build/dvmmon",
"args": ["-c", "./monitor-config.yml"],
"cwd": "${workspaceFolder}/build",
"stopAtEntry": false,
"logging": {
"moduleLoad": true,
"trace": true
}
},
}
]
}

@ -92,7 +92,7 @@ void fatal(const char* msg, ...)
void usage(const char* message, const char* arg)
{
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (message != nullptr) {
::fprintf(stderr, "%s: ", g_progExe.c_str());
@ -209,7 +209,7 @@ int checkArgs(int argc, char* argv[])
#endif
else if (IS("-v")) {
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (argc == 2)
exit(EXIT_SUCCESS);

@ -55,6 +55,18 @@ const int NUMBER_OF_BUFFERS = 32;
#define LOCAL_CALL "Local Traffic"
#define UDP_CALL "UDP Traffic"
#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
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
@ -135,6 +147,128 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit
}
}
/* */
static short search(short val, short* table, short size)
{
for (short i = 0; i < size; i++) {
if (val <= *table++)
return (i);
}
return (size);
}
/* Helper to convert PCM into G.711 aLaw. */
uint8_t pcmToaLaw(short pcm)
{
short mask;
unsigned char aval;
pcm = pcm >> 3;
if (pcm >= 0) {
mask = 0xD5; // sign (7th) bit = 1
} else {
mask = 0x55; // 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 aLawToPCM(uint8_t alaw)
{
alaw ^= 0x55;
short t = (alaw & QUANT_MASK) << 4;
short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT;
switch (seg) {
case 0:
t += 8;
break;
case 1:
t += 0x108;
break;
default:
t += 0x108;
t <<= seg - 1;
}
return ((alaw & SIGN_BIT) ? t : -t);
}
/* Helper to convert PCM into G.711 uLaw. */
uint8_t pcmTouLaw(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 uLaw into PCM. */
short uLawToPCM(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));
}
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
@ -152,6 +286,8 @@ HostBridge::HostBridge(const std::string& confFile) :
m_udpSendAddress("127.0.0.1"),
m_udpReceivePort(32001),
m_udpReceiveAddress("127.0.0.1"),
m_udpNoIncludeLength(false),
m_udpUseULaw(false),
m_srcId(p25::defines::WUID_FNE),
m_srcIdOverride(0U),
m_overrideSrcIdFromMDC(false),
@ -318,7 +454,7 @@ int HostBridge::run()
#endif // !defined(_WIN32)
::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \
"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \
">> Audio Bridge\r\n");
@ -867,6 +1003,13 @@ bool HostBridge::createNetwork()
m_udpSendAddress = networkConf["udpSendAddress"].as<std::string>();
m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as<uint32_t>(34001);
m_udpReceiveAddress = networkConf["udpReceiveAddress"].as<std::string>();
m_udpUseULaw = networkConf["udpUseULaw"].as<bool>(false);
if (m_udpUseULaw)
m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as<bool>(false);
if (m_udpUseULaw && m_udpMetadata)
m_udpMetadata = false; // metadata isn't supported when encoding uLaw
m_srcId = (uint32_t)networkConf["sourceId"].as<uint32_t>(p25::defines::WUID_FNE);
m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as<bool>(false);
@ -925,10 +1068,14 @@ bool HostBridge::createNetwork()
LogInfo(" PCM over UDP Audio: %s", m_udpAudio ? "yes" : "no");
if (m_udpAudio) {
LogInfo(" UDP Audio Metadata: %s", m_udpMetadata ? "yes" : "no");
LogInfo(" UDP Audio end Address: %s", m_udpSendAddress.c_str());
LogInfo(" UDP Audio Send Address: %s", m_udpSendAddress.c_str());
LogInfo(" UDP Audio Send Port: %u", m_udpSendPort);
LogInfo(" UDP Audio Receive Address: %s", m_udpReceiveAddress.c_str());
LogInfo(" UDP Audio Receive Port: %u", m_udpReceivePort);
LogInfo(" UDP Audio Use uLaw Encoding: %u", m_udpUseULaw ? "yes" : "no");
if (m_udpUseULaw) {
LogInfo(" UDP Audio No Length Header: %u", m_udpNoIncludeLength ? "yes" : "no");
}
}
LogInfo(" Source ID: %u", m_srcId);
@ -1004,12 +1151,22 @@ void HostBridge::processUDPAudio()
if (m_debug)
Utils::dump(1U, "UDP Audio Network Packet", buffer, length);
uint32_t pcmLength = __GET_UINT32(buffer, 0U);
uint32_t pcmLength = 0;
if (m_udpNoIncludeLength) {
pcmLength = length;
} else {
pcmLength = __GET_UINT32(buffer, 0U);
}
UInt8Array __pcm = std::make_unique<uint8_t[]>(pcmLength);
uint8_t* pcm = __pcm.get();
::memcpy(pcm, buffer + 4U, pcmLength);
if (m_udpNoIncludeLength) {
::memcpy(pcm, buffer, pcmLength);
}
else {
::memcpy(pcm, buffer + 4U, pcmLength);
}
// Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength);
@ -1025,9 +1182,17 @@ void HostBridge::processUDPAudio()
int smpIdx = 0;
short samples[MBE_SAMPLES_LENGTH];
for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) {
samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]);
smpIdx++;
if (m_udpUseULaw) {
for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx++) {
samples[smpIdx] = uLawToPCM(pcm[pcmIdx]);
smpIdx++;
}
}
else {
for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) {
samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]);
smpIdx++;
}
}
m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH);
@ -1307,18 +1472,37 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst
if (m_udpAudio) {
int pcmIdx = 0;
uint8_t pcm[MBE_SAMPLES_LENGTH * 2U];
for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) {
pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF);
pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF);
pcmIdx += 2;
if (m_udpUseULaw) {
for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) {
pcm[smpIdx] = pcmTouLaw(samples[smpIdx]);
}
}
else {
for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) {
pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF);
pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF);
pcmIdx += 2;
}
}
uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U;
uint8_t* audioData = nullptr;
if (!m_udpMetadata) {
audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length)
__SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U);
if (m_udpUseULaw) {
length = (MBE_SAMPLES_LENGTH) + 4U;
if (m_udpNoIncludeLength) {
length = MBE_SAMPLES_LENGTH;
::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH);
} else {
__SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH);
}
}
else {
__SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U);
}
}
else {
length = (MBE_SAMPLES_LENGTH * 2U) + 12U;
@ -1854,18 +2038,37 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI
if (m_udpAudio) {
int pcmIdx = 0;
uint8_t pcm[MBE_SAMPLES_LENGTH * 2U];
for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) {
pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF);
pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF);
pcmIdx += 2;
if (m_udpUseULaw) {
for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) {
pcm[smpIdx] = pcmTouLaw(samples[smpIdx]);
}
}
else {
for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) {
pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF);
pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF);
pcmIdx += 2;
}
}
uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U;
uint8_t* audioData = nullptr;
if (!m_udpMetadata) {
audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length)
__SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U);
if (m_udpUseULaw) {
length = (MBE_SAMPLES_LENGTH) + 4U;
if (m_udpNoIncludeLength) {
length = MBE_SAMPLES_LENGTH;
::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH);
} else {
__SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH);
}
}
else {
__SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U);
}
}
else {
length = (MBE_SAMPLES_LENGTH * 2U) + 12U;

@ -91,6 +91,31 @@ 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,
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 pcmToaLaw(short pcm);
/**
* @brief Helper to convert G.711 aLaw into PCM.
* @param alaw aLaw value.
* @return short PCM value.
*/
short aLawToPCM(uint8_t alaw);
/**
* @brief Helper to convert PCM into G.711 uLaw.
* @param pcm PCM value.
* @return uint8_t uLaw value.
*/
uint8_t pcmTouLaw(short pcm);
/**
* @brief Helper to convert G.711 uLaw into PCM.
* @param ulaw uLaw value.
* @return short PCM value.
*/
short uLawToPCM(uint8_t ulaw);
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
@ -134,6 +159,8 @@ private:
std::string m_udpSendAddress;
uint16_t m_udpReceivePort;
std::string m_udpReceiveAddress;
bool m_udpNoIncludeLength;
bool m_udpUseULaw;
uint32_t m_srcId;
uint32_t m_srcIdOverride;

@ -63,6 +63,8 @@ bool Thread::run()
void Thread::wait()
{
if (!m_started)
return;
#if defined(_WIN32)
::WaitForSingleObject(m_thread, INFINITE);
::CloseHandle(m_thread);

@ -35,12 +35,12 @@ namespace edac
/**
* @brief Decode Golay (23,12,7) FEC.
* @param code
* @returns uint8_t Number of errors detected.
* @returns uint32_t Data decoded with Golay FEC
*/
static uint32_t decode23127(uint32_t code);
/**
* @brief Decode Golay (24,12,8) FEC.
* @param code
* @param code
* @param out
* @returns bool
*/

@ -20,6 +20,12 @@ using namespace lookups;
const uint32_t UNIT_REG_TIMEOUT = 43200U; // 12 hours
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::mutex AffiliationLookup::m_mutex;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
@ -297,6 +303,7 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi
return false;
}
std::lock_guard<std::mutex> lock(m_mutex);
uint32_t chNo = m_chLookup->getFirstRFChannel();
m_chLookup->removeRFCh(chNo);
@ -326,6 +333,7 @@ void AffiliationLookup::touchGrant(uint32_t dstId)
return;
}
std::lock_guard<std::mutex> lock(m_mutex);
if (isGranted(dstId)) {
m_grantTimers[dstId].start();
}
@ -333,12 +341,15 @@ void AffiliationLookup::touchGrant(uint32_t dstId)
/* Helper to release the channel grant for the destination ID. */
bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock)
{
if (dstId == 0U && !releaseAll) {
return false;
}
if (!noLock)
m_mutex.lock();
// are we trying to release all grants?
if (dstId == 0U && releaseAll) {
LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str());
@ -354,6 +365,8 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
releaseGrant(dstId, false);
}
if (!noLock)
m_mutex.unlock();
return true;
}
@ -383,9 +396,14 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
}
m_grantTimers[dstId].stop();
if (!noLock)
m_mutex.unlock();
return true;
}
if (!noLock)
m_mutex.unlock();
return false;
}
@ -527,6 +545,8 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId)
void AffiliationLookup::clock(uint32_t ms)
{
std::lock_guard<std::mutex> lock(m_mutex);
// clock all the grant timers
std::vector<uint32_t> gntsToRel = std::vector<uint32_t>();
for (auto entry : m_grantChTable) {
@ -540,7 +560,7 @@ void AffiliationLookup::clock(uint32_t ms)
// release grants that have timed out
for (uint32_t dstId : gntsToRel) {
releaseGrant(dstId, false);
releaseGrant(dstId, false, true);
}
if (!m_disableUnitRegTimeout) {

@ -29,6 +29,7 @@
#include <algorithm>
#include <vector>
#include <functional>
#include <mutex>
namespace lookups
{
@ -182,9 +183,10 @@ namespace lookups
* @brief Helper to release the channel grant for the destination ID.
* @param dstId Destination Address.
* @param releaseAll Flag indicating all channel grants should be released.
* @param noLock Flag indicating no mutex lock operation should be performed while releasing.
* @returns bool True, if channel grant was released, otherwise false.
*/
virtual bool releaseGrant(uint32_t dstId, bool releaseAll);
virtual bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false);
/**
* @brief Helper to determine if the channel number is busy.
* @param chNo Channel Number.
@ -298,6 +300,8 @@ namespace lookups
bool m_disableUnitRegTimeout;
bool m_verbose;
static std::mutex m_mutex;
};
} // namespace lookups

@ -178,6 +178,12 @@ namespace lookups
*/
void filename(std::string filename) { m_filename = 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 = 0U; }
protected:
std::string m_filename;
uint32_t m_reloadTime;

@ -46,16 +46,16 @@ void PeerListLookup::clear()
/* Adds a new entry to the list. */
void PeerListLookup::addEntry(uint32_t id, const std::string& password, bool peerLink)
void PeerListLookup::addEntry(uint32_t id, const std::string& alias, const std::string& password, bool peerLink)
{
PeerId entry = PeerId(id, password, peerLink, false);
PeerId entry = PeerId(id, alias, password, peerLink, false);
std::lock_guard<std::mutex> lock(m_mutex);
try {
PeerId _entry = m_table.at(id);
// if either the alias or the enabled flag doesn't match, update the entry
if (_entry.peerId() == id) {
_entry = PeerId(id, password, peerLink, false);
_entry = PeerId(id, alias, password, peerLink, false);
m_table[id] = _entry;
}
} catch (...) {
@ -87,7 +87,7 @@ PeerId PeerListLookup::find(uint32_t id)
try {
entry = m_table.at(id);
} catch (...) {
entry = PeerId(0U, "", false, true);
entry = PeerId(0U, "", "", false, true);
}
return entry;
@ -201,22 +201,29 @@ bool PeerListLookup::load()
// parse tokenized line
uint32_t id = ::atoi(parsed[0].c_str());
// Parse optional alias field (at end of line to avoid breaking change with existing lists)
std::string alias = "";
if (parsed.size() >= 4)
alias = parsed[3].c_str();
// Parse peer link flag
bool peerLink = false;
if (parsed.size() >= 3)
peerLink = ::atoi(parsed[2].c_str()) == 1;
// Check for an optional alias field
if (parsed.size() >= 2) {
if (!parsed[1].empty()) {
m_table[id] = PeerId(id, parsed[1], peerLink, false);
LogDebug(LOG_HOST, "Loaded peer ID %u into peer ID lookup table, using unique peer password%s", id,
(peerLink) ? ", Peer-Link Enabled" : "");
continue;
}
}
// Parse optional password
std::string password = "";
if (parsed.size() >= 2)
password = parsed[1].c_str();
m_table[id] = PeerId(id, "", peerLink, false);
LogDebug(LOG_HOST, "Loaded peer ID %u into peer ID lookup table, using master password%s", id,
// Load into table
m_table[id] = PeerId(id, alias, password, peerLink, false);
// Log depending on what was loaded
LogDebug(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s", id,
(!alias.empty() ? (" (" + alias + ")").c_str() : ""),
(!password.empty() ? "using unique peer password" : "using master password"),
(peerLink) ? ", Peer-Link Enabled" : "");
}
}
@ -258,20 +265,27 @@ bool PeerListLookup::save()
for (auto& entry: m_table) {
// Get the parameters
uint32_t peerId = entry.first;
std::string alias = entry.second.peerAlias();
std::string password = entry.second.peerPassword();
// Format into a string
line = std::to_string(peerId) + ",";
// Add the alias if we have one
// Add the password if we have one
if (password.length() > 0) {
line += password;
line += ",";
}
line += ",";
// Add peerLink flag
bool peerLink = entry.second.peerLink();
if (peerLink) {
line += "1,";
} else {
line += "0,";
}
// Add alias if we have one
if (alias.length() > 0) {
line += alias;
line += ",";
}
// Add the newline
line += "\n";
// Write to file

@ -47,6 +47,7 @@ namespace lookups
*/
PeerId() :
m_peerId(0U),
m_peerAlias(),
m_peerPassword(),
m_peerLink(false),
m_peerDefault(false)
@ -56,12 +57,14 @@ namespace lookups
/**
* @brief Initializes a new instance of the PeerId class.
* @param peerId Peer ID.
* @param peerAlias Peer alias
* @param peerPassword Per Peer Password.
* @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration.
* @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer.
*/
PeerId(uint32_t peerId, const std::string& peerPassword, bool peerLink, bool peerDefault) :
PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool peerDefault) :
m_peerId(peerId),
m_peerAlias(peerAlias),
m_peerPassword(peerPassword),
m_peerLink(peerLink),
m_peerDefault(peerDefault)
@ -77,6 +80,7 @@ namespace lookups
{
if (this != &data) {
m_peerId = data.m_peerId;
m_peerAlias = data.m_peerAlias;
m_peerPassword = data.m_peerPassword;
m_peerLink = data.m_peerLink;
m_peerDefault = data.m_peerDefault;
@ -88,13 +92,15 @@ namespace lookups
/**
* @brief Sets flag values.
* @param peerId Peer ID.
* @param peerAlias Peer Alias
* @param peerPassword Per Peer Password.
* @param sendConfiguration Flag indicating this peer participates in peer link and should be sent configuration.
* @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer.
*/
void set(uint32_t peerId, const std::string& peerPassword, bool peerLink, bool peerDefault)
void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool peerDefault)
{
m_peerId = peerId;
m_peerAlias = peerAlias;
m_peerPassword = peerPassword;
m_peerLink = peerLink;
m_peerDefault = peerDefault;
@ -105,6 +111,10 @@ namespace lookups
* @brief Peer ID.
*/
__READONLY_PROPERTY_PLAIN(uint32_t, peerId);
/**
* @breif Peer Alias
*/
__READONLY_PROPERTY_PLAIN(std::string, peerAlias);
/**
* @brief Per Peer Password.
*/
@ -157,7 +167,7 @@ namespace lookups
* @param password Per Peer Password.
* @param peerLink Flag indicating this peer will participate in peer link and should be sent configuration.
*/
void addEntry(uint32_t id, const std::string& password = "", bool peerLink = false);
void addEntry(uint32_t id, const std::string& alias = "", const std::string& password = "", bool peerLink = false);
/**
* @brief Removes an existing entry from the list.
* @param peerId Unique peer ID to remove.

@ -628,6 +628,12 @@ namespace lookups
*/
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 = 0U; }
private:
std::string m_rulesFile;
uint32_t m_reloadTime;

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX
* Copyright (C) 2020-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2020-2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024 Caleb, KO4UYJ
*
*/
@ -572,6 +572,26 @@ bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data)
return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_p25StreamId);
}
/* Writes P25 TDULC frame data to the network. */
bool BaseNetwork::writeP25TDULC(const p25::lc::LC& control, const uint8_t* data)
{
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false;
if (m_p25StreamId == 0U) {
m_p25StreamId = createStreamId();
}
uint32_t messageLength = 0U;
UInt8Array message = createP25_TDULCMessage(messageLength, control, data);
if (message == nullptr) {
return false;
}
return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_p25StreamId);
}
/* Writes P25 PDU frame data to the network. */
bool BaseNetwork::writeP25PDU(const p25::data::DataHeader& header, const uint8_t currentBlock, const uint8_t* data,
@ -1025,6 +1045,35 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L
return UInt8Array(buffer);
}
/* Creates an P25 TDULC frame message. */
UInt8Array BaseNetwork::createP25_TDULCMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data)
{
using namespace p25::defines;
assert(data != nullptr);
uint8_t* buffer = new uint8_t[P25_TDULC_PACKET_LENGTH + PACKET_PAD];
::memset(buffer, 0x00U, P25_TDULC_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header
p25::data::LowSpeedData lsd = p25::data::LowSpeedData();
createP25_MessageHdr(buffer, DUID::TDULC, control, lsd, FrameType::TERMINATOR);
// pack raw P25 TSDU bytes
uint32_t count = MSG_HDR_SIZE;
::memcpy(buffer + 24U, data, P25_TDULC_FRAME_LENGTH_BYTES);
count += P25_TDULC_FRAME_LENGTH_BYTES;
buffer[23U] = count;
if (m_debug)
Utils::dump(1U, "Network Message, P25 TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD));
length = (P25_TDULC_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer);
}
/* Writes P25 PDU frame data to the network. */
UInt8Array BaseNetwork::createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header,

@ -5,7 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX
* Copyright (C) 2020-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2020-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -84,6 +84,7 @@ namespace network
const uint32_t P25_LDU1_PACKET_LENGTH = 193U; // 24 byte header + DFSI data + 1 byte frame type + 12 byte enc sync
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_TDULC_PACKET_LENGTH = 78U; // 24 byte header + TDULC data
/**
* @brief Network Peer Connection Status
@ -381,6 +382,13 @@ namespace network
* @returns bool True, if message was sent, otherwise false.
*/
virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data);
/**
* @brief Writes P25 TDULC frame data to the network.
* @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] data Buffer containing P25 TDULC data to send.
* @returns bool True, if message was sent, otherwise false.
*/
virtual bool writeP25TDULC(const p25::lc::LC& control, const uint8_t* data);
/**
* @brief Writes P25 PDU frame data to the network.
* @param[in] dataHeader Instance of p25::data::DataHeader containing PDU header data.
@ -634,6 +642,19 @@ namespace network
*/
UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data);
/**
* @brief Creates an P25 TDULC frame message.
*
* The data packed into a P25 TDULC frame message is essentially just a message header with the FEC encoded
* raw TDULC data.
*
* @param[out] length Length of network message buffer.
* @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] data Buffer containing P25 TDULC data to send.
* @returns UInt8Array Buffer containing the built network message.
*/
UInt8Array createP25_TDULCMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data);
/**
* @brief Creates an P25 PDU frame message.
* \code{.unparsed}

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@ -23,6 +23,12 @@ using namespace network::frame;
#include <cassert>
#include <cstring>
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::mutex FrameQueue::m_fqTimestampLock;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
@ -170,6 +176,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_
void FrameQueue::clearTimestamps()
{
std::lock_guard<std::mutex> lock(m_fqTimestampLock);
m_streamTimestamps.clear();
}
@ -187,6 +194,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui
uint32_t timestamp = INVALID_TS;
if (streamId != 0U) {
std::lock_guard<std::mutex> lock(m_fqTimestampLock);
auto entry = m_streamTimestamps.find(streamId);
if (entry != m_streamTimestamps.end()) {
timestamp = entry->second;
@ -221,6 +229,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui
}
if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) {
std::lock_guard<std::mutex> lock(m_fqTimestampLock);
auto entry = m_streamTimestamps.find(streamId);
if (entry != m_streamTimestamps.end()) {
if (m_debug)

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -21,6 +21,7 @@
#include "common/network/RTPFNEHeader.h"
#include "common/network/RawFrameQueue.h"
#include <mutex>
#include <unordered_map>
namespace network
@ -116,6 +117,7 @@ namespace network
private:
uint32_t m_peerId;
std::unordered_map<uint32_t, uint32_t> m_streamTimestamps;
static std::mutex m_fqTimestampLock;
/**
* @brief Generate RTP message for the frame queue.

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -56,8 +56,9 @@ namespace network
* @param sslCtx Instance of the OpenSSL context.
* @param client Address for client.
* @param clientLen Length of sockaddr_in structure.
* @param nonBlocking Flag indicating socket operations should be non-blocking.
*/
SecureTcpClient(const int fd, SSL_CTX* sslCtx, sockaddr_in& client, int clientLen) : Socket(fd),
SecureTcpClient(const int fd, SSL_CTX* sslCtx, sockaddr_in& client, int clientLen, bool nonBlocking) : Socket(fd),
m_sockaddr(),
m_pSSL(nullptr),
m_pSSLCtx(nullptr)
@ -135,23 +136,26 @@ namespace network
} while (status == 1 && !SSL_is_init_finished(m_pSSL));
// reset socket blocking operations
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno);
throw std::runtime_error("Cannot accept SSL client");
}
if (!nonBlocking) {
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno);
throw std::runtime_error("Cannot accept SSL client");
}
if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) {
LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno);
throw std::runtime_error("Cannot accept SSL client");
if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) {
LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno);
throw std::runtime_error("Cannot accept SSL client");
}
}
}
/**
* @brief Initializes a new instance of the SecureTcpClient class.
* @param address IP Address.
* @param port Port.
* @param nonBlocking Flag indicating socket operations should be non-blocking.
*/
SecureTcpClient(const std::string& address, const uint16_t port) :
SecureTcpClient(const std::string& address, const uint16_t port, bool nonBlocking) :
m_pSSL(nullptr),
m_pSSLCtx(nullptr)
{
@ -180,6 +184,20 @@ namespace network
LogError(LOG_NET, "Failed to connect to server, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno);
throw std::runtime_error("Failed to SSL connect to server");
}
// setup socket for non-blocking operations
if (nonBlocking) {
int flags = fcntl(m_fd, F_GETFL, 0);
if (flags < 0) {
LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno);
throw std::runtime_error("Failed to set SSL server connection to non-blocking");
}
if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno);
throw std::runtime_error("Failed to set SSL server connection to non-blocking");
}
}
}
/**
* @brief Finalizes a instance of the SecureTcpClient class.

@ -7,7 +7,7 @@
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -109,9 +109,10 @@ namespace network
/**
* @brief Accept a new TCP connection either secure or unsecure.
* @param nonBlocking Flag indicating accepted TCP connections should use non-blocking sockets.
* @returns SecureTcpClient* Newly accepted TCP connection.
*/
[[nodiscard]] SecureTcpClient* accept()
[[nodiscard]] SecureTcpClient* accept(bool nonBlocking = false)
{
sockaddr_in client = {};
socklen_t clientLen = sizeof(client);
@ -120,7 +121,7 @@ namespace network
return nullptr;
}
return new SecureTcpClient(fd, m_pSSLCtx, client, clientLen);
return new SecureTcpClient(fd, m_pSSLCtx, client, clientLen, nonBlocking);
}
private:

@ -31,13 +31,37 @@ void P25Utils::setStatusBits(uint8_t* data, uint32_t ssOffset, bool b1, bool b2)
WRITE_BIT(data, ssOffset + 1U, b2);
}
/* Helper to set the starting status bits on P25 frame data to 1,1 for idle. */
void P25Utils::setStatusBitsStartIdle(uint8_t* data)
{
assert(data != nullptr);
// set "1,1" (Start of Inbound Slot/Idle) status bits [TIA-102.BAAA]
P25Utils::setStatusBits(data, P25_SS0_START, true, true);
}
/* Helper to set all status bits on a P25 frame data to 1,1 for idle. */
void P25Utils::setStatusBitsAllIdle(uint8_t* data, uint32_t length)
{
assert(data != nullptr);
// set "1,1" (Idle) status bits [TIA-102.BAAA]
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += P25_SS_INCREMENT) {
uint32_t ss1Pos = ss0Pos + 1U;
WRITE_BIT(data, ss0Pos, true); // 1
WRITE_BIT(data, ss1Pos, true); // 1
}
}
/* Helper to add the status bits on P25 frame data. */
void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool inbound, bool control)
void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool busy, bool unknown)
{
assert(data != nullptr);
// insert the "10" (Unknown, use for inbound or outbound) status bits
// set "1,0" (Unknown) status bits [TIA-102.BAAA]
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += P25_SS_INCREMENT) {
uint32_t ss1Pos = ss0Pos + 1U;
WRITE_BIT(data, ss0Pos, true); // 1
@ -47,14 +71,17 @@ void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool inbound, bool
// interleave the requested status bits (every other)
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 2U)) {
uint32_t ss1Pos = ss0Pos + 1U;
if (inbound) {
if (busy) {
// set "0,1" (Busy) status bits [TIA-102.BAAA]
WRITE_BIT(data, ss0Pos, false); // 0
WRITE_BIT(data, ss1Pos, true); // 1
} else {
if (control) {
if (unknown) {
// set "1,0" (Unknown) status bits [TIA-102.BAAA]
WRITE_BIT(data, ss0Pos, true); // 1
WRITE_BIT(data, ss1Pos, false); // 0
} else {
// set "1,1" (Start of Inbound Slot/Idle) status bits [TIA-102.BAAA]
WRITE_BIT(data, ss0Pos, true); // 1
WRITE_BIT(data, ss1Pos, true); // 1
}
@ -62,27 +89,35 @@ void P25Utils::addStatusBits(uint8_t* data, uint32_t length, bool inbound, bool
}
}
/* Helper to add the idle status bits on P25 frame data. */
/* Helper to add the unknown (1,0) status bits on P25 frame data. */
void P25Utils::addIdleStatusBits(uint8_t* data, uint32_t length)
void P25Utils::addUnknownStatusBits(uint8_t* data, uint32_t length, uint8_t interval)
{
assert(data != nullptr);
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 5U)) {
if (interval == 0U)
interval = 1U;
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * interval)) {
uint32_t ss1Pos = ss0Pos + 1U;
// set "1,0" (Unknown) status bits [TIA-102.BAAA]
WRITE_BIT(data, ss0Pos, true); // 1
WRITE_BIT(data, ss1Pos, false); // 0
}
}
/* Helper to add the trunk start slot status bits on P25 frame data. */
/* Helper to add the idle (1,1) status bits on P25 frame data. */
void P25Utils::addTrunkSlotStatusBits(uint8_t* data, uint32_t length)
void P25Utils::addIdleStatusBits(uint8_t* data, uint32_t length, uint8_t interval)
{
assert(data != nullptr);
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 5U)) {
if (interval == 0U)
interval = 1U;
for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * interval)) {
uint32_t ss1Pos = ss0Pos + 1U;
// set "1,1" (Start of Inbound Slot/Idle) status bits [TIA-102.BAAA]
WRITE_BIT(data, ss0Pos, true); // 1
WRITE_BIT(data, ss1Pos, true); // 1
}
@ -156,9 +191,9 @@ uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t start, uint3
return n;
}
/* Encode bit interleaving. */
/* Encode bit interleaving for a given length. */
uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t length)
uint32_t P25Utils::encodeByLength(const uint8_t* in, uint8_t* out, uint32_t length)
{
assert(in != nullptr);
assert(out != nullptr);
@ -172,17 +207,14 @@ uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t length)
while (n < length) {
if (pos == ss0Pos) {
ss0Pos += P25_SS_INCREMENT;
}
else if (pos == ss1Pos) {
ss1Pos += P25_SS_INCREMENT;
}
else {
bool b = READ_BIT(in, n);
WRITE_BIT(out, pos, b);
n++;
}
pos++;

@ -120,35 +120,48 @@ namespace p25
/**
* @brief Helper to set the status bits on P25 frame data.
* @param data P25 frame data buffer.
* @param ssOffset
* @param ssOffset Status symbol offset (bit offset).
* @param b1 Status Bit 1
* @param b2 Status Bit 2
*/
static void setStatusBits(uint8_t* data, uint32_t ssOffset, bool b1, bool b2);
/**
* @brief Helper to set the starting status bits on P25 frame data to 1,1 for idle.
* @param data P25 frame data buffer.
*/
static void setStatusBitsStartIdle(uint8_t* data);
/**
* @brief Helper to set all status bits on a P25 frame data to 1,1 for idle.
* @param data P25 frame data buffer.
* @param length Lenght of P25 frame in bits.
*/
static void setStatusBitsAllIdle(uint8_t* data, uint32_t length);
/**
* @brief Helper to add the status bits on P25 frame data.
* This appropriately sets the status bits for the P25 frame, starting with 1,0 and then
* properly setting 0,1 for inbound traffic, or 1,1 for idle (or 1,0 for control channels).
* properly setting 0,1 for inbound traffic, or 1,1 for idle (or 1,0 for unknown).
* @param data P25 frame data buffer.
* @param length
* @param inbound Flag indicating inbound channel is busy.
* @param control Flag indicating the channel is a control channel.
* @param length Lenght of P25 frame in bits.
* @param busy Flag indicating inbound channel is busy.
* @param unknown Flag indicating unknown slot state.
*/
static void addStatusBits(uint8_t *data, uint32_t length, bool inbound, bool control = false);
static void addStatusBits(uint8_t *data, uint32_t length, bool busy, bool unknown);
/**
* @brief Helper to add the idle status bits on P25 frame data.
* This sets the status bits to 1,0 interleaved every 5th status bit pair.
* @brief Helper to add the unknown (1,0) status bits on P25 frame data.
* This sets the status bits to 1,0 interleaved every variable status bit pair.
* @param data P25 frame data buffer.
* @param length
* @param length Lenght of P25 frame in bits.
* @param interval Status bit pair interval.
*/
static void addIdleStatusBits(uint8_t* data, uint32_t length);
static void addUnknownStatusBits(uint8_t* data, uint32_t length, uint8_t interval = 5U);
/**
* @brief Helper to add the trunk start slot status bits on P25 frame data.
* This sets the status bits to 1,1 interleaved every 5th status bit pair.
* @brief Helper to add the idle (1,1) status bits on P25 frame data.
* This sets the status bits to 1,1 interleaved every variable status bit pair.
* @param data P25 frame data buffer.
* @param length
* @param length Lenght of P25 frame in bits.
* @param interval Status bit pair interval.
*/
static void addTrunkSlotStatusBits(uint8_t* data, uint32_t length);
static void addIdleStatusBits(uint8_t* data, uint32_t length, uint8_t interval = 5U);
/**
* @brief Decode bit interleaving.
@ -175,7 +188,7 @@ namespace p25
* @param length
* @returns uint32_t
*/
static uint32_t encode(const uint8_t* in, uint8_t* out, uint32_t length);
static uint32_t encodeByLength(const uint8_t* in, uint8_t* out, uint32_t length);
/**
* @brief Compare two datasets for the given length.

@ -220,6 +220,9 @@ bool LC::decodeLDU1(const uint8_t* data, uint8_t* imbe)
break;
}
if (m_control->getLCO() == LCO::PRIVATE)
m_control->setGroup(false);
// by LDU1_VOICE8 we should have all the pertinant RS bytes
if (m_frameType == DFSIFrameType::LDU1_VOICE8) {
ulong64_t rsValue = 0U;

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/dfsi/frames/BlockHeader.h"
@ -26,8 +26,10 @@ using namespace p25::dfsi::frames;
/* Initializes a instance of the BlockHeader class. */
BlockHeader::BlockHeader() :
m_payloadType(false),
m_blockLength(BlockType::UNDEFINED)
m_payloadType(true),
m_blockType(BlockType::UNDEFINED),
m_timestampOffset(0U),
m_blockLength(0U)
{
/* stub */
}
@ -35,8 +37,10 @@ BlockHeader::BlockHeader() :
/* Initializes a instance of the BlockHeader class. */
BlockHeader::BlockHeader(uint8_t* data, bool verbose) :
m_payloadType(false),
m_blockLength(BlockType::UNDEFINED)
m_payloadType(true),
m_blockType(BlockType::UNDEFINED),
m_timestampOffset(0U),
m_blockLength(0U)
{
decode(data, verbose);
}

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -53,8 +53,8 @@ namespace p25
*/
class HOST_SW_API BlockHeader {
public:
static const uint8_t LENGTH = 1;
static const uint8_t VERBOSE_LENGTH = 4;
static const uint8_t LENGTH = 1U;
static const uint8_t VERBOSE_LENGTH = 4U;
/**
* @brief Initializes a copy instance of the BlockHeader class.

@ -49,8 +49,8 @@ bool ControlOctet::decode(const uint8_t* data)
{
assert(data != nullptr);
m_signal = (data[0U] & 0x07U) == 0x07U; // Signal Flag
m_compact = (data[0U] & 0x06U) == 0x06U; // Compact Flag
m_signal = (data[0U] & 0x80U) == 0x80U; // Signal Flag
m_compact = (data[0U] & 0x40U) == 0x40U; // Compact Flag
m_blockHeaderCnt = (uint8_t)(data[0U] & 0x3FU); // Block Header Count
return true;
@ -62,7 +62,7 @@ void ControlOctet::encode(uint8_t* data)
{
assert(data != nullptr);
data[0U] = (uint8_t)((m_signal ? 0x07U : 0x00U) + // Signal Flag
(m_compact ? 0x06U : 0x00U) + // Control Flag
data[0U] = (uint8_t)((m_signal ? 0x80U : 0x00U) + // Signal Flag
(m_compact ? 0x40U : 0x00U) + // Control Flag
(m_blockHeaderCnt & 0x3F));
}

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -45,7 +45,7 @@ namespace p25
*/
class HOST_SW_API ControlOctet {
public:
static const uint8_t LENGTH = 1;
static const uint8_t LENGTH = 1U;
/**
* @brief Initializes a copy instance of the ControlOctet class.

@ -44,13 +44,17 @@ namespace p25
namespace FSCMessageType {
/** @brief FSC Control Service Message.*/
enum E : uint8_t {
FSC_CONNECT = 0, //! Establish connection with FSS.
FSC_HEARTBEAT = 1, //! Heartbeat/Connectivity Maintenance.
FSC_CONNECT = 0, //! Establish connection with FSS
FSC_HEARTBEAT = 1, //! Heartbeat/Connectivity Maintenance
FSC_ACK = 2, //! Control Service Ack.
FSC_DISCONNECT = 9, //! Detach Control Service.
FSC_SEL_CHAN = 5, //! Channel Selection
FSC_INVALID = 127, //! Invalid Control Message.
FSC_REPORT_SEL_MODES = 8, //! Report Selected Modes
FSC_DISCONNECT = 9, //! Detach Control Service
FSC_INVALID = 127, //! Invalid Control Message
};
}

@ -30,11 +30,11 @@
// FSC
#include "common/p25/dfsi/frames/fsc/FSCMessage.h"
#include "common/p25/dfsi/frames/fsc/FSCResponse.h"
#include "common/p25/dfsi/frames/fsc/FSCACK.h"
#include "common/p25/dfsi/frames/fsc/FSCConnect.h"
#include "common/p25/dfsi/frames/fsc/FSCConnectResponse.h"
#include "common/p25/dfsi/frames/fsc/FSCReportSelModes.h"
#include "common/p25/dfsi/frames/fsc/FSCDisconnect.h"
#include "common/p25/dfsi/frames/fsc/FSCHeartbeat.h"
#include "common/p25/dfsi/frames/fsc/FSCSelChannel.h"
#endif // __DFSI_FRAMES_H__

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/dfsi/frames/FullRateVoice.h"
@ -38,6 +38,8 @@ FullRateVoice::FullRateVoice() :
{
imbeData = new uint8_t[IMBE_BUF_LEN];
::memset(imbeData, 0x00U, IMBE_BUF_LEN);
additionalData = new uint8_t[ADDITIONAL_LENGTH];
::memset(additionalData, 0x00U, ADDITIONAL_LENGTH);
}
/* Initializes a instance of the FullRateVoice class. */
@ -65,6 +67,21 @@ FullRateVoice::~FullRateVoice()
delete[] additionalData;
}
/* */
uint8_t FullRateVoice::getLength()
{
if (isVoice9or18()) {
return LENGTH_918;
}
if (isVoice3thru8() || isVoice12thru17()) {
return LENGTH;
}
return LENGTH_121011;
}
/* Decode a full rate voice frame. */
bool FullRateVoice::decode(const uint8_t* data)
@ -85,14 +102,11 @@ bool FullRateVoice::decode(const uint8_t* data)
m_superframeCnt = (uint8_t)((data[13U] >> 2) & 0x03U); // Superframe Counter
m_busy = (uint8_t)(data[13U] & 0x03U);
if (isVoice3thru8() || isVoice12thru17() || isVoice9or10()) {
if (additionalData != nullptr)
delete additionalData;
additionalData = new uint8_t[ADDITIONAL_LENGTH];
if (isVoice3thru8() || isVoice12thru17() || isVoice9or18()) {
::memset(additionalData, 0x00U, ADDITIONAL_LENGTH);
if (isVoice9or10()) {
// CAI 9 and 10 are 3 bytes of additional data not 4
if (isVoice9or18()) {
// CAI 9 and 18 are 3 bytes of additional data not 4
::memcpy(additionalData, data + 14U, ADDITIONAL_LENGTH - 1U);
} else {
::memcpy(additionalData, data + 14U, ADDITIONAL_LENGTH);
@ -122,10 +136,9 @@ void FullRateVoice::encode(uint8_t* data)
data[13U] = (uint8_t)(((m_superframeCnt & 0x03U) << 2) + // Superframe Count
(m_busy & 0x03U)); // Busy Status
if ((isVoice3thru8() || isVoice12thru17() || isVoice9or10()) &&
additionalData != nullptr) {
if (isVoice9or10()) {
// CAI 9 and 10 are 3 bytes of additional data not 4
if (isVoice3thru8() || isVoice12thru17() || isVoice9or18()) {
if (isVoice9or18()) {
// CAI 9 and 18 are 3 bytes of additional data not 4
::memcpy(data + 14U, additionalData, ADDITIONAL_LENGTH - 1U);
} else {
::memcpy(data + 14U, additionalData, ADDITIONAL_LENGTH);
@ -163,9 +176,9 @@ bool FullRateVoice::isVoice12thru17()
/* Helper indicating if the frame is voice 9 or 10. */
bool FullRateVoice::isVoice9or10()
bool FullRateVoice::isVoice9or18()
{
if ( (m_frameType == DFSIFrameType::LDU1_VOICE9) || (m_frameType == DFSIFrameType::LDU2_VOICE10) ) {
if ( (m_frameType == DFSIFrameType::LDU1_VOICE9) || (m_frameType == DFSIFrameType::LDU2_VOICE18) ) {
return true;
} else {
return false;

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -37,6 +37,7 @@ namespace p25
* @brief Implements a P25 full rate voice packet.
* \code{.unparsed}
* CAI Frames 1, 2, 10 and 11.
* 14 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -52,6 +53,7 @@ namespace p25
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* CAI Frames 3 - 8.
* 18 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -69,6 +71,7 @@ namespace p25
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* CAI Frames 12 - 17.
* 18 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -86,6 +89,7 @@ namespace p25
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* CAI Frames 9 and 10.
* 17 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -108,6 +112,7 @@ namespace p25
* the layout with 8-bit aligned IMBE blocks instead of message vectors:
*
* CAI Frames 1, 2, 10 and 11.
* 14 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -123,6 +128,7 @@ namespace p25
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* CAI Frames 3 - 8.
* 18 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -133,13 +139,14 @@ namespace p25
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | Et | Er |M|L|E| E1 |SF | B | Link Ctrl | Link Ctrl |
* | | | | |4| | | | | |
* | Et | Er |M|L|E| E1 |SF | B | Link Ctrl | Link Ctrl | Link |
* | | | | |4| | | | | | |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | Link Ctrl |R| Status |
* |Ctr|R| Status | Rsvd |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* CAI Frames 12 - 17.
* 18 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -150,13 +157,14 @@ namespace p25
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | Et | Er |M|L|E| E1 |SF | B | Enc Sync | Enc Sync |
* | | | | |4| | | | | |
* | Et | Er |M|L|E| E1 |SF | B | Enc Sync | Enc Sync | Enc |
* | | | | |4| | | | | | |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | Enc Sync |R| Status |
* |Syn|R| Status | Rsvd |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* CAI Frames 9 and 10.
* CAI Frames 9 and 18.
* 17 bytes
*
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
@ -177,9 +185,12 @@ namespace p25
*/
class HOST_SW_API FullRateVoice {
public:
static const uint8_t LENGTH = 18;
static const uint8_t ADDITIONAL_LENGTH = 4;
static const uint8_t IMBE_BUF_LEN = 11;
static const uint8_t LENGTH_121011 = 14U;
static const uint8_t LENGTH_918 = 17U;
static const uint8_t LENGTH = 18U;
static const uint8_t ADDITIONAL_LENGTH = 4U;
static const uint8_t IMBE_BUF_LEN = 11U;
/**
* @brief Initializes a copy instance of the FullRateVoice class.
@ -195,9 +206,16 @@ namespace p25
*/
~FullRateVoice();
/**
* @brief
* @returns uint8_t
*/
uint8_t getLength();
/**
* @brief Decode a full rate voice frame.
* @param[in] data Buffer to containing FullRateVoice to decode.
* @returns bool
*/
bool decode(const uint8_t* data);
/**
@ -247,10 +265,10 @@ namespace p25
*/
bool isVoice12thru17();
/**
* @brief Helper indicating if the frame is voice 9 or 10.
* @returns bool True, if frame is voice 9, or 10, otherwise false.
* @brief Helper indicating if the frame is voice 9 or 18.
* @returns bool True, if frame is voice 9, or 18, otherwise false.
*/
bool isVoice9or10();
bool isVoice9or18();
};
} // namespace frames
} // namespace dfsi

@ -55,9 +55,9 @@ namespace p25
*/
class HOST_SW_API MotFullRateVoice {
public:
static const uint8_t LENGTH = 17;
static const uint8_t SHORTENED_LENGTH = 14;
static const uint8_t ADDITIONAL_LENGTH = 4;
static const uint8_t LENGTH = 17U;
static const uint8_t SHORTENED_LENGTH = 14U;
static const uint8_t ADDITIONAL_LENGTH = 4U;
/**
* @brief Initializes a copy instance of the MotFullRateVoice class.

@ -55,7 +55,7 @@ namespace p25
*/
class HOST_SW_API MotPDUFrame {
public:
static const uint8_t LENGTH = 20;
static const uint8_t LENGTH = 20U;
/**
* @brief Initializes a copy instance of the MotPDUFrame class.

@ -50,8 +50,8 @@ namespace p25
*/
class HOST_SW_API MotStartOfStream {
public:
static const uint8_t LENGTH = 10;
static const uint8_t FIXED_MARKER = 0x02;
static const uint8_t LENGTH = 10U;
static const uint8_t FIXED_MARKER = 0x02U;
/**
* @brief Initializes a copy instance of the MotStartOfStream class.

@ -58,7 +58,7 @@ namespace p25
*/
class HOST_SW_API MotStartVoiceFrame {
public:
static const uint8_t LENGTH = 22;
static const uint8_t LENGTH = 22U;
/**
* @brief Initializes a copy instance of the MotStartVoiceFrame class.

@ -57,7 +57,7 @@ namespace p25
*/
class HOST_SW_API MotTSBKFrame {
public:
static const uint8_t LENGTH = 24;
static const uint8_t LENGTH = 24U;
/**
* @brief Initializes a copy instance of the MotTSBKFrame class.

@ -61,8 +61,8 @@ namespace p25
*/
class HOST_SW_API MotVoiceHeader1 {
public:
static const uint8_t LENGTH = 30;
static const uint8_t HCW_LENGTH = 21;
static const uint8_t LENGTH = 30U;
static const uint8_t HCW_LENGTH = 21U;
/**
* @brief Initializes a copy instance of the MotVoiceHeader1 class.

@ -57,8 +57,8 @@ namespace p25
*/
class HOST_SW_API MotVoiceHeader2 {
public:
static const uint8_t LENGTH = 22;
static const uint8_t HCW_LENGTH = 20;
static const uint8_t LENGTH = 22U;
static const uint8_t HCW_LENGTH = 20U;
/**
* @brief Initializes a copy instance of the MotVoiceHeader2 class.

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -45,7 +45,7 @@ namespace p25
*/
class HOST_SW_API StartOfStream {
public:
static const uint8_t LENGTH = 4;
static const uint8_t LENGTH = 4U;
/**
* @brief Initializes a copy instance of the StartOfStream class.

@ -35,18 +35,6 @@ FSCACK::FSCACK() : FSCMessage(),
m_messageId = FSCMessageType::FSC_ACK;
}
/* Initializes a instance of the FSCACK class. */
FSCACK::FSCACK(const uint8_t* data) : FSCMessage(data),
m_ackMessageId(FSCMessageType::FSC_INVALID),
m_ackVersion(1U),
m_ackCorrelationTag(0U),
m_responseCode(FSCAckResponseCode::CONTROL_ACK),
m_respLength(0U)
{
decode(data);
}
/* Decode a FSC ACK frame. */
bool FSCACK::decode(const uint8_t* data)
@ -65,7 +53,7 @@ bool FSCACK::decode(const uint8_t* data)
delete responseData;
responseData = new uint8_t[m_respLength];
::memset(responseData, 0x00U, m_respLength);
::memcpy(responseData, data, m_respLength);
::memcpy(responseData, data + 7U, m_respLength);
}
else {
if (responseData != nullptr)
@ -90,6 +78,6 @@ void FSCACK::encode(uint8_t* data)
data[6U] = m_respLength; // Response Data Length
if (m_respLength > 0U && responseData != nullptr) {
::memcpy(data, responseData, m_respLength);
::memcpy(data + 7U, responseData, m_respLength);
}
}

@ -41,17 +41,12 @@ namespace p25
*/
class HOST_SW_API FSCACK : public FSCMessage {
public:
static const uint8_t LENGTH = 6;
static const uint8_t LENGTH = 7U;
/**
* @brief Initializes a copy instance of the FSCACK class.
*/
FSCACK();
/**
* @brief Initializes a copy instance of the FSCACK class.
* @param data Buffer to containing FSCACK to decode.
*/
FSCACK(const uint8_t* data);
/**
* @brief Decode a FSC ACK frame.
@ -78,7 +73,7 @@ namespace p25
/**
* @brief
*/
__READONLY_PROPERTY(uint8_t, ackCorrelationTag, AckCorrelationTag);
__PROPERTY(uint8_t, ackCorrelationTag, AckCorrelationTag);
/**
* @brief Response code.
*/

@ -34,17 +34,6 @@ FSCConnect::FSCConnect() : FSCMessage(),
m_messageId = FSCMessageType::FSC_CONNECT;
}
/* Initializes a instance of the FSCConnect class. */
FSCConnect::FSCConnect(const uint8_t* data) : FSCMessage(data),
m_vcBasePort(0U),
m_vcSSRC(0U),
m_fsHeartbeatPeriod(5U),
m_hostHeartbeatPeriod(5U)
{
decode(data);
}
/* Decode a FSC connect frame. */
bool FSCConnect::decode(const uint8_t* data)

@ -41,17 +41,12 @@ namespace p25
*/
class HOST_SW_API FSCConnect : public FSCMessage {
public:
static const uint8_t LENGTH = 11;
static const uint8_t LENGTH = 11U;
/**
* @brief Initializes a copy instance of the FSCConnect class.
*/
FSCConnect();
/**
* @brief Initializes a copy instance of the FSCConnect class.
* @param data Buffer to containing FSCConnect to decode.
*/
FSCConnect(const uint8_t* data);
/**
* @brief Decode a FSC connect frame.
@ -63,7 +58,7 @@ namespace p25
* @param[out] data Buffer to encode a FSCConnect.
*/
void encode(uint8_t* data) override;
public:
/**
* @brief Voice Conveyance RTP Port.

@ -1,62 +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/dfsi/frames/fsc/FSCConnectResponse.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25::dfsi;
using namespace p25::dfsi::frames;
using namespace p25::dfsi::frames::fsc;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the FSCConnectResponse class. */
FSCConnectResponse::FSCConnectResponse() : FSCResponse(),
m_vcBasePort(0U)
{
/* stub */
}
/* Initializes a instance of the FSCConnectResponse class. */
FSCConnectResponse::FSCConnectResponse(const uint8_t* data) : FSCResponse(data),
m_vcBasePort(0U)
{
decode(data);
}
/* Decode a FSC connect frame. */
bool FSCConnectResponse::decode(const uint8_t* data)
{
assert(data != nullptr);
FSCResponse::decode(data);
m_vcBasePort = __GET_UINT16B(data, 1U); // Voice Conveyance RTP Port
return true;
}
/* Encode a FSC connect frame. */
void FSCConnectResponse::encode(uint8_t* data)
{
assert(data != nullptr);
FSCResponse::encode(data);
__SET_UINT16B(m_vcBasePort, data, 1U); // Voice Conveyance RTP Port
}

@ -1,78 +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
*
*/
/**
* @file FSCConnectResponse.h
* @ingroup dfsi_fsc_frames
* @file FSCConnectResponse.cpp
* @ingroup dfsi_fsc_frames
*/
#if !defined(__FSC_CONNECT_RESPONSE_H__)
#define __FSC_CONNECT_RESPONSE_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "common/p25/dfsi/frames/FrameDefines.h"
#include "common/p25/dfsi/frames/fsc/FSCResponse.h"
namespace p25
{
namespace dfsi
{
namespace frames
{
namespace fsc
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements the FSC Connect Response Message.
* @ingroup dfsi_fsc_frames
*/
class HOST_SW_API FSCConnectResponse : public FSCResponse {
public:
static const uint8_t LENGTH = 3;
/**
* @brief Initializes a copy instance of the FSCConnectResponse class.
*/
FSCConnectResponse();
/**
* @brief Initializes a copy instance of the FSCConnectResponse class.
* @param data Buffer to containing FSCConnectResponse to decode.
*/
FSCConnectResponse(const uint8_t* data);
/**
* @brief Decode a FSC connect response frame.
* @param[in] data Buffer to containing FSCConnectResponse to decode.
*/
bool decode(const uint8_t* data) override;
/**
* @brief Encode a FSC connect response frame.
* @param[out] data Buffer to encode a FSCConnectResponse.
*/
void encode(uint8_t* data) override;
public:
/**
* @brief Voice Conveyance RTP Port.
*/
__PROPERTY(uint16_t, vcBasePort, VCBasePort);
};
} // namespace fsc
} // namespace frames
} // namespace dfsi
} // namespace p25
#endif // __FSC_CONNECT_RESPONSE_H__

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/dfsi/frames/fsc/FSCDisconnect.h"
@ -29,10 +29,3 @@ FSCDisconnect::FSCDisconnect() : FSCMessage()
{
m_messageId = FSCMessageType::FSC_DISCONNECT;
}
/* Initializes a instance of the FSCDisconnect class. */
FSCDisconnect::FSCDisconnect(const uint8_t* data) : FSCMessage(data)
{
decode(data);
}

@ -41,17 +41,12 @@ namespace p25
*/
class HOST_SW_API FSCDisconnect : public FSCMessage {
public:
static const uint8_t LENGTH = 3;
static const uint8_t LENGTH = 3U;
/**
* @brief Initializes a copy instance of the FSCDisconnect class.
*/
FSCDisconnect();
/**
* @brief Initializes a copy instance of the FSCDisconnect class.
* @param data Buffer to containing FSCDisconnect to decode.
*/
FSCDisconnect(const uint8_t* data);
};
} // namespace fsc
} // namespace frames

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/dfsi/frames/fsc/FSCHeartbeat.h"
@ -29,10 +29,3 @@ FSCHeartbeat::FSCHeartbeat() : FSCMessage()
{
m_messageId = FSCMessageType::FSC_HEARTBEAT;
}
/* Initializes a instance of the FSCHeartbeat class. */
FSCHeartbeat::FSCHeartbeat(const uint8_t* data) : FSCMessage(data)
{
decode(data);
}

@ -41,17 +41,12 @@ namespace p25
*/
class HOST_SW_API FSCHeartbeat : public FSCMessage {
public:
static const uint8_t LENGTH = 3;
static const uint8_t LENGTH = 3U;
/**
* @brief Initializes a copy instance of the FSCHeartbeat class.
*/
FSCHeartbeat();
/**
* @brief Initializes a copy instance of the FSCHeartbeat class.
* @param data Buffer to containing FSCHeartbeat to decode.
*/
FSCHeartbeat(const uint8_t* data);
};
} // namespace fsc
} // namespace frames

@ -29,21 +29,11 @@ using namespace p25::dfsi::frames::fsc;
FSCMessage::FSCMessage() :
m_messageId(FSCMessageType::FSC_INVALID),
m_version(1U),
m_correlationTag(0U)
m_correlationTag(1U)
{
/* stub */
}
/* Initializes a instance of the FSCMessage class. */
FSCMessage::FSCMessage(const uint8_t* data) :
m_messageId(FSCMessageType::FSC_INVALID),
m_version(1U),
m_correlationTag(0U)
{
decode(data);
}
/* Decode a FSC message frame. */
bool FSCMessage::decode(const uint8_t* data)
@ -78,25 +68,43 @@ std::unique_ptr<FSCMessage> FSCMessage::createMessage(const uint8_t* data)
{
assert(data != nullptr);
uint8_t msg[FSCMessage::LENGTH + 1U];
::memset(msg, 0x00U, FSCMessage::LENGTH);
uint8_t messageId = (FSCMessageType::E)(data[0U]); // Message ID
uint8_t messageId = (FSCMessageType::E)(msg[0U]); // Message ID
FSCMessage* message = nullptr;
// standard P25 reference opcodes
switch (messageId) {
case FSCMessageType::FSC_CONNECT:
return std::unique_ptr<FSCMessage>(new FSCConnect(data));
message = new FSCConnect();
break;
case FSCMessageType::FSC_HEARTBEAT:
return std::unique_ptr<FSCMessage>(new FSCHeartbeat(data));
message = new FSCHeartbeat();
break;
case FSCMessageType::FSC_ACK:
return std::unique_ptr<FSCMessage>(new FSCACK(data));
message = new FSCACK();
break;
case FSCMessageType::FSC_REPORT_SEL_MODES:
message = new FSCReportSelModes();
break;
case FSCMessageType::FSC_SEL_CHAN:
message = new FSCSelChannel();
break;
case FSCMessageType::FSC_DISCONNECT:
return std::unique_ptr<FSCMessage>(new FSCDisconnect(data));
message = new FSCDisconnect();
break;
default:
LogError(LOG_P25, "FSCMessage::create(), unknown message value, messageId = $%02X", messageId);
LogError(LOG_P25, "FSCMessage::createMessage(), unknown message value, messageId = $%02X", messageId);
break;
}
if (message != nullptr) {
if (!message->decode(data)) {
return nullptr;
}
return std::unique_ptr<FSCMessage>(message);
}
return nullptr;
}

@ -46,11 +46,6 @@ namespace p25
* @brief Initializes a copy instance of the FSCMessage class.
*/
FSCMessage();
/**
* @brief Initializes a copy instance of the FSCMessage class.
* @param data Buffer to containing FSCMessage to decode.
*/
FSCMessage(const uint8_t* data);
/**
* @brief Decode a FSC message frame.
@ -82,7 +77,7 @@ namespace p25
/**
* @brief
*/
__PROTECTED_READONLY_PROPERTY(uint8_t, correlationTag, CorrelationTag);
__PROPERTY(uint8_t, correlationTag, CorrelationTag);
};
} // namespace fsc
} // namespace frames

@ -0,0 +1,31 @@
// 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/dfsi/frames/fsc/FSCReportSelModes.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25::dfsi;
using namespace p25::dfsi::frames;
using namespace p25::dfsi::frames::fsc;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the FSCReportSelModes class. */
FSCReportSelModes::FSCReportSelModes() : FSCMessage()
{
m_messageId = FSCMessageType::FSC_REPORT_SEL_MODES;
}

@ -0,0 +1,56 @@
// 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 FSCReportSelModes.h
* @ingroup dfsi_fsc_frames
* @file FSCReportSelModes.cpp
* @ingroup dfsi_fsc_frames
*/
#if !defined(__FSC_REPORT_SEL_MODES_H__)
#define __FSC_REPORT_SEL_MODES_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "common/p25/dfsi/frames/FrameDefines.h"
#include "common/p25/dfsi/frames/fsc/FSCMessage.h"
namespace p25
{
namespace dfsi
{
namespace frames
{
namespace fsc
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements the FSC Report Selected Modes Message.
* @ingroup dfsi_fsc_frames
*/
class HOST_SW_API FSCReportSelModes : public FSCMessage {
public:
static const uint8_t LENGTH = 3U;
/**
* @brief Initializes a copy instance of the FSCReportSelModes class.
*/
FSCReportSelModes();
};
} // namespace fsc
} // namespace frames
} // namespace dfsi
} // namespace p25
#endif // __FSC_REPORT_SEL_MODES_H__

@ -1,60 +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/dfsi/frames/fsc/FSCResponse.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25::dfsi;
using namespace p25::dfsi::frames;
using namespace p25::dfsi::frames::fsc;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the FSCResponse class. */
FSCResponse::FSCResponse() :
m_version(1U)
{
/* stub */
}
/* Initializes a instance of the FSCResponse class. */
FSCResponse::FSCResponse(const uint8_t* data) :
m_version(1U)
{
decode(data);
}
/* Decode a FSC message frame. */
bool FSCResponse::decode(const uint8_t* data)
{
assert(data != nullptr);
m_version = data[0U]; // Response Version
return true;
}
/* Encode a FSC message frame. */
void FSCResponse::encode(uint8_t* data)
{
assert(data != nullptr);
data[0U] = m_version; // Response Version
}

@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/dfsi/frames/fsc/FSCSelChannel.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25::dfsi;
using namespace p25::dfsi::frames;
using namespace p25::dfsi::frames::fsc;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the FSCSelChannel class. */
FSCSelChannel::FSCSelChannel() : FSCMessage(),
m_rxChan(1U),
m_txChan(1U)
{
m_messageId = FSCMessageType::FSC_SEL_CHAN;
}
/* Decode a FSC select channel frame. */
bool FSCSelChannel::decode(const uint8_t* data)
{
assert(data != nullptr);
FSCMessage::decode(data);
m_rxChan = data[3U]; // Receive Channel
m_txChan = data[4U]; // Transmit Channel
return true;
}
/* Encode a FSC select channel frame. */
void FSCSelChannel::encode(uint8_t* data)
{
assert(data != nullptr);
FSCMessage::encode(data);
data[3U] = m_rxChan; // Receive Channel
data[4U] = m_txChan; // Transmit Channel
}

@ -4,23 +4,24 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file FSCResponse.h
* @file FSCReportSelModes.h
* @ingroup dfsi_fsc_frames
* @file FSCResponse.cpp
* @file FSCReportSelModes.cpp
* @ingroup dfsi_fsc_frames
*/
#if !defined(__FSC_RESPONSE_H__)
#define __FSC_RESPONSE_H__
#if !defined(__FSC_SEL_CHANNEL_H__)
#define __FSC_SEL_CHANNEL_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "common/p25/dfsi/frames/FrameDefines.h"
#include "common/p25/dfsi/frames/fsc/FSCMessage.h"
namespace p25
{
@ -35,43 +36,42 @@ namespace p25
// ---------------------------------------------------------------------------
/**
* @brief Base class FSC response messages derive from.
* @brief Implements the FSC Select Channel Message.
* @ingroup dfsi_fsc_frames
*/
class HOST_SW_API FSCResponse {
class HOST_SW_API FSCSelChannel : public FSCMessage {
public:
static const uint8_t LENGTH = 1;
static const uint8_t LENGTH = 3U;
/**
* @brief Initializes a copy instance of the FSCResponse class.
* @brief Initializes a copy instance of the FSCSelChannel class.
*/
FSCResponse();
/**
* @brief Initializes a copy instance of the FSCResponse class.
* @param data Buffer to containing FSCResponse to decode.
*/
FSCResponse(const uint8_t* data);
FSCSelChannel();
/**
* @brief Decode a FSC message frame.
* @param[in] data Buffer to containing FSCResponse to decode.
* @brief Decode a FSC select channel frame.
* @param[in] data Buffer to containing FSCSelChannel to decode.
*/
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data) override;
/**
* @brief Encode a FSC message frame.
* @param[out] data Buffer to encode a FSCResponse.
* @brief Encode a FSC select channel frame.
* @param[out] data Buffer to encode a FSCSelChannel.
*/
virtual void encode(uint8_t* data);
void encode(uint8_t* data) override;
public:
/**
* @brief Response Version.
* @brief Receive Channel Number.
*/
__PROPERTY(uint8_t, rxChan, RxChan);
/**
* @brief Transmit Channel Number.
*/
__PROTECTED_READONLY_PROPERTY(uint8_t, version, Version);
__PROPERTY(uint8_t, txChan, TxChan);
};
} // namespace fsc
} // namespace frames
} // namespace dfsi
} // namespace p25
#endif // __FSC_RESPONSE_H__
#endif // __FSC_SEL_CHANNEL_H__

@ -96,14 +96,21 @@ LC& LC::operator=(const LC& data)
/* Decode a header data unit. */
bool LC::decodeHDU(const uint8_t* data)
bool LC::decodeHDU(const uint8_t* data, bool rawOnly)
{
assert(data != nullptr);
// deinterleave
uint8_t rs[P25_HDU_LENGTH_BYTES + 1U];
uint8_t raw[P25_HDU_LENGTH_BYTES + 1U];
P25Utils::decode(data, raw, 114U, 780U);
if (rawOnly)
::memcpy(raw, data, P25_HDU_LENGTH_BYTES);
else
P25Utils::decode(data, raw, 114U, 780U);
#if DEBUG_P25_HDU
Utils::dump(2U, "LC::decodeHDU(), HDU Raw", raw, P25_HDU_LENGTH_BYTES);
#endif
// decode Golay (18,6,8) FEC
decodeHDUGolay(raw, rs);
@ -167,7 +174,7 @@ bool LC::decodeHDU(const uint8_t* data)
/* Encode a header data unit. */
void LC::encodeHDU(uint8_t* data)
void LC::encodeHDU(uint8_t* data, bool rawOnly)
{
assert(data != nullptr);
assert(m_mi != nullptr);
@ -202,6 +209,11 @@ void LC::encodeHDU(uint8_t* data)
// encode Golay (18,6,8) FEC
encodeHDUGolay(raw, rs);
if (rawOnly) {
::memcpy(data, raw, P25_HDU_LENGTH_BYTES);
return;
}
// interleave
P25Utils::encode(raw, data, 114U, 780U);
@ -239,7 +251,7 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly)
decodeLDUHamming(raw, rs + 15U);
#if DEBUG_P25_LDU1
Utils::dump(2U, "LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
// decode RS (24,12,13) FEC
@ -256,7 +268,7 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly)
}
#if DEBUG_P25_LDU1
Utils::dump(2U, "LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
return decodeLC(rs, rawOnly);
@ -274,14 +286,14 @@ void LC::encodeLDU1(uint8_t* data)
encodeLC(rs);
#if DEBUG_P25_LDU1
Utils::dump(2U, "LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
// encode RS (24,12,13) FEC
m_rs.encode241213(rs);
#if DEBUG_P25_LDU1
Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
// encode Hamming (10,6,3) FEC and interleave for LC data
@ -338,7 +350,7 @@ bool LC::decodeLDU2(const uint8_t* data)
decodeLDUHamming(raw, rs + 15U);
#if DEBUG_P25_LDU2
Utils::dump(2U, "LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
// decode RS (24,16,9) FEC
@ -355,7 +367,7 @@ bool LC::decodeLDU2(const uint8_t* data)
}
#if DEBUG_P25_LDU2
Utils::dump(2U, "LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
m_algId = rs[9U]; // Algorithm ID
@ -406,14 +418,14 @@ void LC::encodeLDU2(uint8_t* data)
rs[11U] = (m_kId >> 0) & 0xFFU; // ...
#if DEBUG_P25_LDU2
Utils::dump(2U, "LC::encodeLDU2(), LDU2 LC", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
// encode RS (24,16,9) FEC
m_rs.encode24169(rs);
#if DEBUG_P25_LDU2
Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
#endif
// encode Hamming (10,6,3) FEC and interleave for LC data

@ -73,14 +73,16 @@ namespace p25
/**
* @brief Decode a header data unit.
* @param[in] data Buffer containing the HDU to decode.
* @param rawOnly Flag indicating only the raw bytes of the LC should be decoded.
* @returns True, if HDU decoded, otherwise false.
*/
bool decodeHDU(const uint8_t* data);
bool decodeHDU(const uint8_t* data, bool rawOnly = false);
/**
* @brief Encode a header data unit.
* @param[out] data Buffer to encode an HDU.
* @param rawOnly Flag indicating only the raw bytes of the LC should be encoded.
*/
void encodeHDU(uint8_t* data);
void encodeHDU(uint8_t* data, bool rawOnly = false);
/**
* @brief Decode a logical link data unit 1.

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022,2024,2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@ -34,7 +34,17 @@ bool LC_CALL_TERM::decode(const uint8_t* data)
{
assert(data != nullptr);
/* stub */
uint8_t rs[P25_TDULC_LENGTH_BYTES + 1U];
::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES);
bool ret = TDULC::decode(data, rs);
if (!ret)
return false;
ulong64_t rsValue = TDULC::toValue(rs);
m_implicit = true;
m_dstId = (uint32_t)(rsValue & 0xFFFFFFU); // Target Address
return true;
}

@ -79,6 +79,8 @@ std::unique_ptr<TDULC> TDULCFactory::createTDULC(const uint8_t* data)
return decode(new LC_PRIVATE(), data);
case LCO::TEL_INT_VCH_USER:
return decode(new LC_TEL_INT_VCH_USER(), data);
case LCO::CALL_TERM:
return decode(new LC_CALL_TERM(), data);
default:
LogError(LOG_P25, "TDULCFactory::create(), unknown TDULC LCO value, lco = $%02X", lco);
break;

@ -60,7 +60,7 @@ void OSP_U_DEREG_ACK::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
tsbkValue = (tsbkValue << 8) + m_siteData.netId(); // Network ID
tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID
tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address
tsbkValue = (tsbkValue << 24) + m_dstId; // Destination Radio Address
std::unique_ptr<uint8_t[]> tsbk = TSBK::fromValue(tsbkValue);
TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis);

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "ActivityLog.h"
@ -64,7 +64,7 @@ static bool ActivityLogOpen()
}
char filename[200U];
::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", LogGetFilePath().c_str(), LogGetFileRoot().c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_actFilePath.c_str(), m_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
m_actFpLog = ::fopen(filename, "a+t");
m_actTm = *tm;

@ -79,7 +79,7 @@ void fatal(const char* msg, ...)
void usage(const char* message, const char* arg)
{
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (message != nullptr) {
::fprintf(stderr, "%s: ", g_progExe.c_str());
@ -143,7 +143,7 @@ int checkArgs(int argc, char* argv[])
}
else if (IS("-v")) {
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n");
if (argc == 2)
exit(EXIT_SUCCESS);

@ -151,7 +151,7 @@ int HostFNE::run()
#endif // !defined(_WIN32)
::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \
"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \
">> Fixed Network Equipment\r\n");

@ -82,11 +82,13 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port,
m_disallowAdjStsBcast(false),
m_disallowExtAdjStsBcast(true),
m_allowConvSiteAffOverride(false),
m_disallowCallTerm(false),
m_restrictGrantToAffOnly(false),
m_enableInCallCtrl(true),
m_rejectUnknownRID(false),
m_filterHeaders(true),
m_filterTerminators(true),
m_disallowU2U(false),
m_dropU2UPeerTable(),
m_enableInfluxDB(false),
m_influxServerAddress("127.0.0.1"),
@ -129,6 +131,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
m_allowConvSiteAffOverride = conf["allowConvSiteAffOverride"].as<bool>(true);
m_enableInCallCtrl = conf["enableInCallCtrl"].as<bool>(true);
m_rejectUnknownRID = conf["rejectUnknownRID"].as<bool>(false);
m_disallowCallTerm = conf["disallowCallTerm"].as<bool>(false);
m_softConnLimit = conf["connectionLimit"].as<uint32_t>(MAX_HARD_CONN_CAP);
if (m_softConnLimit > MAX_HARD_CONN_CAP) {
@ -165,6 +168,8 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
** Drop Unit to Unit Peers
*/
m_disallowU2U = conf["disallowAllUnitToUnit"].as<bool>(false);
yaml::Node& dropUnitToUnit = conf["dropUnitToUnit"];
if (dropUnitToUnit.size() > 0U) {
for (size_t i = 0; i < dropUnitToUnit.size(); i++) {
@ -184,12 +189,14 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
LogInfo(" Disable Packet Data: %s", m_disablePacketData ? "yes" : "no");
LogInfo(" Dump Packet Data: %s", m_dumpPacketData ? "yes" : "no");
LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no");
LogInfo(" Disable P25 TDULC call termination broadcasts to any peers: %s", m_disallowCallTerm ? "yes" : "no");
LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no");
LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no");
LogInfo(" Reject Unknown RIDs: %s", m_rejectUnknownRID ? "yes" : "no");
LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no");
LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no");
LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no");
LogInfo(" Disallow Unit-to-Unit: %s", m_disallowU2U ? "yes" : "no");
LogInfo(" InfluxDB Reporting Enabled: %s", m_enableInfluxDB ? "yes" : "no");
if (m_enableInfluxDB) {
LogInfo(" InfluxDB Address: %s", m_influxServerAddress.c_str());
@ -761,12 +768,16 @@ void* FNENetwork::threadedNetworkRx(void* arg)
else {
LogWarning(LOG_NET, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState());
network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen);
delete connection;
network->erasePeer(peerId);
}
}
}
else {
network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen);
network->erasePeer(peerId);
LogWarning(LOG_NET, "PEER %u RPTK NAK, having no connection", peerId);
}
}

@ -457,6 +457,7 @@ namespace network
bool m_disallowAdjStsBcast;
bool m_disallowExtAdjStsBcast;
bool m_allowConvSiteAffOverride;
bool m_disallowCallTerm;
bool m_restrictGrantToAffOnly;
bool m_enableInCallCtrl;
bool m_rejectUnknownRID;
@ -466,6 +467,7 @@ namespace network
bool m_forceListUpdate;
bool m_disallowU2U;
std::vector<uint32_t> m_dropU2UPeerTable;
bool m_enableInfluxDB;

@ -193,6 +193,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
// check that we got the appropriate data
if (decompressedLen == m_tgidSize) {
if (m_tidLookup == nullptr) {
LogError(LOG_NET, "Talkgroup ID lookup not available yet.");
goto tid_lookup_cleanup; // yes - I hate myself; but this is quick
}
// store to file
std::unique_ptr<char[]> __str = std::make_unique<char[]>(decompressedLen + 1U);
char* str = __str.get();
@ -217,6 +222,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
file.close();
m_tidLookup->stop(true);
m_tidLookup->setReloadTime(0U);
m_tidLookup->filename(filename);
m_tidLookup->reload();
@ -325,6 +331,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
// check that we got the appropriate data
if (decompressedLen == m_ridSize) {
if (m_ridLookup == nullptr) {
LogError(LOG_NET, "Radio ID lookup not available yet.");
goto rid_lookup_cleanup; // yes - I hate myself; but this is quick
}
// store to file
std::unique_ptr<char[]> __str = std::make_unique<char[]>(decompressedLen + 1U);
char* str = __str.get();
@ -349,6 +360,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
file.close();
m_ridLookup->stop(true);
m_ridLookup->setReloadTime(0U);
m_ridLookup->filename(filename);
m_ridLookup->reload();
@ -457,6 +469,11 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
// check that we got the appropriate data
if (decompressedLen == m_pidSize) {
if (m_pidLookup == nullptr) {
LogError(LOG_NET, "Peer ID lookup not available yet.");
goto pid_lookup_cleanup; // yes - I hate myself; but this is quick
}
// store to file
std::unique_ptr<char[]> __str = std::make_unique<char[]>(decompressedLen + 1U);
char* str = __str.get();
@ -481,6 +498,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco
file.close();
m_pidLookup->stop(true);
m_pidLookup->setReloadTime(0U);
m_pidLookup->filename(filename);
m_pidLookup->reload();

@ -1177,8 +1177,13 @@ void RESTAPI::restAPI_GetPeerList(const HTTPPayload& request, HTTPPayload& reply
json::object peerObj = json::object();
uint32_t peerId = entry.first;
std::string peerAlias = entry.second.peerAlias();
bool peerLink = entry.second.peerLink();
bool peerPassword = !entry.second.peerPassword().empty(); // True if password is not empty, otherwise false
peerObj["peerId"].set<uint32_t>(peerId);
peerObj["peerAlias"].set<std::string>(peerAlias);
peerObj["peerLink"].set<bool>(peerLink);
peerObj["peerPassword"].set<bool>(peerPassword);
peers.push_back(json::value(peerObj));
}
}
@ -1203,14 +1208,51 @@ void RESTAPI::restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply,
errorPayload(reply, "OK", HTTPPayload::OK);
// Validate peer ID (required)
if (!req["peerId"].is<uint32_t>()) {
errorPayload(reply, "peerId was not a valid integer");
return;
}
// Get
uint32_t peerId = req["peerId"].get<uint32_t>();
m_peerListLookup->addEntry(peerId);
// Get peer alias (optional)
std::string peerAlias = "";
if (req.find("peerAlias") != req.end()) {
// Validate
if (!req["peerAlias"].is<std::string>()) {
errorPayload(reply, "peerAlias was not a valid string");
return;
}
// Get
peerAlias = req["peerAlias"].get<std::string>();
}
// Get peer link setting (optional)
bool peerLink = false;
if (req.find("peerLink") != req.end()) {
// Validate
if (!req["peerLink"].is<bool>()) {
errorPayload(reply, "peerLink was not a valid boolean");
return;
}
// Get
peerLink = req["peerLink"].get<bool>();
}
// Get peer password (optional)
std::string peerPassword = "";
if (req.find("peerPassword") != req.end()) {
// Validate
if (!req["peerPassword"].is<std::string>()) {
errorPayload(reply, "peerPassword was not a valid string");
return;
}
// Get
peerPassword = req["peerPassword"].get<std::string>();
}
m_peerListLookup->addEntry(peerId, peerAlias, peerPassword, peerLink);
}
/* REST API endpoint; implements put peer delete request. */

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "fne/Defines.h"
@ -129,7 +129,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
// is the stream valid?
if (validate(peerId, dmrData, streamId)) {
// is this peer ignored?
if (!isPeerPermitted(peerId, dmrData, streamId)) {
if (!isPeerPermitted(peerId, dmrData, streamId, external)) {
return false;
}
@ -141,19 +141,33 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
RxStatus status;
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); });
if (it == m_status.end()) {
LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u",
peerId, srcId, dstId, streamId, external);
}
else {
status = it->second;
{
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId && x.second.slotNo == slotNo) {
return true;
}
return false;
});
if (it == m_status.end()) {
LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u",
peerId, srcId, dstId, streamId, external);
}
else {
status = it->second;
}
}
uint64_t duration = hrc::diff(pktTime, status.callStartTime);
if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != m_status.end()) {
m_status.erase(dstId);
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId && x.second.slotNo == slotNo) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
m_status[dstId].reset();
// is this a parrot talkgroup? if so, clear any remaining frames from the buffer
lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId);
@ -194,7 +208,13 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
return false;
}
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); });
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId && x.second.slotNo == slotNo) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
RxStatus status = it->second;
if (streamId != status.streamId) {
@ -202,7 +222,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket);
if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) {
LogWarning(LOG_NET, "DMR, Call Collision, lasted more then %us with no further updates, forcibly ending call");
m_status.erase(dstId);
m_status[dstId].reset();
m_network->m_callInProgress = false;
}
@ -228,14 +248,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
// this is a new call stream
RxStatus status = RxStatus();
status.callStartTime = pktTime;
status.srcId = srcId;
status.dstId = dstId;
status.slotNo = slotNo;
status.streamId = streamId;
status.peerId = peerId;
m_status[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow...
// bryanb: this could be problematic and is naive, if a dstId appears on both slots (which shouldn't happen)
m_status[dstId].callStartTime = pktTime;
m_status[dstId].srcId = srcId;
m_status[dstId].dstId = dstId;
m_status[dstId].slotNo = slotNo;
m_status[dstId].streamId = streamId;
m_status[dstId].peerId = peerId;
m_status[dstId].activeCall = true;
LogMessage(LOG_NET, "DMR, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external);
@ -367,7 +387,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
{
// if we have an Rx status for the destination deny the grant
if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) {
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId/* && x.second.slotNo == slot*/) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
return false;
}
@ -629,6 +656,8 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::NetDat
bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool external)
{
if (data.getFLCO() == FLCO::PRIVATE) {
if (m_network->m_disallowU2U)
return false;
if (!m_network->checkU2UDroppedPeer(peerId))
return true;
return false;

@ -120,13 +120,31 @@ namespace network
uint8_t* buffer;
uint32_t bufferLen;
/**
* @brief DMR slot number.
*/
uint8_t slotNo;
/**
* @brief RTP Packet Sequence.
*/
uint16_t pktSeq;
/**
* @brief Call Stream ID.
*/
uint32_t streamId;
/**
* @brief Peer ID.
*/
uint32_t peerId;
/**
* @brief Source ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
};
std::deque<ParrotFrame> m_parrotFrames;
@ -139,11 +157,43 @@ namespace network
public:
system_clock::hrc::hrc_t callStartTime;
system_clock::hrc::hrc_t lastPacket;
/**
* @brief Source ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
/**
* @brief DMR slot number.
*/
uint8_t slotNo;
/**
* @brief Call Stream ID.
*/
uint32_t streamId;
/**
* @brief Peer ID.
*/
uint32_t peerId;
/**
* @brief Flag indicating this call is active with traffic currently in progress.
*/
bool activeCall;
/**
* @brief Helper to reset call status.
*/
void reset()
{
srcId = 0U;
dstId = 0U;
slotNo = 0U;
streamId = 0U;
peerId = 0U;
activeCall = false;
}
};
typedef std::pair<const uint32_t, RxStatus> StatusMapPair;
std::unordered_map<uint32_t, RxStatus> m_status;

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "fne/Defines.h"
@ -95,7 +95,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
// is the stream valid?
if (validate(peerId, lc, messageType, streamId)) {
// is this peer ignored?
if (!isPeerPermitted(peerId, lc, messageType, streamId)) {
if (!isPeerPermitted(peerId, lc, messageType, streamId, external)) {
return false;
}
@ -113,8 +113,15 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
RxStatus status = m_status[dstId];
uint64_t duration = hrc::diff(pktTime, status.callStartTime);
if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) {
m_status.erase(dstId);
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
m_status[dstId].reset();
// is this a parrot talkgroup? if so, clear any remaining frames from the buffer
lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId);
@ -154,7 +161,13 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
return false;
}
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; });
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
RxStatus status = m_status[dstId];
if (streamId != status.streamId) {
@ -162,7 +175,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket);
if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) {
LogWarning(LOG_NET, "NXDN, Call Collision, lasted more then %us with no further updates, forcibly ending call");
m_status.erase(dstId);
m_status[dstId].reset();
m_network->m_callInProgress = false;
}
@ -188,13 +201,12 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
}
// this is a new call stream
RxStatus status = RxStatus();
status.callStartTime = pktTime;
status.srcId = srcId;
status.dstId = dstId;
status.streamId = streamId;
status.peerId = peerId;
m_status[dstId] = status;
m_status[dstId].callStartTime = pktTime;
m_status[dstId].srcId = srcId;
m_status[dstId].dstId = dstId;
m_status[dstId].streamId = streamId;
m_status[dstId].peerId = peerId;
m_status[dstId].activeCall = true;
LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external);
@ -320,7 +332,14 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
{
// if we have an Rx status for the destination deny the grant
if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) {
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
return false;
}
@ -444,6 +463,8 @@ bool TagNXDNData::peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound)
bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId, bool external)
{
if (!lc.getGroup()) {
if (m_network->m_disallowU2U)
return false;
if (!m_network->checkU2UDroppedPeer(peerId))
return true;
return false;

@ -94,11 +94,26 @@ namespace network
uint8_t* buffer;
uint32_t bufferLen;
/**
* @brief RTP Packet Sequence.
*/
uint16_t pktSeq;
/**
* @brief Call Stream ID.
*/
uint32_t streamId;
/**
* @brief Peer ID.
*/
uint32_t peerId;
/**
* @brief Source ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
};
std::deque<ParrotFrame> m_parrotFrames;
@ -111,10 +126,38 @@ namespace network
public:
system_clock::hrc::hrc_t callStartTime;
system_clock::hrc::hrc_t lastPacket;
/**
* @brief Source ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
/**
* @brief Call Stream ID.
*/
uint32_t streamId;
/**
* @brief Peer ID.
*/
uint32_t peerId;
/**
* @brief Flag indicating this call is active with traffic currently in progress.
*/
bool activeCall;
/**
* @brief Helper to reset call status.
*/
void reset()
{
srcId = 0U;
dstId = 0U;
streamId = 0U;
peerId = 0U;
activeCall = false;
}
};
typedef std::pair<const uint32_t, RxStatus> StatusMapPair;
std::unordered_map<uint32_t, RxStatus> m_status;

@ -4,11 +4,12 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "fne/Defines.h"
#include "common/p25/lc/tsbk/TSBKFactory.h"
#include "common/p25/lc/tdulc/TDULCFactory.h"
#include "common/p25/Sync.h"
#include "common/Clock.h"
#include "common/Log.h"
@ -153,7 +154,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
// is the stream valid?
if (validate(peerId, control, duid, tsbk.get(), streamId)) {
// is this peer ignored?
if (!isPeerPermitted(peerId, control, duid, streamId)) {
if (!isPeerPermitted(peerId, control, duid, streamId, external)) {
return false;
}
@ -178,14 +179,21 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
}
if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) {
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
if (grantDemand) {
LogWarning(LOG_NET, "P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u",
peerId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external);
return false;
}
else {
m_status.erase(dstId);
m_status[dstId].reset();
// is this a parrot talkgroup? if so, clear any remaining frames from the buffer
lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId);
@ -227,7 +235,13 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
return false;
}
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; });
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
RxStatus status = m_status[dstId];
if (streamId != status.streamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) {
@ -235,7 +249,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket);
if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) {
LogWarning(LOG_NET, "P25, Call Collision, lasted more then %us with no further updates, forcibly ending call");
m_status.erase(dstId);
m_status[dstId].reset();
m_network->m_callInProgress = false;
}
@ -261,13 +275,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
}
// this is a new call stream
RxStatus status = RxStatus();
status.callStartTime = pktTime;
status.srcId = srcId;
status.dstId = dstId;
status.streamId = streamId;
status.peerId = peerId;
m_status[dstId] = status;
m_status[dstId].callStartTime = pktTime;
m_status[dstId].srcId = srcId;
m_status[dstId].dstId = dstId;
m_status[dstId].streamId = streamId;
m_status[dstId].peerId = peerId;
m_status[dstId].activeCall = true;
LogMessage(LOG_NET, "P25, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external);
@ -406,7 +419,14 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
{
// if we have an Rx status for the destination deny the grant
if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) {
auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) {
if (x.second.dstId == dstId) {
if (x.second.activeCall)
return true;
}
return false;
});
if (it != m_status.end()) {
return false;
}
@ -745,6 +765,31 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid)
}
}
// are we receiving a TDULC?
if (duid == DUID::TDULC) {
uint32_t frameLength = buffer[23U];
UInt8Array data = std::unique_ptr<uint8_t[]>(new uint8_t[frameLength]);
::memset(data.get(), 0x00U, frameLength);
::memcpy(data.get(), buffer + 24U, frameLength);
std::unique_ptr<lc::TDULC> tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get());
if (tdulc != nullptr) {
// handle standard P25 reference opcodes
switch (tdulc->getLCO()) {
case LCO::CALL_TERM:
if (m_network->m_disallowCallTerm)
return false;
default:
break;
}
} else {
// bryanb: should these be logged?
//std::string peerIdentity = m_network->resolvePeerIdentity(peerId);
//LogWarning(LOG_NET, "PEER %u (%s), passing TDULC that failed to decode? tdulc == nullptr", peerId, peerIdentity.c_str());
}
}
return true;
}
@ -863,6 +908,8 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint
bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, uint32_t streamId, bool external)
{
if (control.getLCO() == LCO::PRIVATE) {
if (m_network->m_disallowU2U)
return false;
if (!m_network->checkU2UDroppedPeer(peerId))
return true;
return false;
@ -907,7 +954,7 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid,
if (duid == DUID::TDU) {
if (m_network->m_filterTerminators) {
if (control.getSrcId() != 0U && control.getDstId() != 0U) {
if (/*control.getSrcId() != 0U &&*/control.getDstId() != 0U) {
// is this a group call?
lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId());
if (!tg.isInvalid()) {
@ -1153,6 +1200,26 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const
}
break;
}
// handle validating DVM call termination packets
if (tsbk->getMFId() == MFG_DVM_OCS) {
switch (tsbk->getLCO()) {
case LCO::CALL_TERM:
{
lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(tsbk->getDstId());
// check TGID validity
if (tg.isInvalid()) {
return false;
}
if (!tg.config().active()) {
return false;
}
}
break;
}
}
}
return true;
@ -1337,20 +1404,18 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk)
uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES];
::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
// Generate Sync
// generate Sync
Sync::addP25Sync(data);
// network bursts have no NID
// Generate TSBK block
// generate TSBK block
tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU
tsbk->encode(data);
// Add busy bits
P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false);
// Set first busy bits to 1,1
P25Utils::setStatusBits(data, P25_SS0_START, true, true);
// add status bits
P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false, true);
P25Utils::setStatusBitsStartIdle(data);
if (m_debug) {
LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X",

@ -142,11 +142,26 @@ namespace network
uint8_t* buffer;
uint32_t bufferLen;
/**
* @brief RTP Packet Sequence.
*/
uint16_t pktSeq;
/**
* @brief Call Stream ID.
*/
uint32_t streamId;
/**
* @brief Peer ID.
*/
uint32_t peerId;
/**
* @brief Source ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
};
std::deque<ParrotFrame> m_parrotFrames;
@ -160,16 +175,44 @@ namespace network
public:
system_clock::hrc::hrc_t callStartTime;
system_clock::hrc::hrc_t lastPacket;
/**
* @brief Source ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
/**
* @brief Call Stream ID.
*/
uint32_t streamId;
/**
* @brief Peer ID.
*/
uint32_t peerId;
/**
* @brief Flag indicating this call is active with traffic currently in progress.
*/
bool activeCall;
/**
* @brief Helper to reset call status.
*/
void reset()
{
srcId = 0U;
dstId = 0U;
streamId = 0U;
peerId = 0U;
activeCall = false;
}
};
typedef std::pair<const uint32_t, RxStatus> StatusMapPair;
std::unordered_map<uint32_t, RxStatus> m_status;
friend class packetdata::P25PacketData;
packetdata::P25PacketData *m_packetData;
packetdata::P25PacketData* m_packetData;
bool m_debug;

@ -1 +1 @@
Subproject commit 8269757ac2e9e77ad48da3934197c306b3184fc0
Subproject commit a35f0d95ee782efb2097c32a621e34cc8dad60b8

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "ActivityLog.h"
@ -76,7 +76,7 @@ static bool ActivityLogOpen()
}
char filename[200U];
::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", LogGetFilePath().c_str(), LogGetFileRoot().c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_actFilePath.c_str(), m_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
m_actFpLog = ::fopen(filename, "a+t");
m_actTm = *tm;

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@ -432,7 +432,7 @@ bool Host::createModem()
uint8_t afcRange = (uint8_t)hotspotParams["afcRange"].as<uint32_t>(1U);
int rxTuning = hotspotParams["rxTuning"].as<int>(0);
int txTuning = hotspotParams["txTuning"].as<int>(0);
uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as<uint32_t>(100U);
uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as<uint32_t>(95U);
yaml::Node repeaterParams = modemConf["repeater"];
@ -461,8 +461,11 @@ bool Host::createModem()
bool rtrt = dfsiParams["rtrt"].as<bool>(true);
bool diu = dfsiParams["diu"].as<bool>(true);
uint16_t jitter = dfsiParams["jitter"].as<uint16_t>(200U);
bool useFSCForUDP = dfsiParams["useFSC"].as<bool>(false);
uint16_t dfsiCallTimeout = dfsiParams["callTimeout"].as<uint16_t>(200U);
bool useFSCForUDP = dfsiParams["fsc"].as<bool>(false);
uint32_t fscHeartbeat = dfsiParams["fscHeartbeat"].as<uint32_t>(5U);
bool fscInitiator = dfsiParams["initiator"].as<bool>(false);
bool dfsiTIAMode = dfsiParams["dfsiTIAMode"].as<bool>(false);
// clamp fifo sizes
if (dmrFifoLength < DMR_TX_BUFFER_LEN) {
@ -552,12 +555,17 @@ bool Host::createModem()
}
if (portType == PTY_PORT) {
modemPort = new port::UARTPort(uartPort, serialSpeed, false);
modemPort = new port::UARTPort(uartPort, serialSpeed, false, false);
LogInfo(" PTY Port: %s", uartPort.c_str());
LogInfo(" PTY Speed: %u", uartSpeed);
}
else {
modemPort = new port::UARTPort(uartPort, serialSpeed, true);
if (modemMode == MODEM_MODE_DFSI) {
modemPort = new port::UARTPort(uartPort, serialSpeed, false, true);
LogInfo(" RTS/DTR boot flags enabled");
} else {
modemPort = new port::UARTPort(uartPort, serialSpeed, true, false);
}
LogInfo(" UART Port: %s", uartPort.c_str());
LogInfo(" UART Speed: %u", uartSpeed);
}
@ -575,6 +583,9 @@ bool Host::createModem()
LogInfo(" DFSI Jitter Size: %u ms", jitter);
if (g_remoteModemMode) {
LogInfo(" DFSI Use FSC: %s", useFSCForUDP ? "yes" : "no");
LogInfo(" DFSI FSC Heartbeat: %us", fscHeartbeat);
LogInfo(" DFSI FSC Initiator: %s", fscInitiator ? "yes" : "no");
LogInfo(" DFSI TIA-102 Frames: %s", dfsiTIAMode ? "yes" : "no");
}
}
@ -589,8 +600,13 @@ bool Host::createModem()
if (modemMode == MODEM_MODE_DFSI) {
yaml::Node networkConf = m_conf["network"];
uint32_t id = networkConf["id"].as<uint32_t>(1000U);
modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, g_remotePort, useFSCForUDP, debug);
m_udpDSFIRemotePort = modemPort;
if (useFSCForUDP) {
modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, true, fscInitiator, debug);
((modem::port::specialized::V24UDPPort*)modemPort)->setHeartbeatInterval(fscHeartbeat);
} else {
modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, false, false, debug);
}
m_udpDFSIRemotePort = modemPort;
} else {
modemPort = new port::UDPPort(g_remoteAddress, g_remotePort);
}
@ -620,7 +636,7 @@ bool Host::createModem()
LogInfo(" RX Coarse: %u, Fine: %u", rxCoarse, rxFine);
LogInfo(" TX Coarse: %u, Fine: %u", txCoarse, txFine);
LogInfo(" RSSI Coarse: %u, Fine: %u", rssiCoarse, rssiFine);
LogInfo(" RF Power Level: %u", rfPower);
LogInfo(" RF Power Level: %u%", rfPower);
LogInfo(" RX Level: %.1f%%", rxLevel);
LogInfo(" CW Id TX Level: %.1f%%", cwIdTXLevel);
LogInfo(" DMR TX Level: %.1f%%", dmrTXLevel);
@ -651,6 +667,7 @@ bool Host::createModem()
m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, m_p25QueueSizeBytes, rtrt, diu, jitter,
dumpModemStatus, trace, debug);
((ModemV24*)m_modem)->setCallTimeout(dfsiCallTimeout);
((ModemV24*)m_modem)->setTIAFormat(dfsiTIAMode);
} else {
m_modem = new Modem(modemPort, m_duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, fdmaPreamble, dmrRxDelay, p25CorrCount,
m_dmrQueueSizeBytes, m_p25QueueSizeBytes, m_nxdnQueueSizeBytes, disableOFlowReset, ignoreModemConfigArea, dumpModemStatus, trace, debug);
@ -676,6 +693,11 @@ bool Host::createModem()
m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(Host::rmtPortModemHandler, this));
}
if (useFSCForUDP) {
modem::port::specialized::V24UDPPort* udpPort = dynamic_cast<modem::port::specialized::V24UDPPort*>(m_udpDFSIRemotePort);
udpPort->openFSC();
}
bool ret = m_modem->open();
if (!ret) {
delete m_modem;

@ -64,7 +64,7 @@ Host::Host(const std::string& confFile) :
m_modem(nullptr),
m_modemRemote(false),
m_isModemDFSI(false),
m_udpDSFIRemotePort(nullptr),
m_udpDFSIRemotePort(nullptr),
m_network(nullptr),
m_modemRemotePort(nullptr),
m_state(STATE_IDLE),
@ -247,7 +247,7 @@ int Host::run()
#endif // !defined(_WIN32)
::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \
"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \
">> Modem Controller\r\n");
@ -953,10 +953,9 @@ int Host::run()
}
}
if (m_udpDSFIRemotePort != nullptr) {
if (m_udpDFSIRemotePort != nullptr) {
m_mainLoopStage = 11U; // intentional magic number
modem::port::specialized::V24UDPPort* udpPort = dynamic_cast<modem::port::specialized::V24UDPPort*>(m_udpDSFIRemotePort);
modem::port::specialized::V24UDPPort* udpPort = dynamic_cast<modem::port::specialized::V24UDPPort*>(m_udpDFSIRemotePort);
udpPort->clock(ms);
}
@ -1646,9 +1645,14 @@ void Host::setState(uint8_t state)
m_modeTimer.stop();
if (m_state == HOST_STATE_QUIT) {
if (state == HOST_STATE_QUIT) {
::LogInfoEx(LOG_HOST, "Host is shutting down");
if (m_udpDFSIRemotePort != nullptr) {
modem::port::specialized::V24UDPPort* udpPort = dynamic_cast<modem::port::specialized::V24UDPPort*>(m_udpDFSIRemotePort);
udpPort->closeFSC();
}
if (m_modem != nullptr) {
m_modem->close();
delete m_modem;
@ -1671,11 +1675,11 @@ void Host::setState(uint8_t state)
if (m_tidLookup != nullptr) {
m_tidLookup->stop();
delete m_tidLookup;
//delete m_tidLookup;
}
if (m_ridLookup != nullptr) {
m_ridLookup->stop();
delete m_ridLookup;
//delete m_ridLookup;
}
}
else {

@ -99,7 +99,7 @@ private:
modem::Modem* m_modem;
bool m_modemRemote;
bool m_isModemDFSI;
modem::port::IModemPort* m_udpDSFIRemotePort;
modem::port::IModemPort* m_udpDFSIRemotePort;
network::Network* m_network;
modem::port::IModemPort* m_modemRemotePort;

@ -99,7 +99,7 @@ void fatal(const char* msg, ...)
void usage(const char* message, const char* arg)
{
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (message != nullptr) {
::fprintf(stderr, "%s: ", g_progExe.c_str());
@ -216,7 +216,7 @@ int checkArgs(int argc, char* argv[])
}
else if (IS("-v")) {
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n");
if (argc == 2)
exit(EXIT_SUCCESS);

@ -86,12 +86,15 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s
/* Helper to release the channel grant for the destination ID. */
bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock)
{
if (dstId == 0U && !releaseAll) {
return false;
}
if (!noLock)
m_mutex.lock();
// are we trying to release all grants?
if (dstId == 0U && releaseAll) {
LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str());
@ -107,6 +110,8 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
releaseGrant(dstId, false);
}
if (!noLock)
m_mutex.unlock();
return true;
}
@ -139,9 +144,14 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
}
m_grantTimers[dstId].stop();
if (!noLock)
m_mutex.unlock();
return true;
}
if (!noLock)
m_mutex.unlock();
return false;
}

@ -76,9 +76,10 @@ namespace dmr
* @brief Helper to release the channel grant for the destination ID.
* @param dstId Destination Address.
* @param releaseAll Flag indicating all channel grants should be released.
* @param noLock Flag indicating no mutex lock operation should be performed while releasing.
* @returns bool True, if channel grant was released, otherwise false.
*/
bool releaseGrant(uint32_t dstId, bool releaseAll) override;
bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false) override;
/**
* @brief Helper to determine if the channel number is busy.
* @param chNo Channel Number.

@ -868,6 +868,14 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
}
if (!grp && !m_tscc->m_ignoreAffiliationCheck) {
// is this the target registered?
if (!m_tscc->m_affiliations->isUnitReg(dstId)) {
LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, IND_VOICE_CALL (Individual Voice Call) ignored, no unit registration, dstId = %u", m_tscc->m_slotNo, dstId);
return false;
}
}
uint32_t availChNo = m_tscc->m_affiliations->getAvailableChannelForSlot(slot);
if (!m_tscc->m_affiliations->rfCh()->isRFChAvailable() || availChNo == 0U) {
if (grp) {

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -261,6 +261,12 @@ namespace modem
*/
void setP25NAC(uint32_t nac) override;
/**
* @brief Helper to set the TIA-102 format DFSI frame flag.
* @param set
*/
void setTIAFormat(bool set);
/**
* @brief Opens connection to the air interface modem.
* @returns bool True, if connection to modem is made, otherwise false.
@ -296,6 +302,8 @@ namespace modem
bool m_rtrt;
bool m_diu;
uint8_t m_superFrameCnt;
p25::Audio m_audio;
p25::NID* m_nid;
@ -316,6 +324,8 @@ namespace modem
edac::RS634717 m_rs;
bool m_useTIAFormat;
/**
* @brief Helper to write data from the P25 Tx queue to the serial interface.
* @return int Actual number of bytes written to the serial interface.
@ -340,6 +350,12 @@ namespace modem
* @param length Length of buffer.
*/
void convertToAir(const uint8_t *data, uint32_t length);
/**
* @brief Internal helper to convert from TIA-102 DFSI to TIA-102 air interface.
* @param data Buffer containing data to convert.
* @param length Length of buffer.
*/
void convertToAirTIA(const uint8_t *data, uint32_t length);
/**
* @brief Helper to add a V.24 data frame to the P25 Tx queue with the proper timestamp and formatting.
@ -359,12 +375,35 @@ namespace modem
*/
void endOfStream();
/**
* @brief Helper to generate the NID value.
* @param duid P25 DUID.
* @returns uint16_t P25 NID.
*/
uint16_t generateNID(P25DEF::DUID::E duid = P25DEF::DUID::LDU1);
/**
* @brief Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device.
* @param[in] control Instance of p25::lc::LC containing link control data.
*/
void startOfStreamTIA(const p25::lc::LC& control);
/**
* @brief Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device.
*/
void endOfStreamTIA();
/**
* @brief Internal helper to convert from TIA-102 air interface to V.24/DFSI.
* @param data Buffer containing data to convert.
* @param length Length of buffer.
*/
void convertFromAir(uint8_t* data, uint32_t length);
/**
* @brief Internal helper to convert from TIA-102 air interface to TIA-102 DFSI.
* @param data Buffer containing data to convert.
* @param length Length of buffer.
*/
void convertFromAirTIA(uint8_t* data, uint32_t length);
};
} // namespace modem

@ -30,7 +30,7 @@ using namespace modem::port;
/* Initializes a new instance of the PseudoPTYPort class. */
PseudoPTYPort::PseudoPTYPort(const std::string& symlink, SERIAL_SPEED speed, bool assertRTS) : UARTPort(speed, assertRTS),
PseudoPTYPort::PseudoPTYPort(const std::string& symlink, SERIAL_SPEED speed, bool assertRTS) : UARTPort(speed, assertRTS, false),
m_symlink(symlink)
{
/* stub */

@ -37,11 +37,12 @@ using namespace modem::port;
/* Initializes a new instance of the UARTPort class. */
UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS) :
UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS, bool rtsBoot) :
m_isOpen(false),
m_device(device),
m_speed(speed),
m_assertRTS(assertRTS),
m_rtsBoot(rtsBoot),
#if defined(_WIN32)
m_fd(INVALID_HANDLE_VALUE)
#else
@ -334,10 +335,11 @@ int UARTPort::setNonblock(bool nonblock)
/* Initializes a new instance of the UARTPort class. */
UARTPort::UARTPort(SERIAL_SPEED speed, bool assertRTS) :
UARTPort::UARTPort(SERIAL_SPEED speed, bool assertRTS, bool rtsBoot) :
m_isOpen(false),
m_speed(speed),
m_assertRTS(assertRTS),
m_rtsBoot(rtsBoot),
#if defined(_WIN32)
m_fd(INVALID_HANDLE_VALUE)
#else
@ -500,6 +502,40 @@ bool UARTPort::setTermios()
}
}
// Special setting of RTS/DTR for DVM-V24 boards with onboard DTR/RTS boot connections
if (m_rtsBoot) {
::LogInfoEx(LOG_MODEM, "RTS/DTR boot flag enabled, forcing board reset");
uint32_t y;
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str());
::close(m_fd);
return false;
}
// Force RTS bit off
y &= ~TIOCM_RTS;
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str());
::close(m_fd);
return false;
}
// Toggle DTR to force second reset
y &= ~TIOCM_DTR;
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str());
::close(m_fd);
return false;
}
y |= TIOCM_DTR;
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str());
::close(m_fd);
return false;
}
}
#if defined(__APPLE__)
setNonblock(false);
#endif

@ -76,7 +76,7 @@ namespace modem
* @param speed Serial port speed.
* @param assertRTS
*/
UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS = false);
UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS = false, bool rtsBoot = false);
/**
* @brief Finalizes a instance of the UARTPort class.
*/
@ -122,13 +122,14 @@ namespace modem
* @param speed Serial port speed.
* @param assertRTS
*/
UARTPort(SERIAL_SPEED speed, bool assertRTS = false);
UARTPort(SERIAL_SPEED speed, bool assertRTS = false, bool rtsBoot = false);
bool m_isOpen;
std::string m_device;
SERIAL_SPEED m_speed;
bool m_assertRTS;
bool m_rtsBoot;
#if defined(_WIN32)
HANDLE m_fd;
#else

@ -4,7 +4,7 @@
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@ -38,13 +38,19 @@ const uint32_t BUFFER_LENGTH = 2000U;
const char* V24_UDP_HARDWARE = "V.24 UDP Modem Controller";
const uint8_t V24_UDP_PROTOCOL_VERSION = 4U;
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::mutex V24UDPPort::m_bufferMutex;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the V24UDPPort class. */
V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t modemPort, uint16_t controlPort, bool useFSC, bool debug) :
V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t modemPort, uint16_t controlPort, bool useFSC, bool fscInitiator, bool debug) :
m_socket(nullptr),
m_localPort(modemPort),
m_controlSocket(nullptr),
@ -55,15 +61,17 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod
m_addrLen(0U),
m_ctrlAddrLen(0U),
m_buffer(2000U, "UDP Port Ring Buffer"),
m_fscInitiator(fscInitiator),
m_timeoutTimer(1000U, 45U),
m_reqConnectionTimer(1000U, 30U),
m_heartbeatInterval(5U),
m_heartbeatTimer(1000U, 5U),
m_reqConnectionToPeer(true),
m_establishedConnection(false),
m_random(),
m_peerId(peerId),
m_streamId(0U),
m_timestamp(INVALID_TS),
m_pktSeq(0U),
m_fscState(CS_NOT_CONNECTED),
m_modemState(STATE_P25),
m_tx(false),
m_debug(debug)
@ -71,7 +79,6 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod
assert(peerId > 0U);
assert(!address.empty());
assert(modemPort > 0U);
assert(controlPort > 0U);
if (controlPort > 0U && useFSC) {
m_controlSocket = new Socket(controlPort);
@ -108,13 +115,26 @@ V24UDPPort::~V24UDPPort()
delete m_socket;
}
/* Helper to set and configure the heartbeat interval for FSC connections. */
void V24UDPPort::setHeartbeatInterval(uint32_t interval)
{
if (interval < 5U)
interval = 5U;
if (interval > 30U)
interval = 30U;
m_heartbeatInterval = interval;
m_heartbeatTimer = Timer(1000U, interval);
}
/* Updates the timer by the passed number of milliseconds. */
void V24UDPPort::clock(uint32_t ms)
{
// if we have a FSC control socket
if (m_controlSocket != nullptr) {
if (m_reqConnectionToPeer) {
if ((m_fscState == CS_NOT_CONNECTED) && m_fscInitiator) {
if (!m_reqConnectionTimer.isRunning()) {
// make initial request
writeConnect();
@ -124,11 +144,12 @@ void V24UDPPort::clock(uint32_t ms)
if (m_reqConnectionTimer.isRunning() && m_reqConnectionTimer.hasExpired()) {
// make another request
writeConnect();
m_reqConnectionTimer.start();
}
}
}
if (m_establishedConnection) {
if (m_fscState == CS_CONNECTED) {
m_heartbeatTimer.clock(ms);
if (m_heartbeatTimer.isRunning() && m_heartbeatTimer.hasExpired()) {
writeHeartbeat();
@ -136,63 +157,22 @@ void V24UDPPort::clock(uint32_t ms)
}
}
m_timeoutTimer.clock(ms);
if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) {
LogError(LOG_NET, "V.24 UDP, DFSI connection to the remote endpoint has timed out, disconnected");
m_fscState = CS_NOT_CONNECTED;
if (!m_fscInitiator)
m_reqConnectionTimer.stop();
else
m_reqConnectionTimer.start();
m_heartbeatTimer.stop();
m_timeoutTimer.stop();
}
processCtrlNetwork();
}
// if we have a RTP voice socket
if (m_socket != nullptr) {
uint8_t data[BUFFER_LENGTH];
::memset(data, 0x00U, BUFFER_LENGTH);
sockaddr_storage addr;
uint32_t addrLen;
int ret = m_socket->read(data, BUFFER_LENGTH, addr, addrLen);
if (ret != 0) {
// An error occurred on the socket
if (ret < 0)
return;
// Add new data to the ring buffer
if (ret > 0) {
RTPHeader rtpHeader = RTPHeader();
rtpHeader.decode(data);
// ensure payload type is correct
if (rtpHeader.getPayloadType() != DFSI_RTP_PAYLOAD_TYPE)
{
LogError(LOG_MODEM, "Invalid RTP header received from network");
return;
}
// copy message
uint32_t messageLength = ret - RTP_HEADER_LENGTH_BYTES;
UInt8Array __message = std::make_unique<uint8_t[]>(messageLength);
uint8_t* message = __message.get();
::memset(message, 0x00U, messageLength);
::memcpy(message, data + RTP_HEADER_LENGTH_BYTES, messageLength);
if (udp::Socket::match(addr, m_addr)) {
UInt8Array __reply = std::make_unique<uint8_t[]>(messageLength + 4U);
uint8_t* reply = __reply.get();
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = messageLength & 0xFFU;
reply[2U] = CMD_P25_DATA;
reply[3U] = 0x00U;
::memcpy(reply + 4U, message, messageLength);
m_buffer.addData(reply, messageLength + 4U);
}
else {
std::string addrStr = udp::Socket::address(addr);
LogWarning(LOG_HOST, "SECURITY: Remote modem mode encountered invalid IP address; %s", addrStr.c_str());
}
}
}
}
processVCNetwork();
}
/* Resets the RTP packet sequence and stream ID. */
@ -204,18 +184,34 @@ void V24UDPPort::reset()
m_streamId = createStreamId();
}
/* Opens a connection to the port. */
/* Opens a connection to the FSC port. */
bool V24UDPPort::open()
bool V24UDPPort::openFSC()
{
if (m_addrLen == 0U && m_ctrlAddrLen == 0U) {
if (m_ctrlAddrLen == 0U) {
LogError(LOG_NET, "Unable to resolve the address of the modem");
return false;
}
if (m_controlSocket != nullptr) {
return m_controlSocket->open(m_controlAddr);
}
return false;
}
/* Opens a connection to the port. */
bool V24UDPPort::open()
{
if (m_controlSocket != nullptr) {
return true; // FSC mode always returns that the port was opened
} else {
if (m_addrLen == 0U) {
LogError(LOG_NET, "Unable to resolve the address of the modem");
return false;
}
if (m_socket != nullptr) {
return m_socket->open(m_addr);
}
@ -231,6 +227,8 @@ int V24UDPPort::read(uint8_t* buffer, uint32_t length)
assert(buffer != nullptr);
assert(length > 0U);
std::lock_guard<std::mutex> lock(m_bufferMutex);
// Get required data from the ring buffer
uint32_t avail = m_buffer.dataSize();
if (avail < length)
@ -264,7 +262,10 @@ int V24UDPPort::write(const uint8_t* buffer, uint32_t length)
{
if (m_socket != nullptr) {
uint32_t messageLen = 0U;
uint8_t* message = generateMessage(buffer + 3U, length - 3U, m_streamId, m_peerId, m_pktSeq, &messageLen);
uint8_t* message = generateMessage(buffer + 4U, length - 4U, m_streamId, m_peerId, m_pktSeq, &messageLen);
if (m_debug)
Utils::dump(1U, "!!! Tx Outgoing DFSI UDP", buffer + 4U, length - 4U);
bool written = m_socket->write(message, messageLen, m_addr, m_addrLen);
if (written)
@ -285,12 +286,33 @@ int V24UDPPort::write(const uint8_t* buffer, uint32_t length)
return -1;
}
/* Closes the connection to the FSC port. */
void V24UDPPort::closeFSC()
{
if (m_controlSocket != nullptr) {
if (m_fscState == CS_CONNECTED) {
LogMessage(LOG_MODEM, "V.24 UDP, Closing DFSI FSC Connection, vcBasePort = %u", m_localPort);
FSCDisconnect discoMessage = FSCDisconnect();
uint8_t buffer[FSCDisconnect::LENGTH];
::memset(buffer, 0x00U, FSCDisconnect::LENGTH);
discoMessage.encode(buffer);
m_ctrlFrameQueue->write(buffer, FSCDisconnect::LENGTH, m_controlAddr, m_ctrlAddrLen);
Thread::sleep(500U);
}
m_controlSocket->close();
}
}
/* Closes the connection to the port. */
void V24UDPPort::close()
{
if (m_controlSocket != nullptr)
m_controlSocket->close();
if (m_socket != nullptr)
m_socket->close();
}
@ -348,105 +370,322 @@ void* V24UDPPort::threadedCtrlNetworkRx(void* arg)
}
if (req->length > 0) {
if (network->m_reqConnectionToPeer) {
// FSC_CONNECT response -- is ... strange
if (req->buffer[0] == 1U) {
network->m_reqConnectionToPeer = false;
network->m_reqConnectionTimer.stop();
network->m_establishedConnection = true;
FSCConnectResponse resp = FSCConnectResponse(req->buffer);
uint16_t vcBasePort = resp.getVCBasePort();
network->m_localPort = vcBasePort;
network->createVCPort(vcBasePort);
network->m_heartbeatTimer.start();
uint8_t buffer[FSCConnectResponse::LENGTH];
::memset(buffer, 0x00U, FSCConnectResponse::LENGTH);
resp.setVCBasePort(network->m_localPort);
resp.encode(buffer);
network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen);
}
}
else
{
std::unique_ptr<FSCMessage> message = FSCMessage::createMessage(req->buffer);
if (message != nullptr) {
switch (message->getMessageId())
std::unique_ptr<FSCMessage> message = FSCMessage::createMessage(req->buffer);
if (message != nullptr) {
switch (message->getMessageId()) {
case FSCMessageType::FSC_ACK:
{
case FSCMessageType::FSC_ACK:
FSCACK* ackMessage = static_cast<FSCACK*>(message.get());
if (network->m_debug)
LogDebug(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X, respLength = %u", ackMessage->getAckMessageId(), ackMessage->getResponseCode(), ackMessage->getResponseLength());
switch (ackMessage->getResponseCode()) {
case FSCAckResponseCode::CONTROL_NAK:
case FSCAckResponseCode::CONTROL_NAK_CONNECTED:
case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_PARMS:
case FSCAckResponseCode::CONTROL_NAK_BUSY:
LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode());
break;
case FSCAckResponseCode::CONTROL_ACK:
{
FSCACK* ackMessage = static_cast<FSCACK*>(message.get());
switch (ackMessage->getResponseCode())
{
case FSCAckResponseCode::CONTROL_NAK:
case FSCAckResponseCode::CONTROL_NAK_CONNECTED:
case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_PARMS:
case FSCAckResponseCode::CONTROL_NAK_BUSY:
LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode());
break;
switch (ackMessage->getAckMessageId()) {
case FSCMessageType::FSC_CONNECT:
{
uint16_t vcBasePort = __GET_UINT16B(ackMessage->responseData, 1U);
if (network->m_socket != nullptr) {
network->m_socket->close();
delete network->m_socket;
network->m_socket = nullptr;
}
case FSCAckResponseCode::CONTROL_ACK:
{
if (ackMessage->getAckMessageId() == FSCMessageType::FSC_DISCONNECT) {
network->m_reqConnectionTimer.stop();
network->m_reqConnectionToPeer = false;
network->m_establishedConnection = false;
network->m_heartbeatTimer.stop();
}
network->m_localPort = vcBasePort;
network->createVCPort(vcBasePort);
network->m_socket->open(network->m_addr);
network->m_fscState = CS_CONNECTED;
network->m_reqConnectionTimer.stop();
network->m_heartbeatTimer.start();
network->m_timeoutTimer.start();
LogMessage(LOG_MODEM, "V.24 UDP, Established DFSI FSC Connection, vcBasePort = %u", vcBasePort);
}
break;
case FSCMessageType::FSC_DISCONNECT:
{
if (network->m_socket != nullptr) {
network->m_socket->close();
delete network->m_socket;
network->m_socket = nullptr;
}
break;
network->m_fscState = CS_NOT_CONNECTED;
if (!network->m_fscInitiator)
network->m_reqConnectionTimer.stop();
else
network->m_reqConnectionTimer.start();
network->m_heartbeatTimer.stop();
network->m_timeoutTimer.stop();
}
break;
default:
LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId());
break;
}
}
break;
case FSCMessageType::FSC_CONNECT:
{
network->createVCPort(network->m_localPort);
network->m_heartbeatTimer.start();
default:
LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId());
break;
}
}
break;
uint8_t buffer[FSCConnectResponse::LENGTH];
::memset(buffer, 0x00U, FSCConnectResponse::LENGTH);
case FSCMessageType::FSC_CONNECT:
{
FSCConnect* connMessage = static_cast<FSCConnect*>(message.get());
FSCACK ackResp = FSCACK();
ackResp.setCorrelationTag(connMessage->getCorrelationTag());
ackResp.setAckMessageId(FSCMessageType::FSC_CONNECT);
ackResp.setResponseCode(FSCAckResponseCode::CONTROL_ACK);
ackResp.setAckCorrelationTag(connMessage->getCorrelationTag());
FSCConnectResponse resp = FSCConnectResponse(req->buffer);
resp.setVCBasePort(network->m_localPort);
resp.encode(buffer);
if (connMessage->getVersion() != 1U) {
ackResp.setResponseCode(FSCAckResponseCode::CONTROL_NAK_V_UNSUPP);
network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen);
}
break;
uint8_t buffer[FSCACK::LENGTH];
::memset(buffer, 0x00U, FSCACK::LENGTH);
ackResp.encode(buffer);
case FSCMessageType::FSC_DISCONNECT:
{
network->m_reqConnectionTimer.stop();
network->m_reqConnectionToPeer = false;
network->m_establishedConnection = false;
network->m_heartbeatTimer.stop();
}
network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen);
break;
}
case FSCMessageType::FSC_HEARTBEAT:
{
if (network->m_establishedConnection) {
network->writeHeartbeat();
}
}
break;
if (network->m_socket != nullptr) {
network->m_socket->close();
delete network->m_socket;
network->m_socket = nullptr;
}
default:
break;
uint16_t vcPort = connMessage->getVCBasePort();
network->m_heartbeatInterval = connMessage->getHostHeartbeatPeriod();
if (network->m_heartbeatInterval > 30U)
network->m_heartbeatInterval = 30U;
network->m_localPort = vcPort;
network->createVCPort(network->m_localPort);
network->m_socket->open(network->m_addr);
network->m_fscState = CS_CONNECTED;
network->m_reqConnectionTimer.stop();
if (connMessage->getHostHeartbeatPeriod() > 30U)
LogWarning(LOG_MODEM, "V.24 UDP, DFSI FSC Connection, requested heartbeat of %u, reduce to 30 seconds or less", connMessage->getHostHeartbeatPeriod());
network->m_heartbeatTimer = Timer(1000U, network->m_heartbeatInterval);
network->m_heartbeatTimer.start();
network->m_timeoutTimer.start();
LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, vcBasePort = %u, hostHBInterval = %u", network->m_localPort, connMessage->getHostHeartbeatPeriod());
// construct connect ACK response data
uint8_t respData[3U];
::memset(respData, 0x00U, 3U);
respData[0U] = 1U; // Version 1
__SET_UINT16B(network->m_localPort, respData, 1U);
// pack ack
ackResp.setResponseLength(3U);
ackResp.responseData = respData;
uint8_t buffer[FSCACK::LENGTH + 3U];
::memset(buffer, 0x00U, FSCACK::LENGTH + 3U);
ackResp.encode(buffer);
network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH + 3U, network->m_controlAddr, network->m_ctrlAddrLen);
}
break;
case FSCMessageType::FSC_SEL_CHAN:
{
FSCACK ackResp = FSCACK();
ackResp.setCorrelationTag(message->getCorrelationTag());
ackResp.setAckMessageId(FSCMessageType::FSC_SEL_CHAN);
ackResp.setResponseCode(FSCAckResponseCode::CONTROL_ACK);
ackResp.setAckCorrelationTag(message->getCorrelationTag());
ackResp.setResponseLength(0U);
uint8_t buffer[FSCACK::LENGTH];
::memset(buffer, 0x00U, FSCACK::LENGTH);
ackResp.encode(buffer);
network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen);
}
break;
case FSCMessageType::FSC_REPORT_SEL_MODES:
{
FSCACK ackResp = FSCACK();
ackResp.setCorrelationTag(message->getCorrelationTag());
ackResp.setAckMessageId(FSCMessageType::FSC_REPORT_SEL_MODES);
ackResp.setResponseCode(FSCAckResponseCode::CONTROL_ACK);
ackResp.setAckCorrelationTag(message->getCorrelationTag());
// construct connect ACK response data
uint8_t respData[5U];
::memset(respData, 0x00U, 5U);
// bryanb: because DVM is essentially a repeater -- we hardcode these values
respData[0U] = 1U; // Version 1
respData[1U] = 1U; // Repeat Inbound Audio
respData[2U] = 0U; // Rx Channel Selection
respData[3U] = 0U; // Tx Channel Selection
respData[4U] = 1U; // Monitor Mode
// pack ack
ackResp.setResponseLength(5U);
ackResp.responseData = respData;
uint8_t buffer[FSCACK::LENGTH + 5U];
::memset(buffer, 0x00U, FSCACK::LENGTH + 5U);
ackResp.encode(buffer);
network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH + 5U, network->m_controlAddr, network->m_ctrlAddrLen);
}
break;
case FSCMessageType::FSC_DISCONNECT:
{
LogMessage(LOG_MODEM, "V.24 UDP, DFSI FSC Disconnect, vcBasePort = %u", network->m_localPort);
if (network->m_socket != nullptr) {
network->m_socket->close();
delete network->m_socket;
network->m_socket = nullptr;
}
network->m_fscState = CS_NOT_CONNECTED;
network->m_reqConnectionTimer.stop();
network->m_heartbeatTimer.stop();
network->m_timeoutTimer.stop();
}
break;
case FSCMessageType::FSC_HEARTBEAT:
network->m_timeoutTimer.start();
break;
default:
break;
}
}
}
if (req->buffer != nullptr)
delete[] req->buffer;
delete req;
}
return nullptr;
}
/* Process voice conveyance frames from the network. */
void V24UDPPort::processVCNetwork()
{
// if we have a RTP voice socket
if (m_socket != nullptr) {
uint8_t data[BUFFER_LENGTH];
::memset(data, 0x00U, BUFFER_LENGTH);
sockaddr_storage addr;
uint32_t addrLen;
int ret = m_socket->read(data, BUFFER_LENGTH, addr, addrLen);
if (ret != 0) {
// An error occurred on the socket
if (ret < 0)
return;
// Add new data to the ring buffer
if (ret > 0) {
if (m_debug)
Utils::dump("!!! Rx Incoming DFSI UDP", data, ret);
V24PacketRequest* req = new V24PacketRequest();
req->address = addr;
req->addrLen = addrLen;
req->rtpHeader = RTPHeader();
req->rtpHeader.decode(data);
// ensure payload type is correct
if (req->rtpHeader.getPayloadType() != DFSI_RTP_PAYLOAD_TYPE) {
LogError(LOG_MODEM, "Invalid RTP header received from network");
delete req;
return;
}
// copy message
req->length = ret - RTP_HEADER_LENGTH_BYTES;
req->buffer = new uint8_t[req->length];
::memset(req->buffer, 0x00U, req->length);
::memcpy(req->buffer, data + RTP_HEADER_LENGTH_BYTES, req->length);
if (!Thread::runAsThread(this, threadedVCNetworkRx, req)) {
delete[] req->buffer;
delete req;
return;
}
}
}
}
}
/* Process a data frames from the network. */
void* V24UDPPort::threadedVCNetworkRx(void* arg)
{
V24PacketRequest* req = (V24PacketRequest*)arg;
if (req != nullptr) {
#if defined(_WIN32)
::CloseHandle(req->thread);
#else
::pthread_detach(req->thread);
#endif // defined(_WIN32)
V24UDPPort* network = static_cast<V24UDPPort*>(req->obj);
if (network == nullptr) {
delete req;
return nullptr;
}
if (req->length > 0) {
if (udp::Socket::match(req->address, network->m_addr)) {
UInt8Array __reply = std::make_unique<uint8_t[]>(req->length + 4U);
uint8_t* reply = __reply.get();
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = (req->length + 4U) & 0xFFU;
reply[2U] = CMD_P25_DATA;
reply[3U] = 0x00U;
::memcpy(reply + 4U, req->buffer, req->length);
std::lock_guard<std::mutex> lock(m_bufferMutex);
network->m_buffer.addData(reply, req->length + 4U);
}
else {
std::string addrStr = udp::Socket::address(req->address);
LogWarning(LOG_HOST, "SECURITY: Remote modem mode encountered invalid IP address; %s", addrStr.c_str());
}
}
@ -477,9 +716,11 @@ void V24UDPPort::createVCPort(uint16_t port)
void V24UDPPort::writeConnect()
{
LogMessage(LOG_MODEM, "V.24 UDP, Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort);
FSCConnect connect = FSCConnect();
connect.setFSHeartbeatPeriod(5U); // hardcoded?
connect.setHostHeartbeatPeriod(5U); // hardcoded?
connect.setFSHeartbeatPeriod(m_heartbeatInterval);
connect.setHostHeartbeatPeriod(m_heartbeatInterval);
connect.setVCBasePort(m_localPort);
connect.setVCSSRC(m_peerId);
@ -488,6 +729,8 @@ void V24UDPPort::writeConnect()
connect.encode(buffer);
m_fscState = CS_CONNECTING;
m_ctrlFrameQueue->write(buffer, FSCConnect::LENGTH, m_controlAddr, m_ctrlAddrLen);
}
@ -580,6 +823,7 @@ void V24UDPPort::getVersion()
reply[1U] = count;
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_buffer.addData(reply, count);
}
@ -612,6 +856,7 @@ void V24UDPPort::getStatus()
reply[11U] = 0U;
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_buffer.addData(reply, 12U);
}
@ -626,6 +871,7 @@ void V24UDPPort::writeAck(uint8_t type)
reply[2U] = CMD_ACK;
reply[3U] = type;
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_buffer.addData(reply, 4U);
}
@ -641,5 +887,6 @@ void V24UDPPort::writeNAK(uint8_t opcode, uint8_t err)
reply[3U] = opcode;
reply[4U] = err;
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_buffer.addData(reply, 5U);
}

@ -8,7 +8,7 @@
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
/**
@ -31,6 +31,7 @@
#include <string>
#include <random>
#include <mutex>
namespace modem
{
@ -71,14 +72,20 @@ namespace modem
* @param modemPort Port number.
* @param controlPort Control Port number.
* @param useFSC Flag indicating whether or not FSC handshakes are used to setup communications.
* @param fscInitiator Flag indicating whether or not the FSC handshake should be initiated when the port is opened.
* @param debug Flag indicating whether network debug is enabled.
*/
V24UDPPort(uint32_t peerId, const std::string& modemAddress, uint16_t modemPort, uint16_t controlPort = 0U, bool useFSC = false, bool debug = false);
V24UDPPort(uint32_t peerId, const std::string& modemAddress, uint16_t modemPort, uint16_t controlPort = 0U, bool useFSC = false, bool fscInitiator = false, bool debug = false);
/**
* @brief Finalizes a instance of the V24UDPPort class.
*/
~V24UDPPort() override;
/**
* @brief Helper to set and configure the heartbeat interval for FSC connections.
*/
void setHeartbeatInterval(uint32_t interval);
/**
* @brief Updates the timer by the passed number of milliseconds.
* @param ms Number of milliseconds.
@ -91,20 +98,25 @@ namespace modem
void reset();
/**
* @brief Opens a connection to the serial port.
* @brief Opens a connection to the FSC port.
* @returns bool True, if connection is opened, otherwise false.
*/
bool openFSC();
/**
* @brief Opens a connection to the port.
* @returns bool True, if connection is opened, otherwise false.
*/
bool open() override;
/**
* @brief Reads data from the serial port.
* @brief Reads data from the port.
* @param[out] buffer Buffer to read data from the port to.
* @param length Length of data to read from the port.
* @returns int Actual length of data read from serial port.
*/
int read(uint8_t* buffer, uint32_t length) override;
/**
* @brief Writes data to the serial port.
* @brief Writes data to the port.
* @param[in] buffer Buffer containing data to write to port.
* @param length Length of data to write to port.
* @returns int Actual length of data written to the port.
@ -112,7 +124,11 @@ namespace modem
int write(const uint8_t* buffer, uint32_t length) override;
/**
* @brief Closes the connection to the serial port.
* @brief Closes the connection to the FSC port.
*/
void closeFSC();
/**
* @brief Closes the connection to the port.
*/
void close() override;
@ -131,12 +147,14 @@ namespace modem
RingBuffer<uint8_t> m_buffer;
bool m_fscInitiator;
Timer m_timeoutTimer;
Timer m_reqConnectionTimer;
uint32_t m_heartbeatInterval;
Timer m_heartbeatTimer;
bool m_reqConnectionToPeer;
bool m_establishedConnection;
std::mt19937 m_random;
uint32_t m_peerId;
@ -145,11 +163,20 @@ namespace modem
uint32_t m_timestamp;
uint16_t m_pktSeq;
enum CS_STATE : uint8_t {
CS_NOT_CONNECTED = 0,
CS_CONNECTING = 1,
CS_CONNECTED = 2
};
CS_STATE m_fscState;
uint8_t m_modemState;
bool m_tx;
bool m_debug;
static std::mutex m_bufferMutex;
/**
* @brief Process FSC control frames from the network.
*/
@ -162,6 +189,18 @@ namespace modem
*/
static void* threadedCtrlNetworkRx(void* arg);
/**
* @brief Process voice conveyance frames from the network.
*/
void processVCNetwork();
/**
* @brief Entry point to process a given network packet.
* @param arg Instance of the NetPacketRequest structure.
* @returns void* (Ignore)
*/
static void* threadedVCNetworkRx(void* arg);
/**
* @brief Internal helper to setup the voice channel port.
* @param port Port number.

@ -479,6 +479,14 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin
}
}
if (!grp && !m_nxdn->m_ignoreAffiliationCheck) {
// is this the target registered?
if (!m_nxdn->m_affiliations.isUnitReg(dstId)) {
LogWarning(LOG_RF, "NXDN, %s ignored, no unit registration, dstId = %u", rcch->toString().c_str(), dstId);
return false;
}
}
if (!m_nxdn->m_affiliations.rfCh()->isRFChAvailable()) {
if (grp) {
if (!net) {

@ -1780,14 +1780,14 @@ void Control::writeRF_TDU(bool noNetwork, bool imm)
uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U];
::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES);
// Generate Sync
// generate Sync
Sync::addP25Sync(data + 2U);
// Generate NID
// generate NID
m_nid.encode(data + 2U, DUID::TDU);
// Add busy bits
P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false);
// add status bits
P25Utils::setStatusBitsAllIdle(data + 2U, P25_TDU_FRAME_LENGTH_BITS);
if (!noNetwork)
m_voice->writeNetwork(data + 2U, DUID::TDU);

@ -46,9 +46,9 @@ std::vector<uint32_t> P25AffiliationLookup::clearGroupAff(uint32_t dstId, bool r
/* Helper to release the channel grant for the destination ID. */
bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll, bool noLock)
{
bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll);
bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll, noLock);
if (ret) {
if (m_rfGrantChCnt > 0U) {
m_p25->m_siteData.setChCnt(m_chLookup->rfChSize() + m_rfGrantChCnt);

@ -72,9 +72,10 @@ namespace p25
* @brief Helper to release the channel grant for the destination ID.
* @param dstId Destination Address.
* @param releaseAll Flag indicating all channel grants should be released.
* @param noLock Flag indicating no mutex lock operation should be performed while releasing.
* @returns bool True, if channel grant was released, otherwise false.
*/
bool releaseGrant(uint32_t dstId, bool releaseAll) override;
bool releaseGrant(uint32_t dstId, bool releaseAll, bool noLock = false) override;
/** @} */
protected:

@ -548,6 +548,12 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBKO::ISP_GRP_AFF_Q_RSP, srcId);
// validate the source RID
VALID_SRCID(tsbk->toString(true), TSBKO::IOSP_ACK_RSP, srcId);
// validate the target RID
VALID_DSTID(tsbk->toString(true), TSBKO::IOSP_ACK_RSP, srcId, dstId);
if (m_p25->m_ackTSBKRequests) {
writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_GRP_AFF_Q_RSP, true, true);
}
@ -559,6 +565,14 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
}
::ActivityLog("P25", true, "group affiliation query response from %u to %s %u", srcId, "TG ", dstId);
if (!m_p25->m_affiliations.isGroupAff(srcId, dstId)) {
// update dynamic affiliation table
m_p25->m_affiliations.groupAff(srcId, dstId);
if (m_p25->m_network != nullptr)
m_p25->m_network->announceGroupAffiliation(srcId, dstId);
}
}
break;
case TSBKO::ISP_U_DEREG_REQ:
@ -773,15 +787,15 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr
case LCO::CALL_TERM:
{
if (m_p25->m_dedicatedControl) {
uint32_t chNo = tsbk->getGrpVchNo();
if (m_verbose) {
LogMessage(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u",
tsbk->toString().c_str(), chNo, srcId, dstId);
}
// is the specified channel granted?
if (/*m_p25->m_affiliations.isChBusy(chNo) &&*/ m_p25->m_affiliations.isGranted(dstId)) {
uint32_t chNo = tsbk->getGrpVchNo();
if (m_verbose) {
LogMessage(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u",
tsbk->toString().c_str(), chNo, srcId, dstId);
}
m_p25->m_affiliations.releaseGrant(dstId, false);
}
}
@ -1341,7 +1355,7 @@ void ControlSignaling::writeNetworkRF(lc::TDULC* tduLc, const uint8_t* data, boo
lc.setSrcId(tduLc->getSrcId());
lc.setDstId(tduLc->getDstId());
m_p25->m_network->writeP25TSDU(lc, data);
m_p25->m_network->writeP25TDULC(lc, data);
if (autoReset)
m_p25->m_network->resetP25();
}
@ -1357,17 +1371,17 @@ void ControlSignaling::writeRF_TDULC(lc::TDULC* lc, bool noNetwork)
uint8_t data[P25_TDULC_FRAME_LENGTH_BYTES + 2U];
::memset(data + 2U, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
// Generate Sync
// generate Sync
Sync::addP25Sync(data + 2U);
// Generate NID
// generate NID
m_p25->m_nid.encode(data + 2U, DUID::TDULC);
// Generate TDULC Data
// generate TDULC Data
lc->encode(data + 2U);
// Add busy bits
P25Utils::addStatusBits(data + 2U, P25_TDULC_FRAME_LENGTH_BITS, false);
// add status bits
P25Utils::addStatusBits(data + 2U, P25_TDULC_FRAME_LENGTH_BITS, false, false);
m_p25->m_rfTimeout.stop();
@ -1396,17 +1410,17 @@ void ControlSignaling::writeNet_TDULC(lc::TDULC* lc)
buffer[0U] = modem::TAG_EOT;
buffer[1U] = 0x00U;
// Generate Sync
// generate Sync
Sync::addP25Sync(buffer + 2U);
// Generate NID
// generate NID
m_p25->m_nid.encode(buffer + 2U, DUID::TDULC);
// Regenerate TDULC Data
// regenerate TDULC Data
lc->encode(buffer + 2U);
// Add busy bits
P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BITS, false);
// add status bits
P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BITS, false, false);
m_p25->addFrame(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U, true);
@ -1443,13 +1457,13 @@ void ControlSignaling::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool for
uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U];
::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
// Generate Sync
// generate Sync
Sync::addP25Sync(data + 2U);
// Generate NID
// generate NID
m_p25->m_nid.encode(data + 2U, DUID::TSDU);
// Generate TSBK block
// generate TSBK block
tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU
tsbk->encode(data + 2U);
@ -1461,12 +1475,10 @@ void ControlSignaling::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool for
Utils::dump(1U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES + 2U, P25_TSBK_FEC_LENGTH_BYTES);
}
// Add busy bits
// add status bits
P25Utils::addStatusBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, m_inbound, true);
P25Utils::addTrunkSlotStatusBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS);
// Set first busy bits to 1,1
P25Utils::setStatusBits(data + 2U, P25_SS0_START, true, true);
P25Utils::addIdleStatusBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS);
P25Utils::setStatusBitsStartIdle(data + 2U);
if (!noNetwork)
writeNetworkRF(tsbk, data + 2U, true);
@ -1513,22 +1525,20 @@ void ControlSignaling::writeNet_TSDU(lc::TSBK* tsbk)
buffer[0U] = modem::TAG_DATA;
buffer[1U] = 0x00U;
// Generate Sync
// generate Sync
Sync::addP25Sync(buffer + 2U);
// Generate NID
// generate NID
m_p25->m_nid.encode(buffer + 2U, DUID::TSDU);
// Regenerate TSDU Data
// regenerate TSDU Data
tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU
tsbk->encode(buffer + 2U);
// Add busy bits
// add status bits
P25Utils::addStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES, false, true);
P25Utils::addTrunkSlotStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES);
// Set first busy bits to 1,1
P25Utils::setStatusBits(buffer + 2U, P25_SS0_START, true, true);
P25Utils::addIdleStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES);
P25Utils::setStatusBitsStartIdle(buffer + 2U);
m_p25->addFrame(buffer, P25_TSDU_FRAME_LENGTH_BYTES + 2U, true);
@ -1566,7 +1576,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk)
// trigger encoding of last block and write to queue
if (m_mbfCnt + 1U == TSBK_MBF_CNT) {
// Generate TSBK block
// generate TSBK block
tsbk->setLastBlock(true); // set last block
tsbk->encode(frame, true);
@ -1580,7 +1590,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk)
Utils::setBitRange(frame, m_rfMBF, (m_mbfCnt * P25_TSBK_FEC_LENGTH_BITS), P25_TSBK_FEC_LENGTH_BITS);
// Generate TSDU frame
// generate TSDU frame
uint8_t tsdu[P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES];
::memset(tsdu, 0x00U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES);
@ -1597,7 +1607,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk)
Utils::dump(1U, "!!! *TSDU (MBF) TSBK Block", frame, P25_TSBK_FEC_LENGTH_BYTES);
}
// Add TSBK data
// add TSBK data
Utils::setBitRange(frame, tsdu, offset, P25_TSBK_FEC_LENGTH_BITS);
offset += P25_TSBK_FEC_LENGTH_BITS;
@ -1608,18 +1618,18 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk)
uint8_t data[P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES + 2U];
::memset(data + 2U, 0x00U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES);
// Generate Sync
// generate Sync
Sync::addP25Sync(data + 2U);
// Generate NID
// generate NID
m_p25->m_nid.encode(data + 2U, DUID::TSDU);
// interleave
P25Utils::encode(tsdu, data + 2U, 114U, 720U);
// Add busy bits
// add busy bits
P25Utils::addStatusBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS, m_inbound, true);
P25Utils::addTrunkSlotStatusBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS);
P25Utils::addIdleStatusBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS);
data[0U] = modem::TAG_DATA;
data[1U] = 0x00U;
@ -1631,7 +1641,7 @@ void ControlSignaling::writeRF_TSDU_MBF(lc::TSBK* tsbk)
return;
}
// Generate TSBK block
// generate TSBK block
tsbk->setLastBlock(false); // clear last block
tsbk->encode(frame, true);
@ -2190,6 +2200,14 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
}
if (!grp && !m_p25->m_ignoreAffiliationCheck) {
// is this the target registered?
if (!m_p25->m_affiliations.isUnitReg(dstId)) {
LogWarning(LOG_NET, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request) ignored, no unit registration, dstId = %u", dstId);
return false;
}
}
if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) {
if (grp) {
if (!net) {
@ -2938,21 +2956,19 @@ void ControlSignaling::writeNet_TSDU_From_RF(lc::TSBK* tsbk, uint8_t* data)
::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
// Generate Sync
// generate Sync
Sync::addP25Sync(data);
// Generate NID
// generate NID
m_p25->m_nid.encode(data, DUID::TSDU);
// Regenerate TSDU Data
// regenerate TSDU Data
tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU
tsbk->encode(data);
// Add busy bits
P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false);
// Set first busy bits to 1,1
P25Utils::setStatusBits(data, P25_SS0_START, true, true);
// add status bits
P25Utils::addStatusBits(data, P25_TSDU_FRAME_LENGTH_BYTES, false, false);
P25Utils::setStatusBitsStartIdle(data);
}
/* Helper to automatically inhibit a source ID on a denial. */

@ -96,6 +96,8 @@ bool Data::process(uint8_t* data, uint32_t len)
m_rfPduUserDataLength = 0U;
}
//Utils::dump(1U, "Raw PDU ISP", data, len);
uint32_t start = m_rfPDUCount * P25_PDU_FRAME_LENGTH_BITS;
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
@ -368,7 +370,7 @@ bool Data::process(uint8_t* data, uint32_t len)
if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) {
if (m_retryCount < MAX_PDU_RETRY_CNT) {
m_p25->writeRF_Preamble();
writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, false, true);
writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true);
m_retryCount++;
}
else {
@ -610,6 +612,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength)
}
if (m_p25->m_netState == RS_NET_DATA) {
m_inbound = false; // forcibly set inbound to false
::memcpy(m_netPDU + m_netDataOffset, data + 24U, blockLength);
m_netDataOffset += blockLength;
m_netPDUCount++;
@ -814,6 +817,9 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress,
m_p25->writeRF_TDU(true, imm);
uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS;
if (dataHeader.getPadLength() > 0U)
bitLength += (dataHeader.getPadLength() * 8U);
uint32_t offset = P25_PREAMBLE_LENGTH_BITS;
UInt8Array __data = std::make_unique<uint8_t[]>((bitLength / 8U) + 1U);
@ -898,7 +904,7 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress,
}
}
writeRF_PDU(data, bitLength, false, imm);
writeRF_PDU(data, bitLength, imm);
}
/* Updates the processor by the passed number of milliseconds. */
@ -1386,12 +1392,12 @@ void Data::writeNetwork(const uint8_t currentBlock, const uint8_t *data, uint32_
/* Helper to write a P25 PDU packet. */
void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, bool imm, bool ackRetry)
void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ackRetry)
{
assert(pdu != nullptr);
assert(bitLength > 0U);
m_p25->writeRF_Preamble();
m_p25->writeRF_TDU(true, imm);
if (!ackRetry) {
if (m_retryPDUData != nullptr)
@ -1414,24 +1420,23 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, boo
uint8_t data[P25_PDU_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U);
// Add the data
uint32_t newBitLength = P25Utils::encode(pdu, data + 2U, bitLength);
// add the data
uint32_t newBitLength = P25Utils::encodeByLength(pdu, data + 2U, bitLength);
uint32_t newByteLength = newBitLength / 8U;
if ((newBitLength % 8U) > 0U)
newByteLength++;
// Regenerate Sync
// generate Sync
Sync::addP25Sync(data + 2U);
// Regenerate NID
// generate NID
m_p25->m_nid.encode(data + 2U, DUID::PDU);
// Add status bits
P25Utils::addStatusBits(data + 2U, newBitLength, false);
P25Utils::addTrunkSlotStatusBits(data + 2U, newBitLength);
// add status bits
P25Utils::addStatusBits(data + 2U, newBitLength, m_inbound, true);
P25Utils::setStatusBitsStartIdle(data + 2U);
// Set first busy bits to 1,1
P25Utils::setStatusBits(data + 2U, P25_SS0_START, true, true);
//Utils::dump("Raw PDU OSP", data, newByteLength + 2U);
if (m_p25->m_duplex) {
data[0U] = modem::TAG_DATA;
@ -1440,10 +1445,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, boo
m_p25->addFrame(data, newByteLength + 2U, false, imm);
}
// add trailing null pad; only if control data isn't being transmitted
if (!m_p25->m_ccRunning && !noNulls) {
m_p25->writeRF_Nulls();
}
m_p25->writeRF_TDU(true, imm);
}
/* Helper to write a network P25 PDU packet. */
@ -1451,6 +1453,9 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls, boo
void Data::writeNet_PDU_Buffered()
{
uint32_t bitLength = ((m_netDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS;
if (m_netDataHeader.getPadLength() > 0U)
bitLength += (m_netDataHeader.getPadLength() * 8U);
uint32_t offset = P25_PREAMBLE_LENGTH_BITS;
UInt8Array __data = std::make_unique<uint8_t[]>((bitLength / 8U) + 1U);
@ -1542,6 +1547,9 @@ void Data::writeNet_PDU_Buffered()
void Data::writeRF_PDU_Buffered()
{
uint32_t bitLength = ((m_rfDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS;
if (m_rfDataHeader.getPadLength() > 0U)
bitLength += (m_rfDataHeader.getPadLength() * 8U);
uint32_t offset = P25_PREAMBLE_LENGTH_BITS;
UInt8Array __data = std::make_unique<uint8_t[]>((bitLength / 8U) + 1U);
@ -1663,7 +1671,7 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA
/* Helper to write a PDU acknowledge response. */
void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId, bool noNulls)
void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId)
{
if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK)
return;
@ -1702,5 +1710,5 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a
rspHeader.getResponseClass(), rspHeader.getResponseType(), rspHeader.getResponseStatus(), rspHeader.getLLId(), rspHeader.getSrcLLId());
}
writeRF_PDU(data, bitLength, noNulls);
writeRF_PDU(data, bitLength);
}

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

Loading…
Cancel
Save

Powered by TurnKey Linux.