diff --git a/CMakeLists.txt b/CMakeLists.txt index 33fef823..b4d5190f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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} diff --git a/README.md b/README.md index 57081573..608fa999 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 55217ff7..4b3540b9 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -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 diff --git a/configs/config.example.yml b/configs/config.example.yml index de7b01fe..7609de15 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -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 diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 3d04ff95..d4c6565c 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -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: [] diff --git a/configs/peer_list.example.dat b/configs/peer_list.example.dat index c94eb702..50a77385 100644 --- a/configs/peer_list.example.dat +++ b/configs/peer_list.example.dat @@ -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)" +# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional)," #1234,,0, #5678,MYSECUREPASSWORD,0, #9876,MYSECUREPASSWORD,1, +#5432,MYSECUREPASSWORD,,Peer Alias 1, +#1012,MYSECUREPASSWORD,1,Peer Alias 2, diff --git a/contrib/vscode/launch.json b/contrib/vscode/launch.json index 093012f2..b5eebe63 100644 --- a/contrib/vscode/launch.json +++ b/contrib/vscode/launch.json @@ -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 - } - }, + } ] } diff --git a/src/bridge/BridgeMain.cpp b/src/bridge/BridgeMain.cpp index 07063d1f..0ca43790 100644 --- a/src/bridge/BridgeMain.cpp +++ b/src/bridge/BridgeMain.cpp @@ -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); diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 89faa792..bd625428 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -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(); m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as(34001); m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); + m_udpUseULaw = networkConf["udpUseULaw"].as(false); + + if (m_udpUseULaw) + m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as(false); + + if (m_udpUseULaw && m_udpMetadata) + m_udpMetadata = false; // metadata isn't supported when encoding uLaw m_srcId = (uint32_t)networkConf["sourceId"].as(p25::defines::WUID_FNE); m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as(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(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; diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 2533fcfe..675f8cfa 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -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; diff --git a/src/common/Thread.cpp b/src/common/Thread.cpp index 49fc0ad4..93e9cc96 100644 --- a/src/common/Thread.cpp +++ b/src/common/Thread.cpp @@ -63,6 +63,8 @@ bool Thread::run() void Thread::wait() { + if (!m_started) + return; #if defined(_WIN32) ::WaitForSingleObject(m_thread, INFINITE); ::CloseHandle(m_thread); diff --git a/src/common/edac/Golay24128.h b/src/common/edac/Golay24128.h index 92f52f38..e9562792 100644 --- a/src/common/edac/Golay24128.h +++ b/src/common/edac/Golay24128.h @@ -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 */ diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 168196aa..e4dde41c 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -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 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 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 lock(m_mutex); + // clock all the grant timers std::vector gntsToRel = std::vector(); 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) { diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index 1edc4a39..2c7ac586 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -29,6 +29,7 @@ #include #include #include +#include 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 diff --git a/src/common/lookups/LookupTable.h b/src/common/lookups/LookupTable.h index 361f6e40..6eabc41c 100644 --- a/src/common/lookups/LookupTable.h +++ b/src/common/lookups/LookupTable.h @@ -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; diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index fee33719..f64d131c 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -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 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 diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index eed88808..b1b318ba 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -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. diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index 90c93fef..449a12d8 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -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; diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index f5235b39..cdfa4e16 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -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, diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index c3a6b26e..8919b8c5 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -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} diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index c1daca3c..3c855629 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -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 #include +// --------------------------------------------------------------------------- +// 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 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 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 lock(m_fqTimestampLock); auto entry = m_streamTimestamps.find(streamId); if (entry != m_streamTimestamps.end()) { if (m_debug) diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 3a23ff43..413c69d9 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.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) 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 #include namespace network @@ -116,6 +117,7 @@ namespace network private: uint32_t m_peerId; std::unordered_map m_streamTimestamps; + static std::mutex m_fqTimestampLock; /** * @brief Generate RTP message for the frame queue. diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h index 8c7c5a14..40e5938c 100644 --- a/src/common/network/tcp/SecureTcpClient.h +++ b/src/common/network/tcp/SecureTcpClient.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 * */ /** @@ -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. diff --git a/src/common/network/tcp/SecureTcpListener.h b/src/common/network/tcp/SecureTcpListener.h index c1fc14c9..a94a7833 100644 --- a/src/common/network/tcp/SecureTcpListener.h +++ b/src/common/network/tcp/SecureTcpListener.h @@ -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: diff --git a/src/common/p25/P25Utils.cpp b/src/common/p25/P25Utils.cpp index f4efa990..a9d57b36 100644 --- a/src/common/p25/P25Utils.cpp +++ b/src/common/p25/P25Utils.cpp @@ -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++; diff --git a/src/common/p25/P25Utils.h b/src/common/p25/P25Utils.h index bfad54d1..bc030a43 100644 --- a/src/common/p25/P25Utils.h +++ b/src/common/p25/P25Utils.h @@ -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. diff --git a/src/common/p25/dfsi/LC.cpp b/src/common/p25/dfsi/LC.cpp index da8807f4..4463546d 100644 --- a/src/common/p25/dfsi/LC.cpp +++ b/src/common/p25/dfsi/LC.cpp @@ -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; diff --git a/src/common/p25/dfsi/frames/BlockHeader.cpp b/src/common/p25/dfsi/frames/BlockHeader.cpp index 01e56f29..5385ed37 100644 --- a/src/common/p25/dfsi/frames/BlockHeader.cpp +++ b/src/common/p25/dfsi/frames/BlockHeader.cpp @@ -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); } diff --git a/src/common/p25/dfsi/frames/BlockHeader.h b/src/common/p25/dfsi/frames/BlockHeader.h index b56c54e3..9d47a6e7 100644 --- a/src/common/p25/dfsi/frames/BlockHeader.h +++ b/src/common/p25/dfsi/frames/BlockHeader.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 * */ /** @@ -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. diff --git a/src/common/p25/dfsi/frames/ControlOctet.cpp b/src/common/p25/dfsi/frames/ControlOctet.cpp index 0308b526..6f4a6e20 100644 --- a/src/common/p25/dfsi/frames/ControlOctet.cpp +++ b/src/common/p25/dfsi/frames/ControlOctet.cpp @@ -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)); } diff --git a/src/common/p25/dfsi/frames/ControlOctet.h b/src/common/p25/dfsi/frames/ControlOctet.h index 308ec2e4..3395408b 100644 --- a/src/common/p25/dfsi/frames/ControlOctet.h +++ b/src/common/p25/dfsi/frames/ControlOctet.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 * */ /** @@ -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. diff --git a/src/common/p25/dfsi/frames/FrameDefines.h b/src/common/p25/dfsi/frames/FrameDefines.h index 4798cab8..890c2671 100644 --- a/src/common/p25/dfsi/frames/FrameDefines.h +++ b/src/common/p25/dfsi/frames/FrameDefines.h @@ -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 }; } diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index b529d58d..7779b8d1 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -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__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/FullRateVoice.cpp b/src/common/p25/dfsi/frames/FullRateVoice.cpp index f8b1ed56..d334e84f 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/FullRateVoice.cpp @@ -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; diff --git a/src/common/p25/dfsi/frames/FullRateVoice.h b/src/common/p25/dfsi/frames/FullRateVoice.h index a3696bb0..100fa7b9 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.h +++ b/src/common/p25/dfsi/frames/FullRateVoice.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 * */ /** @@ -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 diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.h b/src/common/p25/dfsi/frames/MotFullRateVoice.h index 1def57cf..111a0bbd 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.h +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.h @@ -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. diff --git a/src/common/p25/dfsi/frames/MotPDUFrame.h b/src/common/p25/dfsi/frames/MotPDUFrame.h index 4280d15a..792143a6 100644 --- a/src/common/p25/dfsi/frames/MotPDUFrame.h +++ b/src/common/p25/dfsi/frames/MotPDUFrame.h @@ -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. diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.h b/src/common/p25/dfsi/frames/MotStartOfStream.h index 73e898d2..4afcab92 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.h +++ b/src/common/p25/dfsi/frames/MotStartOfStream.h @@ -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. diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h index 0c7d3713..acaede19 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h @@ -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. diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.h b/src/common/p25/dfsi/frames/MotTSBKFrame.h index 15db4872..654f2640 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.h +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.h @@ -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. diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.h b/src/common/p25/dfsi/frames/MotVoiceHeader1.h index 37cdc3cd..049572bd 100644 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.h +++ b/src/common/p25/dfsi/frames/MotVoiceHeader1.h @@ -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. diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader2.h b/src/common/p25/dfsi/frames/MotVoiceHeader2.h index 1fa6615a..07af5245 100644 --- a/src/common/p25/dfsi/frames/MotVoiceHeader2.h +++ b/src/common/p25/dfsi/frames/MotVoiceHeader2.h @@ -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. diff --git a/src/common/p25/dfsi/frames/StartOfStream.h b/src/common/p25/dfsi/frames/StartOfStream.h index 11c6fbac..06972926 100644 --- a/src/common/p25/dfsi/frames/StartOfStream.h +++ b/src/common/p25/dfsi/frames/StartOfStream.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 * */ /** @@ -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. diff --git a/src/common/p25/dfsi/frames/fsc/FSCACK.cpp b/src/common/p25/dfsi/frames/fsc/FSCACK.cpp index 2fa01505..6d5d025a 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCACK.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCACK.cpp @@ -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); } } diff --git a/src/common/p25/dfsi/frames/fsc/FSCACK.h b/src/common/p25/dfsi/frames/fsc/FSCACK.h index f2df8f51..2c378a82 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCACK.h +++ b/src/common/p25/dfsi/frames/fsc/FSCACK.h @@ -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. */ diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp b/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp index ce7d3666..ed0f7149 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCConnect.cpp @@ -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) diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnect.h b/src/common/p25/dfsi/frames/fsc/FSCConnect.h index 067a93bd..f199ba05 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCConnect.h +++ b/src/common/p25/dfsi/frames/fsc/FSCConnect.h @@ -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. diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp b/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp deleted file mode 100644 index 6af0a65e..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.cpp +++ /dev/null @@ -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 -#include - -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 -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h b/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h deleted file mode 100644 index 18196c15..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCConnectResponse.h +++ /dev/null @@ -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__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp index f6f79c01..2001055b 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.cpp @@ -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); -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h index 0cea9537..1e9517b9 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h +++ b/src/common/p25/dfsi/frames/fsc/FSCDisconnect.h @@ -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 diff --git a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp index 43aef62e..339d0325 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.cpp @@ -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); -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h index 15a33d1f..ccc7e68e 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h +++ b/src/common/p25/dfsi/frames/fsc/FSCHeartbeat.h @@ -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 diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp index cd483f35..cc640c2f 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.cpp @@ -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::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(new FSCConnect(data)); + message = new FSCConnect(); + break; case FSCMessageType::FSC_HEARTBEAT: - return std::unique_ptr(new FSCHeartbeat(data)); + message = new FSCHeartbeat(); + break; case FSCMessageType::FSC_ACK: - return std::unique_ptr(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(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(message); + } + return nullptr; } diff --git a/src/common/p25/dfsi/frames/fsc/FSCMessage.h b/src/common/p25/dfsi/frames/fsc/FSCMessage.h index 1772c86c..08398c7a 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCMessage.h +++ b/src/common/p25/dfsi/frames/fsc/FSCMessage.h @@ -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 diff --git a/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp new file mode 100644 index 00000000..139a773c --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.cpp @@ -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 +#include + +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; +} diff --git a/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.h b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.h new file mode 100644 index 00000000..e6be7042 --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCReportSelModes.h @@ -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__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/fsc/FSCResponse.cpp b/src/common/p25/dfsi/frames/fsc/FSCResponse.cpp deleted file mode 100644 index 4cf5b57d..00000000 --- a/src/common/p25/dfsi/frames/fsc/FSCResponse.cpp +++ /dev/null @@ -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 -#include - -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 -} diff --git a/src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp new file mode 100644 index 00000000..6b97b856 --- /dev/null +++ b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.cpp @@ -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 +#include + +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 +} diff --git a/src/common/p25/dfsi/frames/fsc/FSCResponse.h b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.h similarity index 55% rename from src/common/p25/dfsi/frames/fsc/FSCResponse.h rename to src/common/p25/dfsi/frames/fsc/FSCSelChannel.h index cb4cbf18..31727576 100644 --- a/src/common/p25/dfsi/frames/fsc/FSCResponse.h +++ b/src/common/p25/dfsi/frames/fsc/FSCSelChannel.h @@ -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__ \ No newline at end of file +#endif // __FSC_SEL_CHANNEL_H__ \ No newline at end of file diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 98e35693..9d67e65c 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -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 diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index 1b854af5..bab14773 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -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. diff --git a/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp b/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp index 96377749..84634f4c 100644 --- a/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp +++ b/src/common/p25/lc/tdulc/LC_CALL_TERM.cpp @@ -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; } diff --git a/src/common/p25/lc/tdulc/TDULCFactory.cpp b/src/common/p25/lc/tdulc/TDULCFactory.cpp index 8d3bd6a2..4d5a9890 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.cpp +++ b/src/common/p25/lc/tdulc/TDULCFactory.cpp @@ -79,6 +79,8 @@ std::unique_ptr 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; diff --git a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp index ba06e082..5cd92136 100644 --- a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp +++ b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp @@ -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 tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); diff --git a/src/fne/ActivityLog.cpp b/src/fne/ActivityLog.cpp index 4da91f94..d7a974f4 100644 --- a/src/fne/ActivityLog.cpp +++ b/src/fne/ActivityLog.cpp @@ -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; diff --git a/src/fne/FNEMain.cpp b/src/fne/FNEMain.cpp index ec63f18c..765c6cbb 100644 --- a/src/fne/FNEMain.cpp +++ b/src/fne/FNEMain.cpp @@ -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); diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 266238eb..d1292106 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -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"); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 0ae3ce2a..56eba65f 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -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(true); m_enableInCallCtrl = conf["enableInCallCtrl"].as(true); m_rejectUnknownRID = conf["rejectUnknownRID"].as(false); + m_disallowCallTerm = conf["disallowCallTerm"].as(false); m_softConnLimit = conf["connectionLimit"].as(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(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); } } diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 555296cc..209b7489 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -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 m_dropU2UPeerTable; bool m_enableInfluxDB; diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index b66446ff..5054daf5 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -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 __str = std::make_unique(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 __str = std::make_unique(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 __str = std::make_unique(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(); diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 46e39825..3b30f09d 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -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(peerId); - + peerObj["peerAlias"].set(peerAlias); + peerObj["peerLink"].set(peerLink); + peerObj["peerPassword"].set(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()) { errorPayload(reply, "peerId was not a valid integer"); return; } - + // Get uint32_t peerId = req["peerId"].get(); - m_peerListLookup->addEntry(peerId); + // Get peer alias (optional) + std::string peerAlias = ""; + if (req.find("peerAlias") != req.end()) { + // Validate + if (!req["peerAlias"].is()) { + errorPayload(reply, "peerAlias was not a valid string"); + return; + } + // Get + peerAlias = req["peerAlias"].get(); + } + + // Get peer link setting (optional) + bool peerLink = false; + if (req.find("peerLink") != req.end()) { + // Validate + if (!req["peerLink"].is()) { + errorPayload(reply, "peerLink was not a valid boolean"); + return; + } + // Get + peerLink = req["peerLink"].get(); + } + + // Get peer password (optional) + std::string peerPassword = ""; + if (req.find("peerPassword") != req.end()) { + // Validate + if (!req["peerPassword"].is()) { + errorPayload(reply, "peerPassword was not a valid string"); + return; + } + // Get + peerPassword = req["peerPassword"].get(); + } + + m_peerListLookup->addEntry(peerId, peerAlias, peerPassword, peerLink); } /* REST API endpoint; implements put peer delete request. */ diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 31a45e9a..14cf9fe3 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -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; diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index c49a87dd..23e7873b 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -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 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 StatusMapPair; std::unordered_map m_status; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index f7d72537..c359abff 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -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; diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 003235f1..195d2497 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -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 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 StatusMapPair; std::unordered_map m_status; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index e1a6f591..c18bc059 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -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(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer + 24U, frameLength); + + std::unique_ptr 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", diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index a1da1ad3..4ab19f33 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -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 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 StatusMapPair; std::unordered_map m_status; friend class packetdata::P25PacketData; - packetdata::P25PacketData *m_packetData; + packetdata::P25PacketData* m_packetData; bool m_debug; diff --git a/src/fw/v24 b/src/fw/v24 index 8269757a..a35f0d95 160000 --- a/src/fw/v24 +++ b/src/fw/v24 @@ -1 +1 @@ -Subproject commit 8269757ac2e9e77ad48da3934197c306b3184fc0 +Subproject commit a35f0d95ee782efb2097c32a621e34cc8dad60b8 diff --git a/src/host/ActivityLog.cpp b/src/host/ActivityLog.cpp index 51ca5034..6764a4c8 100644 --- a/src/host/ActivityLog.cpp +++ b/src/host/ActivityLog.cpp @@ -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; diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index eaab7480..cb8cf5c3 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -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(1U); int rxTuning = hotspotParams["rxTuning"].as(0); int txTuning = hotspotParams["txTuning"].as(0); - uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as(100U); + uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as(95U); yaml::Node repeaterParams = modemConf["repeater"]; @@ -461,8 +461,11 @@ bool Host::createModem() bool rtrt = dfsiParams["rtrt"].as(true); bool diu = dfsiParams["diu"].as(true); uint16_t jitter = dfsiParams["jitter"].as(200U); - bool useFSCForUDP = dfsiParams["useFSC"].as(false); uint16_t dfsiCallTimeout = dfsiParams["callTimeout"].as(200U); + bool useFSCForUDP = dfsiParams["fsc"].as(false); + uint32_t fscHeartbeat = dfsiParams["fscHeartbeat"].as(5U); + bool fscInitiator = dfsiParams["initiator"].as(false); + bool dfsiTIAMode = dfsiParams["dfsiTIAMode"].as(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(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(m_udpDFSIRemotePort); + udpPort->openFSC(); + } + bool ret = m_modem->open(); if (!ret) { delete m_modem; diff --git a/src/host/Host.cpp b/src/host/Host.cpp index e4865c9b..ae6fe178 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -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(m_udpDSFIRemotePort); - + modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(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(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 { diff --git a/src/host/Host.h b/src/host/Host.h index 685a092b..0dd04fb9 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -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; diff --git a/src/host/HostMain.cpp b/src/host/HostMain.cpp index b9fefec5..f5bd93fa 100644 --- a/src/host/HostMain.cpp +++ b/src/host/HostMain.cpp @@ -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); diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.cpp b/src/host/dmr/lookups/DMRAffiliationLookup.cpp index 9791fa61..37b1c540 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.cpp +++ b/src/host/dmr/lookups/DMRAffiliationLookup.cpp @@ -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; } diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.h b/src/host/dmr/lookups/DMRAffiliationLookup.h index 90c85125..0e394f12 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.h +++ b/src/host/dmr/lookups/DMRAffiliationLookup.h @@ -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. diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 2835a18f..304d345f 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -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) { diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index a96a6cad..7d7deba3 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -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-2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2024 Patrick McDonnell, W3AXL * */ @@ -44,6 +44,7 @@ ModemV24::ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, u false, false, dumpModemStatus, trace, debug), m_rtrt(rtrt), m_diu(diu), + m_superFrameCnt(0U), m_audio(), m_nid(nullptr), m_txP25Queue(p25TxQueueSize, "TX P25 Queue"), @@ -56,7 +57,8 @@ ModemV24::ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, u m_callTimeout(200U), m_jitter(jitter), m_lastP25Tx(0U), - m_rs() + m_rs(), + m_useTIAFormat(false) { m_v24Connected = false; // defaulted to false for V.24 modems @@ -89,6 +91,13 @@ void ModemV24::setP25NAC(uint32_t nac) m_nid = new NID(nac); } +/* Helper to set the TIA-102 format DFSI frame flag. */ + +void ModemV24::setTIAFormat(bool set) +{ + m_useTIAFormat = set; +} + /* Opens connection to the air interface modem. */ bool ModemV24::open() @@ -127,7 +136,10 @@ bool ModemV24::open() m_error = false; - LogMessage(LOG_MODEM, "Modem Ready [Direct Mode]"); + if (m_useTIAFormat) + LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / TIA-102]"); + else + LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / V.24]"); return true; } @@ -182,7 +194,10 @@ void ModemV24::clock(uint32_t ms) std::lock_guard lock(m_p25ReadLock); // convert data from V.24/DFSI formatting to TIA-102 air formatting - convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + if (m_useTIAFormat) + convertToAirTIA(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + else + convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; @@ -429,7 +444,10 @@ int ModemV24::write(const uint8_t* data, uint32_t length) ::memset(buffer, 0x00U, length); ::memcpy(buffer, data + 2U, length); - convertFromAir(buffer, length); + if (m_useTIAFormat) + convertFromAirTIA(buffer, length); + else + convertFromAir(buffer, length); return length; } else { return Modem::write(data, length); @@ -542,14 +560,14 @@ void ModemV24::create_TDU(uint8_t* buffer) 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::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_EOT; buffer[1U] = 0x01U; @@ -672,17 +690,17 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) lc.setKId(m_rxCall->kId); lc.setMI(m_rxCall->MI); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_nid->encode(buffer + 2U, DUID::HDU); - // Generate HDU + // generate HDU lc.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; @@ -753,24 +771,22 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - // Add the data - uint32_t newBitLength = P25Utils::encode(data, buffer + 2U, bitLength); + // add the data + uint32_t newBitLength = P25Utils::encodeByLength(data, buffer + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; if ((newBitLength % 8U) > 0U) newByteLength++; - // Regenerate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Regenerate NID + // generate NID m_nid->encode(buffer + 2U, DUID::PDU); - // Add status bits - P25Utils::addStatusBits(buffer + 2U, newBitLength, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, newBitLength, false, false); P25Utils::addIdleStatusBits(buffer + 2U, newBitLength); - - // Set first busy bits to 1,1 - P25Utils::setStatusBits(buffer + 2U, P25_SS0_START, true, true); + P25Utils::setStatusBitsStartIdle(buffer + 2U); storeConvertedRx(buffer, P25_PDU_FRAME_LENGTH_BYTES + 2U); } @@ -790,22 +806,20 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID 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); storeConvertedRx(buffer, P25_TSDU_FRAME_LENGTH_BYTES + 2U); } @@ -947,8 +961,9 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } } break; + default: - break; + break; } // increment our voice frame counter @@ -1028,8 +1043,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 204U, 8U); - // add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; @@ -1070,8 +1085,8 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 204U, 8U); - // add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; @@ -1081,226 +1096,1177 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } } -/* Helper to add a V.24 data frame to the P25 TX queue with the proper timestamp and formatting */ +/* Internal helper to convert from TIA-102 DFSI to TIA-102 air interface. */ -void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType) +void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) { assert(data != nullptr); - assert(len > 0U); - - if (m_debug) - LogDebug(LOG_MODEM, "ModemV24::queueP25Frame() msgType = $%02X", msgType); - if (m_trace) - Utils::dump(1U, "ModemV24::queueP25Frame() data", data, len); - - // get current time in ms - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - // timestamp for this message (in ms) - uint64_t msgTime = 0U; - - // if this is our first message, timestamp is just now + the jitter buffer offset in ms - if (m_lastP25Tx == 0U) { - msgTime = now + m_jitter; - - // if the message type requests no jitter delay -- just set the message time to now - if (msgType == STT_NON_IMBE_NO_JITTER) - msgTime = now; - } - // if we had a message before this, calculate the new timestamp dynamically - else { - // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above - if ((int64_t)(now - m_lastP25Tx) > m_jitter) { - msgTime = now + m_jitter; - } - // otherwise, we time out messages as required by the message type - else { - if (msgType == STT_IMBE) { - // IMBEs must go out at 20ms intervals - msgTime = m_lastP25Tx + 20U; - } else { - // Otherwise we don't care, we use 5ms since that's the theoretical minimum time a 9600 baud message can take - msgTime = m_lastP25Tx + 5U; - } - } - } - - len += 4U; - - // convert 16-bit length to 2 bytes - uint8_t length[2U]; - if (len > 255U) - length[0U] = (len >> 8U) & 0xFFU; - else - length[0U] = 0x00U; - length[1U] = len & 0xFFU; - - m_txP25Queue.addData(length, 2U); - - // add the data tag - uint8_t tag = TAG_DATA; - m_txP25Queue.addData(&tag, 1U); - - // convert 64-bit timestamp to 8 bytes and add - uint8_t tsBytes[8U]; - assert(sizeof msgTime == 8U); - ::memcpy(tsBytes, &msgTime, 8U); - m_txP25Queue.addData(tsBytes, 8U); - - // add the DVM start byte, length byte, CMD byte, and padding 0 - uint8_t header[4U]; - header[0U] = DVM_SHORT_FRAME_START; - header[1U] = len & 0xFFU; - header[2U] = CMD_P25_DATA; - header[3U] = 0x00U; - m_txP25Queue.addData(header, 4U); - - // add the data - m_txP25Queue.addData(data, len - 4U); - - // update the last message time - m_lastP25Tx = msgTime; -} - -/* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ + assert(length > 0U); -void ModemV24::startOfStream(const p25::lc::LC& control) -{ - m_txCallInProgress = true; + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - MotStartOfStream start = MotStartOfStream(); - start.setStartStop(StartStopFlag::START); - start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + // get the DFSI data (skip the 0x00 padded byte at the start) + UInt8Array __dfsiData = std::make_unique(length - 1U); + uint8_t* dfsiData = __dfsiData.get(); + ::memset(dfsiData, 0x00U, length - 1U); + ::memcpy(dfsiData, data + 1U, length - 1U); - // create buffer for bytes and encode - uint8_t startBuf[start.LENGTH]; - ::memset(startBuf, 0x00U, start.LENGTH); - start.encode(startBuf); + if (m_debug) + Utils::dump("DFSI RX data from board", dfsiData, length - 1U); - if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH); + ControlOctet ctrl = ControlOctet(); + ctrl.decode(dfsiData); - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + uint8_t blockCnt = ctrl.getBlockHeaderCnt(); - uint8_t mi[MI_LENGTH_BYTES]; - ::memset(mi, 0x00U, MI_LENGTH_BYTES); - control.getMI(mi); + // iterate through blocks + uint8_t hdrOffs = 1U, dataOffs = blockCnt + 1U; + for (uint8_t i = 0U; i < blockCnt; i++) { + BlockHeader hdr = BlockHeader(); + hdr.decode(dfsiData + hdrOffs); - uint8_t vhdr[DFSI_VHDR_LEN]; - ::memset(vhdr, 0x00U, DFSI_VHDR_LEN); + BlockType::E blockType = hdr.getBlockType(); + switch (blockType) { + case BlockType::START_OF_STREAM: + { + StartOfStream start = StartOfStream(); + start.decode(dfsiData + dataOffs); - ::memcpy(vhdr, mi, MI_LENGTH_BYTES); + uint16_t nac = start.getNID() & 0xFFFU; - vhdr[9U] = control.getMFId(); - vhdr[10U] = control.getAlgId(); - __SET_UINT16B(control.getKId(), vhdr, 11U); - __SET_UINT16B(control.getDstId(), vhdr, 13U); + // bryanb: maybe compare the NACs? - // perform RS encoding - m_rs.encode362017(vhdr); + dataOffs += StartOfStream::LENGTH; + } + break; + case BlockType::END_OF_STREAM: + { + dataOffs += 1U; - // convert the binary bytes to hex bytes - uint8_t raw[DFSI_VHDR_RAW_LEN]; - uint32_t offset = 0; - for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { - raw[i] = Utils::bin2Hex(vhdr, offset); - } + // generate Sync + Sync::addP25Sync(buffer + 2U); - // prepare VHDR1 - MotVoiceHeader1 vhdr1 = MotVoiceHeader1(); - vhdr1.startOfStream->setStartStop(StartStopFlag::START); - vhdr1.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - vhdr1.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + // generate NID + m_nid->encode(buffer + 2U, DUID::TDU); - ::memcpy(vhdr1.header, raw, 8U); - ::memcpy(vhdr1.header + 9U, raw + 8U, 8U); - ::memcpy(vhdr1.header + 18U, raw + 16U, 2U); + // add status bits + P25Utils::setStatusBitsAllIdle(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS); - // encode VHDR1 and send - uint8_t vhdr1Buf[vhdr1.LENGTH]; - ::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH); - vhdr1.encode(vhdr1Buf); + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_TDU_FRAME_LENGTH_BITS + 2U); + } + break; - if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH); + case BlockType::VOICE_HEADER_P1: + { + // copy to call data VHDR1 + ::memset(m_rxCall->VHDR1, 0x00U, 18U); + ::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U); - queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE); + dataOffs += 19U; // 18 Golay + Block Type Marker + } + break; + case BlockType::VOICE_HEADER_P2: + { + // copy to call data VHDR2 + ::memset(m_rxCall->VHDR2, 0x00U, 18U); + ::memcpy(m_rxCall->VHDR2, dfsiData + dataOffs + 1U, 18U); - // prepare VHDR2 - MotVoiceHeader2 vhdr2 = MotVoiceHeader2(); - ::memcpy(vhdr2.header, raw + 18U, 8U); - ::memcpy(vhdr2.header + 9U, raw + 26U, 8U); - ::memcpy(vhdr2.header + 18U, raw + 34U, 2U); + dataOffs += 19U; // 18 Golay + Block Type Marker - // encode VHDR2 and send - uint8_t vhdr2Buf[vhdr2.LENGTH]; - ::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH); - vhdr2.encode(vhdr2Buf); + // buffer for raw VHDR data + uint8_t raw[DFSI_VHDR_RAW_LEN]; + ::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN); - if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH); + ::memcpy(raw, m_rxCall->VHDR1, 18U); + ::memcpy(raw + 18U, m_rxCall->VHDR2, 18U); - queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE); -} + assert(raw != nullptr); -/* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ + // buffer for decoded VHDR data + uint8_t vhdr[P25_HDU_LENGTH_BYTES]; + ::memset(vhdr, 0x00U, P25_HDU_LENGTH_BYTES); -void ModemV24::endOfStream() -{ - MotStartOfStream end = MotStartOfStream(); - end.setStartStop(StartStopFlag::STOP); + assert(vhdr != nullptr); - // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); - end.encode(endBuf); + uint32_t offset = 0U; + for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { + Utils::hex2Bin(raw[i], vhdr, offset); + } - if (m_trace) - Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH); + // try to decode the RS data + try { + bool ret = m_rs.decode362017(vhdr); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); + } else { + // late entry? + if (!m_rxCallInProgress) { + m_rxCallInProgress = true; + m_rxCall->resetCallData(); + if (m_debug) + LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); + } - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); - m_txCallInProgress = false; -} + m_rxCall->mfId = vhdr[9U]; + m_rxCall->algoId = vhdr[10U]; + m_rxCall->kId = __GET_UINT16B(vhdr, 11U); + m_rxCall->dstId = __GET_UINT16B(vhdr, 13U); -/* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ + if (m_debug) { + LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); + } -void ModemV24::convertFromAir(uint8_t* data, uint32_t length) -{ - assert(data != nullptr); - assert(length > 0U); + // generate a HDU + lc::LC lc = lc::LC(); + lc.setDstId(m_rxCall->dstId); + lc.setAlgId(m_rxCall->algoId); + lc.setKId(m_rxCall->kId); + lc.setMI(m_rxCall->MI); - if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() data", data, length); + // generate Sync + Sync::addP25Sync(buffer + 2U); - uint8_t ldu[9U * 25U]; - ::memset(ldu, 0x00U, 9 * 25U); + // generate NID + m_nid->encode(buffer + 2U, DUID::HDU); - // decode the NID - bool valid = m_nid->decode(data + 2U); - if (!valid) - return; + // generate HDU + lc.encodeHDU(buffer + 2U); - DUID::E duid = m_nid->getDUID(); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true, false); - // handle individual DUIDs - lc::LC lc = lc::LC(); - data::LowSpeedData lsd = data::LowSpeedData(); - switch (duid) { - case DUID::HDU: - { - bool ret = lc.decodeHDU(data + 2U); - if (!ret) { - LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U); + } + } + catch (...) { + LogError(LOG_MODEM, "V.24/DFSI RX traffic got exception while trying to decode RS data for VHDR"); } - - startOfStream(lc); } break; - case DUID::LDU1: + + case BlockType::FULL_RATE_VOICE: { - bool ret = lc.decodeLDU1(data + 2U, true); - if (!ret) { + FullRateVoice voice = FullRateVoice(); + //m_superFrameCnt = voice.getSuperframeCnt(); + voice.decode(dfsiData + dataOffs); + + DFSIFrameType::E frameType = voice.getFrameType(); + switch (frameType) { + case DFSIFrameType::LDU1_VOICE1: + { + ::memcpy(m_rxCall->netLDU1 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE2: + { + ::memcpy(m_rxCall->netLDU1 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE3: + { + ::memcpy(m_rxCall->netLDU1 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->lco = voice.additionalData[0U]; + m_rxCall->mfId = voice.additionalData[1U]; + m_rxCall->serviceOptions = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU1_VOICE4: + { + ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->dstId = __GET_UINT16(voice.additionalData, 0U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU1_VOICE5: + { + ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->srcId = __GET_UINT16(voice.additionalData, 0U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU1_VOICE6: + { + ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE7: + { + ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE8: + { + ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU1_VOICE9: + { + ::memcpy(m_rxCall->netLDU1 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->lsd1 = voice.additionalData[0U]; + m_rxCall->lsd2 = voice.additionalData[1U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC9 traffic missing metadata"); + } + } + break; + + case DFSIFrameType::LDU2_VOICE10: + { + ::memcpy(m_rxCall->netLDU2 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE11: + { + ::memcpy(m_rxCall->netLDU2 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE12: + { + ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + ::memcpy(m_rxCall->MI, voice.additionalData, 3U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE13: + { + ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE14: + { + ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE15: + { + ::memcpy(m_rxCall->netLDU2 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->algoId = voice.additionalData[0U]; + m_rxCall->kId = __GET_UINT16B(voice.additionalData, 1U); + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); + } + } + break; + case DFSIFrameType::LDU2_VOICE16: + { + ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE17: + { + ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + break; + case DFSIFrameType::LDU2_VOICE18: + { + ::memcpy(m_rxCall->netLDU2 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + m_rxCall->lsd1 = voice.additionalData[0U]; + m_rxCall->lsd2 = voice.additionalData[1U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC18 traffic missing metadata"); + } + } + break; + + default: + break; + } + + // increment our voice frame counter + m_rxCall->n++; + } + break; + + default: + break; + } + + hdrOffs += BlockHeader::LENGTH; + } + + m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // encode LDU1 if ready + if (m_rxCall->n == 9U) { + lc::LC lc = lc::LC(); + lc.setLCO(m_rxCall->lco); + lc.setMFId(m_rxCall->mfId); + + if (lc.isStandardMFId()) { + lc.setSrcId(m_rxCall->srcId); + lc.setDstId(m_rxCall->dstId); + } else { + uint8_t rsBuffer[P25_LDU_LC_FEC_LENGTH_BYTES]; + ::memset(rsBuffer, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); + + rsBuffer[0U] = m_rxCall->lco; + rsBuffer[1U] = m_rxCall->mfId; + rsBuffer[2U] = m_rxCall->serviceOptions; + rsBuffer[3U] = (m_rxCall->dstId >> 16) & 0xFFU; + rsBuffer[4U] = (m_rxCall->dstId >> 8) & 0xFFU; + rsBuffer[5U] = (m_rxCall->dstId >> 0) & 0xFFU; + rsBuffer[6U] = (m_rxCall->srcId >> 16) & 0xFFU; + rsBuffer[7U] = (m_rxCall->srcId >> 8) & 0xFFU; + rsBuffer[8U] = (m_rxCall->srcId >> 0) & 0xFFU; + + // combine bytes into ulong64_t (8 byte) value + ulong64_t rsValue = 0U; + rsValue = rsBuffer[1U]; + rsValue = (rsValue << 8) + rsBuffer[2U]; + rsValue = (rsValue << 8) + rsBuffer[3U]; + rsValue = (rsValue << 8) + rsBuffer[4U]; + rsValue = (rsValue << 8) + rsBuffer[5U]; + rsValue = (rsValue << 8) + rsBuffer[6U]; + rsValue = (rsValue << 8) + rsBuffer[7U]; + rsValue = (rsValue << 8) + rsBuffer[8U]; + + lc.setRS(rsValue); + } + + bool emergency = ((m_rxCall->serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag + bool encryption = ((m_rxCall->serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag + uint8_t priority = ((m_rxCall->serviceOptions & 0xFFU) & 0x07U); // Priority + lc.setEmergency(emergency); + lc.setEncrypted(encryption); + lc.setPriority(priority); + + data::LowSpeedData lsd = data::LowSpeedData(); + lsd.setLSD1(m_rxCall->lsd1); + lsd.setLSD2(m_rxCall->lsd2); + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // generate NID + m_nid->encode(buffer + 2U, DUID::LDU1); + + // generate LDU1 Data + lc.encodeLDU1(buffer + 2U); + + // generate Low Speed Data + lsd.process(buffer + 2U); + + // generate audio + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 10U, 0U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 26U, 1U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 55U, 2U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 80U, 3U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 105U, 4U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 130U, 5U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 155U, 6U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 180U, 7U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 204U, 8U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); + } + + // encode LDU2 if ready + if (m_rxCall->n == 18U) { + lc::LC lc = lc::LC(); + lc.setMI(m_rxCall->MI); + lc.setAlgId(m_rxCall->algoId); + lc.setKId(m_rxCall->kId); + + data::LowSpeedData lsd = data::LowSpeedData(); + lsd.setLSD1(m_rxCall->lsd1); + lsd.setLSD2(m_rxCall->lsd2); + + // generate Sync + Sync::addP25Sync(buffer + 2U); + + // generate NID + m_nid->encode(buffer + 2U, DUID::LDU2); + + // generate LDU2 data + lc.encodeLDU2(buffer + 2U); + + // generate Low Speed Data + lsd.process(buffer + 2U); + + // generate audio + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 10U, 0U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 26U, 1U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 55U, 2U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 80U, 3U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 105U, 4U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 130U, 5U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 155U, 6U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 180U, 7U); + m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 204U, 8U); + + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x01U; + storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + m_rxCall->n = 0; + } +} + +/* Helper to add a V.24 data frame to the P25 TX queue with the proper timestamp and formatting */ + +void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType) +{ + assert(data != nullptr); + assert(len > 0U); + + if (m_debug) + LogDebug(LOG_MODEM, "ModemV24::queueP25Frame() msgType = $%02X", msgType); + if (m_trace) + Utils::dump(1U, "ModemV24::queueP25Frame() data", data, len); + + // get current time in ms + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // timestamp for this message (in ms) + uint64_t msgTime = 0U; + + // if this is our first message, timestamp is just now + the jitter buffer offset in ms + if (m_lastP25Tx == 0U) { + msgTime = now + m_jitter; + + // if the message type requests no jitter delay -- just set the message time to now + if (msgType == STT_NON_IMBE_NO_JITTER) + msgTime = now; + } + // if we had a message before this, calculate the new timestamp dynamically + else { + // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above + if ((int64_t)(now - m_lastP25Tx) > m_jitter) { + msgTime = now + m_jitter; + } + // otherwise, we time out messages as required by the message type + else { + if (msgType == STT_IMBE) { + // IMBEs must go out at 20ms intervals + msgTime = m_lastP25Tx + 20U; + } else { + // Otherwise we don't care, we use 5ms since that's the theoretical minimum time a 9600 baud message can take + msgTime = m_lastP25Tx + 5U; + } + } + } + + len += 4U; + + // convert 16-bit length to 2 bytes + uint8_t length[2U]; + if (len > 255U) + length[0U] = (len >> 8U) & 0xFFU; + else + length[0U] = 0x00U; + length[1U] = len & 0xFFU; + + m_txP25Queue.addData(length, 2U); + + // add the data tag + uint8_t tag = TAG_DATA; + m_txP25Queue.addData(&tag, 1U); + + // convert 64-bit timestamp to 8 bytes and add + uint8_t tsBytes[8U]; + assert(sizeof msgTime == 8U); + ::memcpy(tsBytes, &msgTime, 8U); + m_txP25Queue.addData(tsBytes, 8U); + + // add the DVM start byte, length byte, CMD byte, and padding 0 + uint8_t header[4U]; + header[0U] = DVM_SHORT_FRAME_START; + header[1U] = len & 0xFFU; + header[2U] = CMD_P25_DATA; + header[3U] = 0x00U; + m_txP25Queue.addData(header, 4U); + + // add the data + m_txP25Queue.addData(data, len - 4U); + + // update the last message time + m_lastP25Tx = msgTime; +} + +/* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ + +void ModemV24::startOfStream(const p25::lc::LC& control) +{ + m_txCallInProgress = true; + + MotStartOfStream start = MotStartOfStream(); + start.setStartStop(StartStopFlag::START); + start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + + // create buffer for bytes and encode + uint8_t startBuf[start.LENGTH]; + ::memset(startBuf, 0x00U, start.LENGTH); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH); + + queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + control.getMI(mi); + + uint8_t vhdr[DFSI_VHDR_LEN]; + ::memset(vhdr, 0x00U, DFSI_VHDR_LEN); + + ::memcpy(vhdr, mi, MI_LENGTH_BYTES); + + vhdr[9U] = control.getMFId(); + vhdr[10U] = control.getAlgId(); + __SET_UINT16B(control.getKId(), vhdr, 11U); + __SET_UINT16B(control.getDstId(), vhdr, 13U); + + // perform RS encoding + m_rs.encode362017(vhdr); + + // convert the binary bytes to hex bytes + uint8_t raw[DFSI_VHDR_RAW_LEN]; + uint32_t offset = 0; + for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { + raw[i] = Utils::bin2Hex(vhdr, offset); + } + + // prepare VHDR1 + MotVoiceHeader1 vhdr1 = MotVoiceHeader1(); + vhdr1.startOfStream->setStartStop(StartStopFlag::START); + vhdr1.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + vhdr1.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + + ::memcpy(vhdr1.header, raw, 8U); + ::memcpy(vhdr1.header + 9U, raw + 8U, 8U); + ::memcpy(vhdr1.header + 18U, raw + 16U, 2U); + + // encode VHDR1 and send + uint8_t vhdr1Buf[vhdr1.LENGTH]; + ::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH); + vhdr1.encode(vhdr1Buf); + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH); + + queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE); + + // prepare VHDR2 + MotVoiceHeader2 vhdr2 = MotVoiceHeader2(); + ::memcpy(vhdr2.header, raw + 18U, 8U); + ::memcpy(vhdr2.header + 9U, raw + 26U, 8U); + ::memcpy(vhdr2.header + 18U, raw + 34U, 2U); + + // encode VHDR2 and send + uint8_t vhdr2Buf[vhdr2.LENGTH]; + ::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH); + vhdr2.encode(vhdr2Buf); + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH); + + queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE); +} + +/* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ + +void ModemV24::endOfStream() +{ + MotStartOfStream end = MotStartOfStream(); + end.setStartStop(StartStopFlag::STOP); + + // create buffer and encode + uint8_t endBuf[MotStartOfStream::LENGTH]; + ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); + end.encode(endBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH); + + queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + + m_txCallInProgress = false; +} + +/* Helper to generate the NID value. */ + +uint16_t ModemV24::generateNID(DUID::E duid) +{ + uint8_t nid[2U]; + ::memset(nid, 0x00U, 2U); + + nid[0U] = (m_p25NAC >> 4) & 0xFFU; + nid[1U] = (m_p25NAC << 4) & 0xF0U; + nid[1U] |= duid; + + return __GET_UINT16B(nid, 0U); +} + +/* Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device. */ + +void ModemV24::startOfStreamTIA(const p25::lc::LC& control) +{ + m_txCallInProgress = true; + m_superFrameCnt = 0U; + + p25::lc::LC lc = p25::lc::LC(control); + + uint16_t length = 0U; + uint8_t buffer[P25_HDU_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(1U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + + // generate start of stream + StartOfStream start = StartOfStream(); + start.setNID(generateNID()); + start.encode(buffer + 2U); + length += StartOfStream::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStreamTIA() StartOfStream", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + control.getMI(mi); + + uint8_t vhdr[P25_HDU_LENGTH_BYTES]; + ::memset(vhdr, 0x00U, P25_HDU_LENGTH_BYTES); + + ::memcpy(vhdr, mi, MI_LENGTH_BYTES); + + vhdr[9U] = control.getMFId(); + vhdr[10U] = control.getAlgId(); + __SET_UINT16B(control.getKId(), vhdr, 11U); + __SET_UINT16B(control.getDstId(), vhdr, 13U); + + // perform RS encoding + m_rs.encode362017(vhdr); + + // convert the binary bytes to hex bytes + uint8_t raw[DFSI_VHDR_RAW_LEN]; + uint32_t offset = 0; + for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { + raw[i] = Utils::bin2Hex(vhdr, offset); + } + + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); + length = 0U; + + // generate control octet + ctrl.setBlockHeaderCnt(2U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header 1 + hdr.setBlockType(BlockType::VOICE_HEADER_P1); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + length += BlockHeader::LENGTH; + + // generate voice header 1 + uint8_t hdu[P25_HDU_LENGTH_BYTES]; + ::memset(hdu, 0x00U, P25_HDU_LENGTH_BYTES); + lc.encodeHDU(hdu, true); + + // prepare VHDR1 + buffer[3U] = DFSIFrameType::MOT_VHDR_1; + ::memcpy(buffer + 4U, raw, 18U); + length += 19U; // 18 Golay + Block Type Marker + + start.encode(buffer + length); + length += StartOfStream::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader1", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); + + ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); + length = 0U; + + // generate control octet + ctrl.setBlockHeaderCnt(2U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header 1 + hdr.setBlockType(BlockType::VOICE_HEADER_P2); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + length += BlockHeader::LENGTH; + + // prepare VHDR2 + buffer[3U] = DFSIFrameType::MOT_VHDR_2; + ::memcpy(buffer + 4U, raw + 18U, 18U); + length += 19U; // 18 Golay + Block Type Marker + + start.encode(buffer + length); + length += StartOfStream::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader2", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); +} + +/* Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device. */ + +void ModemV24::endOfStreamTIA() +{ + m_superFrameCnt = 0U; + + uint16_t length = 0U; + uint8_t buffer[2U]; + ::memset(buffer, 0x00U, 2U); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(1U); + ctrl.encode(buffer); + length += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::END_OF_STREAM); + hdr.encode(buffer + 1U); + length += BlockHeader::LENGTH; + + if (m_trace) + Utils::dump(1U, "ModemV24::endOfStreamTIA() EndOfStream", buffer, length); + + queueP25Frame(buffer, length, STT_NON_IMBE); + + m_txCallInProgress = false; +} + +/* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ + +void ModemV24::convertFromAir(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAir() data", data, length); + + uint8_t ldu[9U * 25U]; + ::memset(ldu, 0x00U, 9 * 25U); + + // decode the NID + bool valid = m_nid->decode(data + 2U); + if (!valid) + return; + + DUID::E duid = m_nid->getDUID(); + + // handle individual DUIDs + lc::LC lc = lc::LC(); + data::LowSpeedData lsd = data::LowSpeedData(); + switch (duid) { + case DUID::HDU: + { + bool ret = lc.decodeHDU(data + 2U); + if (!ret) { + LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); + } + + startOfStream(lc); + } + break; + case DUID::LDU1: + { + bool ret = lc.decodeLDU1(data + 2U, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); + return; + } + + lsd.process(data + 2U); + + // late entry? + if (!m_txCallInProgress) { + startOfStream(lc); + if (m_debug) + LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); + } + + // generate audio + m_audio.decode(data + 2U, ldu + 10U, 0U); + m_audio.decode(data + 2U, ldu + 26U, 1U); + m_audio.decode(data + 2U, ldu + 55U, 2U); + m_audio.decode(data + 2U, ldu + 80U, 3U); + m_audio.decode(data + 2U, ldu + 105U, 4U); + m_audio.decode(data + 2U, ldu + 130U, 5U); + m_audio.decode(data + 2U, ldu + 155U, 6U); + m_audio.decode(data + 2U, ldu + 180U, 7U); + m_audio.decode(data + 2U, ldu + 204U, 8U); + } + break; + case DUID::LDU2: + { + bool ret = lc.decodeLDU2(data + 2U); + if (!ret) { + LogWarning(LOG_MODEM, P25_LDU2_STR ", undecodable LC"); + return; + } + + lsd.process(data + 2U); + + // generate audio + m_audio.decode(data + 2U, ldu + 10U, 0U); + m_audio.decode(data + 2U, ldu + 26U, 1U); + m_audio.decode(data + 2U, ldu + 55U, 2U); + m_audio.decode(data + 2U, ldu + 80U, 3U); + m_audio.decode(data + 2U, ldu + 105U, 4U); + m_audio.decode(data + 2U, ldu + 130U, 5U); + m_audio.decode(data + 2U, ldu + 155U, 6U); + m_audio.decode(data + 2U, ldu + 180U, 7U); + m_audio.decode(data + 2U, ldu + 204U, 8U); + } + break; + + case DUID::TDU: + case DUID::TDULC: + if (m_txCallInProgress) + endOfStream(); + break; + + case DUID::PDU: + break; + + case DUID::TSDU: + { + lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); + if (!tsbk.decode(data + 2U)) { + LogWarning(LOG_MODEM, P25_TSDU_STR ", undecodable LC"); + return; + } + + MotStartOfStream startOfStream = MotStartOfStream(); + startOfStream.setStartStop(StartStopFlag::START); + startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + startOfStream.setStreamType(StreamTypeFlag::TSBK); + + // create buffer and encode + uint8_t startBuf[MotStartOfStream::LENGTH]; + ::memset(startBuf, 0x00U, MotStartOfStream::LENGTH); + startOfStream.encode(startBuf); + + queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + + MotTSBKFrame tf = MotTSBKFrame(); + tf.startOfStream->setStartStop(StartStopFlag::START); + tf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + tf.startOfStream->setStreamType(StreamTypeFlag::TSBK); + delete[] tf.tsbkData; + + tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; + ::memset(tf.tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES); + ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); + + // create buffer and encode + uint8_t tsbkBuf[MotTSBKFrame::LENGTH]; + ::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH); + tf.encode(tsbkBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH); + + queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER); + + MotStartOfStream endOfStream = MotStartOfStream(); + endOfStream.setStartStop(StartStopFlag::STOP); + endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + endOfStream.setStreamType(StreamTypeFlag::TSBK); + + // create buffer and encode + uint8_t endBuf[MotStartOfStream::LENGTH]; + ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); + endOfStream.encode(endBuf); + + queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + } + break; + + default: + break; + } + + if (duid == DUID::LDU1 || duid == DUID::LDU2) { + uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); + + if (duid == DUID::LDU1) { + rs[0U] = lc.getLCO(); // LCO + + // split ulong64_t (8 byte) value into bytes + rs[1U] = (uint8_t)((lc.getRS() >> 56) & 0xFFU); + rs[2U] = (uint8_t)((lc.getRS() >> 48) & 0xFFU); + rs[3U] = (uint8_t)((lc.getRS() >> 40) & 0xFFU); + rs[4U] = (uint8_t)((lc.getRS() >> 32) & 0xFFU); + rs[5U] = (uint8_t)((lc.getRS() >> 24) & 0xFFU); + rs[6U] = (uint8_t)((lc.getRS() >> 16) & 0xFFU); + rs[7U] = (uint8_t)((lc.getRS() >> 8) & 0xFFU); + rs[8U] = (uint8_t)((lc.getRS() >> 0) & 0xFFU); + + // encode RS (24,12,13) FEC + m_rs.encode241213(rs); + } else { + // generate MI data + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + lc.getMI(mi); + + for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) + rs[i] = mi[i]; // Message Indicator + + rs[9U] = lc.getAlgId(); // Algorithm ID + rs[10U] = (lc.getKId() >> 8) & 0xFFU; // Key ID + rs[11U] = (lc.getKId() >> 0) & 0xFFU; // ... + + // encode RS (24,16,9) FEC + m_rs.encode24169(rs); + } + + for (int n = 0; n < 9; n++) { + uint8_t* buffer = nullptr; + uint16_t bufferSize = 0; + MotFullRateVoice voice = MotFullRateVoice(); + + switch (n) { + case 0: // VOICE1/10 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); + + MotStartVoiceFrame svf = MotStartVoiceFrame(); + svf.startOfStream->setStartStop(StartStopFlag::START); + svf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + svf.fullRateVoice->setFrameType(voice.getFrameType()); + svf.fullRateVoice->setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); + svf.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); + + ::memcpy(svf.fullRateVoice->imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); + + buffer = new uint8_t[MotStartVoiceFrame::LENGTH]; + ::memset(buffer, 0x00U, MotStartVoiceFrame::LENGTH); + svf.encode(buffer); + bufferSize = MotStartVoiceFrame::LENGTH; + } + break; + case 1: // VOICE2/11 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); + voice.setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); + ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); + } + break; + case 2: // VOICE3/12 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE3 : DFSIFrameType::LDU2_VOICE12); + ::memcpy(voice.imbeData, ldu + 55U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + // copy additional data + voice.additionalData[0U] = rs[0U]; + voice.additionalData[1U] = rs[1U]; + voice.additionalData[2U] = rs[2U]; + } + break; + case 3: // VOICE4/13 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE4 : DFSIFrameType::LDU2_VOICE13); + ::memcpy(voice.imbeData, ldu + 80U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + // copy additional data + voice.additionalData[0U] = rs[3U]; + voice.additionalData[1U] = rs[4U]; + voice.additionalData[2U] = rs[5U]; + } + break; + case 4: // VOICE5/14 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE5 : DFSIFrameType::LDU2_VOICE14); + ::memcpy(voice.imbeData, ldu + 105U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[6U]; + voice.additionalData[1U] = rs[7U]; + voice.additionalData[2U] = rs[8U]; + } + break; + case 5: // VOICE6/15 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE6 : DFSIFrameType::LDU2_VOICE15); + ::memcpy(voice.imbeData, ldu + 130U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[9U]; + voice.additionalData[1U] = rs[10U]; + voice.additionalData[2U] = rs[11U]; + } + break; + case 6: // VOICE7/16 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE7 : DFSIFrameType::LDU2_VOICE16); + ::memcpy(voice.imbeData, ldu + 155U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[12U]; + voice.additionalData[1U] = rs[13U]; + voice.additionalData[2U] = rs[14U]; + } + break; + case 7: // VOICE8/17 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE8 : DFSIFrameType::LDU2_VOICE17); + ::memcpy(voice.imbeData, ldu + 180U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = rs[15U]; + voice.additionalData[1U] = rs[16U]; + voice.additionalData[2U] = rs[17U]; + } + break; + case 8: // VOICE9/18 + { + voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE9 : DFSIFrameType::LDU2_VOICE18); + ::memcpy(voice.imbeData, ldu + 204U, RAW_IMBE_LENGTH_BYTES); + + voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; + ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); + + voice.additionalData[0U] = lsd.getLSD1(); + voice.additionalData[1U] = lsd.getLSD2(); + } + break; + } + + // For n=0 (VHDR1/10) case we create the buffer in the switch, for all other frame types we do that here + if (n != 0) { + buffer = new uint8_t[voice.size()]; + ::memset(buffer, 0x00U, voice.size()); + voice.encode(buffer); + bufferSize = voice.size(); + } + + if (buffer != nullptr) { + if (m_trace) { + Utils::dump("ModemV24::convertFromAir() Encoded V.24 Voice Frame Data", buffer, bufferSize); + } + + queueP25Frame(buffer, bufferSize, STT_IMBE); + delete[] buffer; + } + } + } +} + +/* Internal helper to convert from TIA-102 air interface to TIA-102 DFSI. */ + +void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirTIA() data", data, length); + + uint8_t ldu[9U * 25U]; + ::memset(ldu, 0x00U, 9 * 25U); + + // decode the NID + bool valid = m_nid->decode(data + 2U); + if (!valid) + return; + + DUID::E duid = m_nid->getDUID(); + + // handle individual DUIDs + lc::LC lc = lc::LC(); + data::LowSpeedData lsd = data::LowSpeedData(); + switch (duid) { + case DUID::HDU: + { + bool ret = lc.decodeHDU(data + 2U); + if (!ret) { + LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); + } + + startOfStreamTIA(lc); + } + break; + case DUID::LDU1: + { + bool ret = lc.decodeLDU1(data + 2U, true); + if (!ret) { LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); return; } @@ -1309,7 +2275,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) // late entry? if (!m_txCallInProgress) { - startOfStream(lc); + startOfStreamTIA(lc); if (m_debug) LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); } @@ -1352,66 +2318,14 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) case DUID::TDU: case DUID::TDULC: if (m_txCallInProgress) - endOfStream(); + endOfStreamTIA(); break; case DUID::PDU: break; case DUID::TSDU: - { - lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); - if (!tsbk.decode(data + 2U)) { - LogWarning(LOG_MODEM, P25_TSDU_STR ", undecodable LC"); - return; - } - - MotStartOfStream startOfStream = MotStartOfStream(); - startOfStream.setStartStop(StartStopFlag::START); - startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - startOfStream.setStreamType(StreamTypeFlag::TSBK); - - // create buffer and encode - uint8_t startBuf[MotStartOfStream::LENGTH]; - ::memset(startBuf, 0x00U, MotStartOfStream::LENGTH); - startOfStream.encode(startBuf); - - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - - MotTSBKFrame tf = MotTSBKFrame(); - tf.startOfStream->setStartStop(StartStopFlag::START); - tf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - tf.startOfStream->setStreamType(StreamTypeFlag::TSBK); - delete[] tf.tsbkData; - - tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; - ::memset(tf.tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES); - ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); - - // create buffer and encode - uint8_t tsbkBuf[MotTSBKFrame::LENGTH]; - ::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH); - tf.encode(tsbkBuf); - - if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH); - - queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER); - - MotStartOfStream endOfStream = MotStartOfStream(); - endOfStream.setStartStop(StartStopFlag::STOP); - endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - endOfStream.setStreamType(StreamTypeFlag::TSBK); - - // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); - endOfStream.encode(endBuf); - - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - } - break; + break; default: break; @@ -1456,32 +2370,18 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) for (int n = 0; n < 9; n++) { uint8_t* buffer = nullptr; uint16_t bufferSize = 0; - MotFullRateVoice voice = MotFullRateVoice(); + FullRateVoice voice = FullRateVoice(); switch (n) { case 0: // VOICE1/10 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); - - MotStartVoiceFrame svf = MotStartVoiceFrame(); - svf.startOfStream->setStartStop(StartStopFlag::START); - svf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - svf.fullRateVoice->setFrameType(voice.getFrameType()); - svf.fullRateVoice->setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); - svf.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR); - - ::memcpy(svf.fullRateVoice->imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); - - buffer = new uint8_t[MotStartVoiceFrame::LENGTH]; - ::memset(buffer, 0x00U, MotStartVoiceFrame::LENGTH); - svf.encode(buffer); - bufferSize = MotStartVoiceFrame::LENGTH; + ::memcpy(voice.imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); } break; case 1: // VOICE2/11 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); - voice.setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR); ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); } break; @@ -1579,22 +2479,42 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) break; } - // For n=0 (VHDR1/10) case we create the buffer in the switch, for all other frame types we do that here - if (n != 0) { - buffer = new uint8_t[voice.size()]; - ::memset(buffer, 0x00U, voice.size()); - voice.encode(buffer); - bufferSize = voice.size(); - } + buffer = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // generate control octet + ControlOctet ctrl = ControlOctet(); + ctrl.setBlockHeaderCnt(1U); + ctrl.encode(buffer); + bufferSize += ControlOctet::LENGTH; + + // generate block header + BlockHeader hdr = BlockHeader(); + hdr.setBlockType(BlockType::FULL_RATE_VOICE); + hdr.encode(buffer + 1U); + bufferSize += BlockHeader::LENGTH; + + voice.setSuperframeCnt(m_superFrameCnt); + voice.encode(buffer + bufferSize); + bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAir() Encoded V.24 Voice Frame Data", buffer, bufferSize); + Utils::dump("ModemV24::convertFromAirTIA() Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); delete[] buffer; } } + + // bryanb: this is a naive way of incrementing the superframe counter, we basically just increment it after + // processing and LDU2 + if (duid == DUID::LDU2) { + if (m_superFrameCnt == 255U) + m_superFrameCnt = 0U; + else + m_superFrameCnt++; + } } } diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 71a126ed..cb48e31a 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.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 * */ /** @@ -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 diff --git a/src/host/modem/port/PseudoPTYPort.cpp b/src/host/modem/port/PseudoPTYPort.cpp index 3e7948dd..cf17279f 100644 --- a/src/host/modem/port/PseudoPTYPort.cpp +++ b/src/host/modem/port/PseudoPTYPort.cpp @@ -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 */ diff --git a/src/host/modem/port/UARTPort.cpp b/src/host/modem/port/UARTPort.cpp index 1f12a2df..cbd4592e 100644 --- a/src/host/modem/port/UARTPort.cpp +++ b/src/host/modem/port/UARTPort.cpp @@ -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 diff --git a/src/host/modem/port/UARTPort.h b/src/host/modem/port/UARTPort.h index 4aed835f..b5a4a1a7 100644 --- a/src/host/modem/port/UARTPort.h +++ b/src/host/modem/port/UARTPort.h @@ -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 diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 25f11586..fc3f2018 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -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(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(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 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 message = FSCMessage::createMessage(req->buffer); - if (message != nullptr) { - switch (message->getMessageId()) + std::unique_ptr message = FSCMessage::createMessage(req->buffer); + if (message != nullptr) { + switch (message->getMessageId()) { + case FSCMessageType::FSC_ACK: { - case FSCMessageType::FSC_ACK: + FSCACK* ackMessage = static_cast(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(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(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(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(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 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 lock(m_bufferMutex); m_buffer.addData(reply, count); } @@ -612,6 +856,7 @@ void V24UDPPort::getStatus() reply[11U] = 0U; + std::lock_guard 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 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 lock(m_bufferMutex); m_buffer.addData(reply, 5U); } \ No newline at end of file diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index ba114168..ac1f7b84 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -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 #include +#include 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 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. diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 8d2bc00f..80fc82bf 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -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) { diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 154d1ef5..172f61c0 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -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); diff --git a/src/host/p25/lookups/P25AffiliationLookup.cpp b/src/host/p25/lookups/P25AffiliationLookup.cpp index 72dda5e5..10bdbedf 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.cpp +++ b/src/host/p25/lookups/P25AffiliationLookup.cpp @@ -46,9 +46,9 @@ std::vector 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); diff --git a/src/host/p25/lookups/P25AffiliationLookup.h b/src/host/p25/lookups/P25AffiliationLookup.h index 56540b6a..875f524f 100644 --- a/src/host/p25/lookups/P25AffiliationLookup.h +++ b/src/host/p25/lookups/P25AffiliationLookup.h @@ -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: diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index b645ec12..9cd38c29 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -548,6 +548,12 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(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_ptrm_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. */ diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index de8c6e00..30071bca 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -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((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((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((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); } diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 111bcb20..7ff8fc58 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -202,11 +202,10 @@ namespace p25 * @brief Helper to write a P25 PDU packet. * @param[in] pdu Constructed PDU to transmit. * @param bitlength Length of PDU in bits. - * @param noNulls Flag indicating no trailing nulls should be transmitted. * @param imm Flag indicating the PDU should be written to the immediate queue. * @param ackRetry Flag indicating the PDU is being sent as an acknowledged retry. */ - void writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool noNulls = false, bool imm = false, bool ackRetry = false); + void writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm = false, bool ackRetry = false); /** * @brief Helper to write a network P25 PDU packet. * This will take buffered network PDU data and repeat it over the air. @@ -231,9 +230,8 @@ namespace p25 * @param ackStatus * @param llId Logical Link ID. * @param srcLlId Source Logical Link ID. - * @param noNulls Flag indicating no trailing nulls should be transmitted. */ - void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U, bool noNulls = false); + void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U); }; } // namespace packet } // namespace p25 diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index 8a8f530f..56f763ff 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017,2018 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -195,6 +195,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_lastDUID = DUID::LDU1; bool alreadyDecoded = false; + bool hduEncrypt = false; FrameType::E frameType = FrameType::DATA_UNIT; ulong64_t rsValue = 0U; if (m_p25->m_rfState == RS_RF_LISTENING) { @@ -392,6 +393,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfLC = lc; m_rfLastLDU1 = m_rfLC; + hduEncrypt = encrypted; m_lastRejectId = 0U; ::ActivityLog("P25", true, "RF %svoice transmission from %u to %s%u", encrypted ? "encrypted ": "", srcId, group ? "TG " : "", dstId); @@ -508,17 +510,17 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::HDU); - // Generate HDU + // generate HDU m_rfLC.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(buffer, DUID::HDU); @@ -660,6 +662,10 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfLastLDU1 = m_rfLC; } } + else { + // this might be the first LDU1 -- set the encryption flag if necessary + m_rfLC.setEncrypted(hduEncrypt); + } m_inbound = true; @@ -740,8 +746,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfErrs += errors; m_rfFrames++; - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::LDU1, frameType); @@ -857,8 +863,8 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfErrs += errors; m_rfFrames++; - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::LDU2); @@ -937,17 +943,17 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::HDU); - // Generate HDU + // generate HDU m_rfLC.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(buffer, DUID::HDU); @@ -989,8 +995,8 @@ bool Voice::process(uint8_t* data, uint32_t len) // generate NID m_p25->m_nid.encode(data + 2U, DUID::VSELP1); - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::VSELP1); @@ -1031,8 +1037,8 @@ bool Voice::process(uint8_t* data, uint32_t len) // generate NID m_p25->m_nid.encode(data + 2U, DUID::VSELP2); - // add busy bits - P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound); + // add status bits + P25Utils::addStatusBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, m_inbound, false); writeNetwork(data + 2U, DUID::VSELP2); @@ -1138,6 +1144,12 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L } } + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { + m_p25->m_rfTGHang.stop(); + } + // perform authoritative network TG hangtimer and traffic preemption if (m_p25->m_authoritative) { // don't process network frames if the destination ID's don't match and the network TG hang timer is running @@ -1347,6 +1359,17 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L break; case DUID::TDU: case DUID::TDULC: + if (duid == DUID::TDULC) { + std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data); + if (tdulc == nullptr) { + LogWarning(LOG_NET, P25_TDULC_STR ", undecodable TDULC"); + } + else { + if (tdulc->getLCO() != LCO::CALL_TERM) + break; + } + } + // ignore a TDU that doesn't contain our destination ID if (control.getDstId() != m_p25->m_netLastDstId) { return false; @@ -1507,14 +1530,14 @@ void Voice::writeNet_TDU() 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::TDU); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); m_p25->addFrame(buffer, P25_TDU_FRAME_LENGTH_BYTES + 2U, true); @@ -1776,17 +1799,17 @@ void Voice::writeNet_LDU1() uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::HDU); - // Generate header + // generate HDU m_netLC.encodeHDU(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1864,13 +1887,13 @@ void Voice::writeNet_LDU1() uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::LDU1); - // Generate LDU1 data + // generate LDU1 data if (!m_netLC.isStandardMFId()) { if (m_debug) { LogDebug(LOG_NET, "P25, LDU1 LC, non-standard payload, lco = $%02X, mfId = $%02X", m_netLC.getLCO(), m_netLC.getMFId()); @@ -1880,7 +1903,7 @@ void Voice::writeNet_LDU1() m_netLC.encodeLDU1(buffer + 2U); - // Add the Audio + // add the Audio m_audio.encode(buffer + 2U, m_netLDU1 + 10U, 0U); m_audio.encode(buffer + 2U, m_netLDU1 + 26U, 1U); m_audio.encode(buffer + 2U, m_netLDU1 + 55U, 2U); @@ -1891,13 +1914,13 @@ void Voice::writeNet_LDU1() m_audio.encode(buffer + 2U, m_netLDU1 + 180U, 7U); m_audio.encode(buffer + 2U, m_netLDU1 + 204U, 8U); - // Add the Low Speed Data + // add the Low Speed Data m_netLSD.setLSD1(lsd.getLSD1()); m_netLSD.setLSD2(lsd.getLSD2()); m_netLSD.encode(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1962,16 +1985,16 @@ void Voice::writeNet_LDU2() uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); - // Generate Sync + // generate Sync Sync::addP25Sync(buffer + 2U); - // Generate NID + // generate NID m_p25->m_nid.encode(buffer + 2U, DUID::LDU2); - // Generate LDU2 data + // generate LDU2 data m_netLC.encodeLDU2(buffer + 2U); - // Add the Audio + // add the Audio m_audio.encode(buffer + 2U, m_netLDU2 + 10U, 0U); m_audio.encode(buffer + 2U, m_netLDU2 + 26U, 1U); m_audio.encode(buffer + 2U, m_netLDU2 + 55U, 2U); @@ -1982,13 +2005,13 @@ void Voice::writeNet_LDU2() m_audio.encode(buffer + 2U, m_netLDU2 + 180U, 7U); m_audio.encode(buffer + 2U, m_netLDU2 + 204U, 8U); - // Add the Low Speed Data + // add the Low Speed Data m_netLSD.setLSD1(lsd.getLSD1()); m_netLSD.setLSD2(lsd.getLSD2()); m_netLSD.encode(buffer + 2U); - // Add busy bits - P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false); + // add status bits + P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; diff --git a/src/host/setup/ChannelConfigSetWnd.h b/src/host/setup/ChannelConfigSetWnd.h index 574f646d..e4a4af03 100644 --- a/src/host/setup/ChannelConfigSetWnd.h +++ b/src/host/setup/ChannelConfigSetWnd.h @@ -149,6 +149,9 @@ private: m_channelNo.setRange(0, 4095); m_channelNo.setShadow(false); m_channelNo.addCallback("changed", [&]() { + if (!m_radioChNo.isChecked()) + return; + m_setup->m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo.getValue()); m_setup->calculateRxTxFreq(); m_channelFreq.setValue(m_setup->m_txFrequency); @@ -167,34 +170,17 @@ private: m_channelFreq.setValue(m_setup->m_txFrequency); m_channelFreq.setShadow(false); m_channelFreq.addCallback("changed", [&]() { - entry = m_setup->m_idenTable->find(m_setup->m_channelId); - - uint32_t txFrequency = m_channelFreq.getValue(); - - uint32_t prevTxFrequency = m_setup->m_txFrequency; - m_setup->m_txFrequency = txFrequency; - uint32_t prevRxFrequency = m_setup->m_rxFrequency; - m_setup->m_rxFrequency = m_setup->m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); - - float spaceHz = entry.chSpaceKhz() * 1000; + if (!m_radioChFreq.isChecked()) + return; - uint32_t rootFreq = m_setup->m_txFrequency - entry.baseFrequency(); - uint8_t prevChannelNo = m_setup->m_channelNo; - m_setup->m_channelNo = (uint32_t)(rootFreq / spaceHz); - - if (m_setup->m_channelNo < 0 || m_setup->m_channelNo > 4096) { - m_setup->m_channelNo = prevChannelNo; - m_setup->m_txFrequency = prevTxFrequency; - m_setup->m_rxFrequency = prevRxFrequency; - } - - m_setup->m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_setup->m_channelNo); - m_setup->calculateRxTxFreq(); + uint32_t txFrequency = (uint32_t)(m_channelFreq.getValue()); + m_setup->calculateRxTxFreq(false, txFrequency); m_channelNo.setValue(m_setup->m_channelNo); if (m_setup->m_isConnected) { m_setup->writeRFParams(); } }); + m_hzLabel.setGeometry(FPoint(40, 12), FSize(5, 1)); } diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 28112b1d..a623103a 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -598,7 +598,7 @@ void HostSetup::saveConfig() /* Helper to calculate the Rx/Tx frequencies. */ -bool HostSetup::calculateRxTxFreq(bool consoleDisplay) +bool HostSetup::calculateRxTxFreq(bool consoleDisplay, uint32_t txFrequency) { IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { @@ -610,37 +610,59 @@ bool HostSetup::calculateRxTxFreq(bool consoleDisplay) return false; } - yaml::Node systemConf = m_conf["system"]; - yaml::Node rfssConfig = systemConf["config"]; - m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); - if (m_channelNo == 0U) { // clamp to 1 - m_channelNo = 1U; - } - if (m_channelNo > 4095U) { // clamp to 4095 - m_channelNo = 4095U; - } + if (txFrequency > 0U) { + uint32_t prevTxFrequency = m_txFrequency; + m_txFrequency = txFrequency; + uint32_t prevRxFrequency = m_rxFrequency; + m_rxFrequency = m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); - if (m_startupDuplex) { - if (entry.txOffsetMhz() == 0U) { - if (consoleDisplay) { - g_logDisplayLevel = 1U; - } + float spaceHz = entry.chSpaceKhz() * 1000.0; - ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); - return false; - } + uint32_t rootFreq = m_txFrequency - entry.baseFrequency(); + uint8_t prevChannelNo = m_channelNo; + m_channelNo = (uint32_t)(rootFreq / spaceHz); - uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); - float calcTxOffset = entry.txOffsetMhz() * 1000000.0; + if (m_channelNo < 0 || m_channelNo > 4096) { + m_channelNo = prevChannelNo; + m_txFrequency = prevTxFrequency; + m_rxFrequency = prevRxFrequency; + } - m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + (int32_t)calcTxOffset); - m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo); } else { - uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + yaml::Node systemConf = m_conf["system"]; + yaml::Node rfssConfig = systemConf["config"]; + m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); + if (m_channelNo == 0U) { // clamp to 1 + m_channelNo = 1U; + } + if (m_channelNo > 4095U) { // clamp to 4095 + m_channelNo = 4095U; + } + + if (m_startupDuplex) { + if (entry.txOffsetMhz() == 0U) { + if (consoleDisplay) { + g_logDisplayLevel = 1U; + } + + ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); + return false; + } - m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); - m_txFrequency = m_rxFrequency; + uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + float calcTxOffset = entry.txOffsetMhz() * 1000000.0; + + m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + (int32_t)calcTxOffset); + m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + } + else { + uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + + m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + m_txFrequency = m_rxFrequency; + } } if (m_isHotspot) { @@ -852,7 +874,7 @@ bool HostSetup::createModem(bool consoleDisplay) m_modem->setLevels(rxLevel, txLevel, txLevel, txLevel, txLevel); m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj); m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); - m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 100U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, + m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 95U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, afcEnable, afcKI, afcKP, afcRange); m_modem->setSoftPot(rxCoarsePot, rxFinePot, txCoarsePot, txFinePot, rssiCoarsePot, rssiFinePot); diff --git a/src/host/setup/HostSetup.h b/src/host/setup/HostSetup.h index 2c0d36c1..8d7c6545 100644 --- a/src/host/setup/HostSetup.h +++ b/src/host/setup/HostSetup.h @@ -218,9 +218,10 @@ protected: /** * @brief Helper to calculate the Rx/Tx frequencies. * @param consoleDisplay Flag indicating output should goto the console log. + * @param txFrequency Transmit frequency to use (this will auto calculate the Rx frequency from Tx frequency). * @returns bool True, if Rx/Tx frequencies are calculated, otherwise false. */ - bool calculateRxTxFreq(bool consoleDisplay = false); + bool calculateRxTxFreq(bool consoleDisplay = false, uint32_t txFrequency = 0U); /** * @brief Helper to log the system configuration parameters. */ diff --git a/src/host/setup/SetupApplication.h b/src/host/setup/SetupApplication.h index 67b48e0e..07936da6 100644 --- a/src/host/setup/SetupApplication.h +++ b/src/host/setup/SetupApplication.h @@ -193,15 +193,15 @@ protected: uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); - // Generate Sync + // generate Sync p25::Sync::addP25Sync(data + 2U); - // Generate NID + // generate NID std::unique_ptr nid = std::make_unique(1U); nid->encode(data + 2U, DUID::TDU); - // Add busy bits - p25::P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false); + // add status bits + p25::P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); data[0U] = modem::TAG_EOT; data[1U] = 0x00U; diff --git a/src/monitor/MonitorMain.cpp b/src/monitor/MonitorMain.cpp index ba6aea8c..36cb43ae 100644 --- a/src/monitor/MonitorMain.cpp +++ b/src/monitor/MonitorMain.cpp @@ -67,7 +67,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()); @@ -134,7 +134,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\n"); if (argc == 2) exit(EXIT_SUCCESS); @@ -186,7 +186,7 @@ int main(int argc, char** argv) } ::LogInfo(__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" \ ">> Host Monitor\r\n"); diff --git a/src/monitor/MonitorMainWnd.h b/src/monitor/MonitorMainWnd.h index b71824c3..d72a0ef0 100644 --- a/src/monitor/MonitorMainWnd.h +++ b/src/monitor/MonitorMainWnd.h @@ -102,7 +102,7 @@ public: FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" L"" + __BANNER__ + L"\n" L"Version " + __VER__ + L"\n\n" - L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); info.setCenterText(); diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp index b7b1a8bb..d3d2b88a 100644 --- a/src/remote/RESTClientMain.cpp +++ b/src/remote/RESTClientMain.cpp @@ -346,7 +346,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\n"); if (argc == 2) exit(EXIT_SUCCESS); diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index 30b57362..5ed79fd0 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -997,7 +997,7 @@ void* threadNetworkPump(void* arg) 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()); @@ -1082,7 +1082,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\n"); if (argc == 2) exit(EXIT_SUCCESS); @@ -1133,18 +1133,10 @@ int main(int argc, char** argv) return 1; } - if (!g_webSocketMode) { - ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2017-2024 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" \ - ">> FNE System View\r\n"); - } else { - ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ - "Copyright (c) 2024 DVMProject (https://github.com/dvmproject) Authors.\r\n" \ - "This program is non-free software; redistribution is strictly prohibited.\r\n" \ - "RESTRICTED CONFIDENTIAL PROPRIETARY. DO NOT DISTRIBUTE.\r\n" \ - ">> FNE System View\r\n"); - } + ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\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" \ + ">> FNE System View\r\n"); try { ret = yaml::Parse(g_conf, g_iniFile.c_str()); diff --git a/src/sysview/SysViewMainWnd.h b/src/sysview/SysViewMainWnd.h index 2b3ac015..cc420b19 100644 --- a/src/sysview/SysViewMainWnd.h +++ b/src/sysview/SysViewMainWnd.h @@ -180,7 +180,7 @@ public: FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" L"" + __BANNER__ + L"\n" L"Version " + __VER__ + L"\n\n" - L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); info.setCenterText(); diff --git a/src/sysview/TransmitWndBase.h b/src/sysview/TransmitWndBase.h index 9411841f..5c5394d4 100644 --- a/src/sysview/TransmitWndBase.h +++ b/src/sysview/TransmitWndBase.h @@ -338,20 +338,18 @@ protected: 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 (g_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", diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index 29f73048..1de24f5d 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -209,6 +209,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 lookups not available yet."); + goto tid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); @@ -341,6 +346,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 lookups not available yet."); + goto rid_lookup_cleanup; // yes - I hate myself; but this is quick + } + // store to file std::unique_ptr __str = std::make_unique(decompressedLen + 1U); char* str = __str.get(); diff --git a/src/tged/TGEdMain.cpp b/src/tged/TGEdMain.cpp index 589f16d1..da216e83 100644 --- a/src/tged/TGEdMain.cpp +++ b/src/tged/TGEdMain.cpp @@ -67,7 +67,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()); @@ -134,7 +134,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\n"); if (argc == 2) exit(EXIT_SUCCESS); @@ -186,7 +186,7 @@ int main(int argc, char** argv) } ::LogInfo(__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" \ ">> Talkgroup Rules Editor\r\n"); diff --git a/src/tged/TGEdMain.h b/src/tged/TGEdMain.h index 7f3c5db0..128a0817 100644 --- a/src/tged/TGEdMain.h +++ b/src/tged/TGEdMain.h @@ -31,6 +31,44 @@ #undef __EXE_NAME__ #define __EXE_NAME__ "tged" +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * bryanb: This is some low-down, dirty, C++ hack-o-ramma. + */ + +/** + * @brief Implements RTTI type defining. + * @typedef Tag + */ +template +struct RTTIResult { + typedef typename Tag::type type; + static type ptr; +}; + +template +typename RTTIResult::type RTTIResult::ptr; + +/** + * @brief Implements nasty hack to access private members of a class. + * @typedef Tag + * @typedef TypePtr + */ +template +struct HackTheGibson : RTTIResult { + /* fill it ... */ + struct filler { + filler() { RTTIResult::ptr = TypePtr; } + }; + static filler fillerObj; +}; + +template +typename HackTheGibson::filler HackTheGibson::fillerObj; + // --------------------------------------------------------------------------- // Externs // --------------------------------------------------------------------------- diff --git a/src/tged/TGEdMainWnd.h b/src/tged/TGEdMainWnd.h index bf61ffc1..2182f720 100644 --- a/src/tged/TGEdMainWnd.h +++ b/src/tged/TGEdMainWnd.h @@ -84,7 +84,7 @@ public: FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" L"" + __BANNER__ + L"\n" L"Version " + __VER__ + L"\n\n" - L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Copyright (c) 2017-2025 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); info.setCenterText(); diff --git a/src/tged/TGEditPeerListWnd.h b/src/tged/TGEditPeerListWnd.h index b7d984d1..6b8314b4 100644 --- a/src/tged/TGEditPeerListWnd.h +++ b/src/tged/TGEditPeerListWnd.h @@ -219,12 +219,18 @@ private: */ void addEntry() { + LogMessage(LOG_HOST, "Adding %s peer ID %s from TG %s (%u)", m_title.c_str(), m_entry.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + if (m_entry.getText() == "") { - m_listBox.insert(std::to_string(0U)); + peerList.push_back(0U); } else { - m_listBox.insert(m_entry.getText()); + uint32_t peerId = ::atoi(m_entry.getText().c_str()); + peerList.push_back(peerId); } + loadList(); + //setFocusWidget(&m_listBox); redraw(); } @@ -238,9 +244,20 @@ private: size_t curItem = m_listBox.currentItem(); auto item = m_listBox.getItem(curItem); - LogMessage(LOG_HOST, "Removing %s peer ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), - m_rule.name().c_str(), m_rule.source().tgId()); - m_listBox.remove(curItem); + if (item.getText() != "") { + uint32_t peerId = ::atoi(item.getText().c_str()); + for (std::vector::iterator it = peerList.begin(); it != peerList.end(); it++) { + auto entry = *it; + if (entry == peerId) { + LogMessage(LOG_HOST, "Removing %s peer ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + peerList.erase(it); + break; + } + } + } + + loadList(); //setFocusWidget(&m_listBox); redraw(); @@ -283,15 +300,9 @@ private: return; } - peerList.clear(); - for (uint32_t i = 0U; i < m_listBox.getCount(); i++) { - auto item = m_listBox.getItem(i + 1U); - if (item.getText() != "") { - uint32_t peerId = ::atoi(item.getText().c_str()); - LogMessage(LOG_HOST, "%s peer ID %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), - m_rule.name().c_str(), m_rule.source().tgId()); - peerList.push_back(peerId); - } + for (auto entry : peerList) { + LogMessage(LOG_HOST, "%s peer ID %u for TG %s (%u)", m_title.c_str(), entry, + m_rule.name().c_str(), m_rule.source().tgId()); } CloseWndBase::onClose(e); diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index e97dcb06..079061c8 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -23,6 +23,13 @@ #include using namespace finalcut; +struct PrivateFListViewScrollToY { typedef void(FListView::*type)(int); }; +template class HackTheGibson; +struct PrivateFListViewIteratorFirst { typedef FListViewIterator FListView::*type; }; +template class HackTheGibson; +struct PrivateFListViewVBarPtr { typedef FScrollbarPtr FListView::*type; }; +template class HackTheGibson; + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- @@ -94,6 +101,15 @@ public: auto entry = g_tidLookups->groupVoice()[0U]; m_selected = entry; + // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + int firstScrollLinePos = 0; + if (m_listView.getCount() > 0) { + firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); + } + m_listView.clear(); for (auto entry : g_tidLookups->groupVoice()) { // pad TGs properly @@ -101,8 +117,8 @@ public: oss << std::setw(5) << std::setfill('0') << entry.source().tgId(); // build list view entry - const std::array columns = { - entry.name(), entry.nameAlias(), oss.str(), + const std::array columns = { + entry.name(), entry.nameAlias(), oss.str(), std::to_string(entry.source().tgSlot()), (entry.config().active()) ? "X" : "", (entry.config().affiliated()) ? "X" : "", std::to_string(entry.config().inclusionSize()), @@ -115,6 +131,17 @@ public: m_listView.insert(line); } + // bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + if ((size_t)firstScrollLinePos > m_listView.getCount()) + firstScrollLinePos = 0; + if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { + (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + } + // generate dialog title uint32_t len = g_tidLookups->groupVoice().size(); std::stringstream ss; @@ -133,6 +160,7 @@ private: FButton m_addTG{"&Add", this}; FButton m_editTG{"&Edit", this}; + FLabel m_fileName{"/path/to/file.yml", this}; FButton m_deleteTG{"&Delete", this}; /** @@ -169,6 +197,9 @@ private: m_editTG.setDisable(); m_editTG.addCallback("clicked", [&]() { editEntry(); }); + m_fileName.setGeometry(FPoint(27, int(getHeight() - 4)), FSize(42, 1)); + m_fileName.setText(g_iniFile); + m_deleteTG.setGeometry(FPoint(int(getWidth()) - 13, int(getHeight() - 4)), FSize(10, 1)); m_deleteTG.setDisable(); m_deleteTG.addCallback("clicked", [&]() { deleteEntry(); }); @@ -179,6 +210,7 @@ private: m_listView.addColumn("Name", 25); m_listView.addColumn("Alias", 20); m_listView.addColumn("TGID", 9); + m_listView.addColumn("Slot", 4); m_listView.addColumn("Active", 5); m_listView.addColumn("Affiliated", 5); m_listView.addColumn("Inclusions", 5); @@ -188,9 +220,9 @@ private: // set right alignment for TGID m_listView.setColumnAlignment(3, finalcut::Align::Right); - m_listView.setColumnAlignment(4, finalcut::Align::Center); + m_listView.setColumnAlignment(4, finalcut::Align::Right); m_listView.setColumnAlignment(5, finalcut::Align::Center); - m_listView.setColumnAlignment(6, finalcut::Align::Right); + m_listView.setColumnAlignment(6, finalcut::Align::Center); m_listView.setColumnAlignment(7, finalcut::Align::Right); m_listView.setColumnAlignment(8, finalcut::Align::Right); m_listView.setColumnAlignment(9, finalcut::Align::Right); @@ -201,7 +233,6 @@ private: m_listView.setColumnSortType(3, finalcut::SortType::Name); // sort by TGID - m_listView.setColumnSort(2, finalcut::SortOrder::Ascending); m_listView.setColumnSort(3, finalcut::SortOrder::Ascending); m_listView.addCallback("clicked", [&]() { editEntry(); }); @@ -215,8 +246,10 @@ private: auto entry = g_tidLookups->find(tgid); if (!entry.isInvalid()) { m_selected = entry; +/* if (m_selectedTgId != tgid) LogMessage(LOG_HOST, "Selected TG %s (%u) for editing", m_selected.name().c_str(), m_selected.source().tgId()); +*/ m_selectedTgId = tgid; m_editTG.setEnable(); @@ -285,6 +318,23 @@ private: LogMessage(LOG_HOST, "Deleting TG %s (%u)", m_selected.name().c_str(), m_selected.source().tgId()); g_tidLookups->eraseEntry(m_selected.source().tgId(), m_selected.source().tgSlot()); + + // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + int firstScrollLinePos = 0; + if (m_listView.getCount() > 0) { + firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); + } + if ((size_t)firstScrollLinePos > m_listView.getCount()) + firstScrollLinePos = 0; + if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { + --firstScrollLinePos; + (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + } + loadListView(); } @@ -319,14 +369,14 @@ private: // Use box-drawing characters to draw a border constexpr std::array box_char {{ - static_cast(0x2554), - static_cast(0x2550), - static_cast(0x2557), - static_cast(0x2551), - static_cast(0x2551), - static_cast(0x255A), - static_cast(0x2550), - static_cast(0x255D) + static_cast(0x2554), // ╔ + static_cast(0x2550), // ═ + static_cast(0x2557), // ╗ + static_cast(0x2551), // ║ + static_cast(0x2551), // ║ + static_cast(0x255A), // ╚ + static_cast(0x2550), // ═ + static_cast(0x255D) // ╝ }}; drawGenericBox(this, box, box_char);