diff --git a/configs/adj_site_map.example.yml b/configs/adj_site_map.example.yml new file mode 100644 index 000000000..cc1d0cf52 --- /dev/null +++ b/configs/adj_site_map.example.yml @@ -0,0 +1,14 @@ +# +# Digital Voice Modem - Adj. Site Map +# + +# +# Peer List +# +peers: + # Peer ID. + - peerId: 1234567 + # Flag indicating whether this talkgroup is active or not. + active: true + # List of peer IDs that are neighbors for this peer. + neighbors: [] diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 36fc829ab..436b5e1bf 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -125,6 +125,11 @@ system: # Textual Name identity: BRIDGE + # Network ID (WACN). + netId: BB800 + # System ID. + sysId: 001 + # PCM audio gain for received (from digital network) audio frames. # - This is used to apply gain to the decoded IMBE/AMBE audio, post-decoding. rxAudioGain: 1.0 @@ -145,7 +150,7 @@ system: # - (Not used when utilizing external USB vocoder!) vocoderEncoderAudioGain: 3.0 - # Audio transmit mode (1 - DMR, 2 - P25). + # Audio transmit mode (1 - DMR, 2 - P25, 3 - Analog). txMode: 1 # Relative sample level for VOX to activate. @@ -179,3 +184,9 @@ system: trace: false # Flag indicating whether or not debug logging is enabled. debug: false + + # RTS PTT Configuration + # Flag indicating whether RTS PTT control is enabled. + rtsPttEnable: false + # Serial port device for RTS PTT control (e.g., /dev/ttyUSB0). + rtsPttPort: "/dev/ttyUSB0" diff --git a/configs/config.example.yml b/configs/config.example.yml index f7d045c6c..14359fc37 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -155,6 +155,10 @@ protocols: ignoreAffiliationCheck: false # Flag indicating the host should send a network grant demand for conventional traffic. convNetGrantDemand: false + # Flag indicating the fallback legacy group registration for radios that do not support group affiliation. + # (Useful for alerting the FNE to affiliations to TGIDs for radios that do not properly support group + # affiliation.) + legacyGroupReg: false # Flag indicating whether or not received RF embedded LC data only should be transmitted. embeddedLCOnly: false # Flag indicating whether talker alias data should be dumped to the log. @@ -240,12 +244,16 @@ protocols: # (This applies only in conventional operations where channel granting is utilized and RF-only talkgroup # steering is required.) disableNetworkGrant: false + # Flag indicating whether or not TGID 0 (P25 blackhole talkgroup) will be allowed. + # (Normally this should *never* be enabled, it is here to support poorly behaved systems that transmit + # TGID 0 for some reason.) + forceAllowTG0: false # Flag indicating whether or not a TGID will be tested for affiliations before being granted. ignoreAffiliationCheck: false # Flag indicating that the host will attempt to automatically inhibit unauthorized RIDs (those not in the # RID ACL list). inhibitUnauthorized: false - # Flag indicating the fallback legacy group grant for radios that do not support group affilition to + # Flag indicating the fallback legacy group grant for radios that do not support group affiliation to # have group grants transmitted. (Useful for alerting the FNE to affiliations to TGIDs for radios that # do not properly support group affiliation.) legacyGroupGrnt: true @@ -267,6 +275,8 @@ protocols: dumpDataPacket: false # Flag indicating whether or not this host will repeat P25 data traffic. repeatDataPacket: true + # Flag indicating whether or not the host will ignore CRC errors in P25 PDU data packets. + ignoreDataCRC: false # Flag indicating whether verbose dumping of P25 TSBK data is enabled. dumpTsbkData: false # Amount of time to hang after a voice call. @@ -278,7 +288,7 @@ protocols: # Flag indicating that unit-to-unit availiability checks should be performed for a private call. unitToUnitAvailCheck: false # Flag indicating explicit source ID support is enabled. - allowExplicitSourceId: true + allowExplicitSourceId: false # Flag indicating whether or not the host will respond to SNDCP data requests. sndcpSupport: false # BER/Error threshold for silencing voice packets. (0 or 1233 disables) @@ -319,6 +329,10 @@ protocols: # Flag indicating whether or not a TGID will be tested for affiliations before being granted. ignoreAffiliationCheck: false + # Flag indicating the fallback legacy group registration for radios that do not support group affiliation. + # (Useful for alerting the FNE to affiliations to TGIDs for radios that do not properly support group + # affiliation.) + legacyGroupReg: false # Flag indicating the host should verify group affiliation. verifyAff: false # Flag indicating the host should verify unit registration. @@ -470,6 +484,10 @@ system: pSuperGroup: FFFE # Announcment Talkgroup Group. announcementGroup: FFFE + # Default Talkgroup for idle network traffic. + # NOTE: TGID 0 is used here to indicate "promiscuous" mode, where first-come-first-serve traffic from the network + # is allowed. + defaultNetIdleTalkgroup: 0 # DMR network ID. dmrNetId: 1 @@ -532,8 +550,12 @@ system: # Amount of time to wait before starting DMR transmissions after a signal is received. dmrRxDelay: 7 # Amount of packet correlations that should occur before P25 data is returned from the modem to the host. - # (Note: Changing this value will impact P25 protocol stability, and should not be altered.) - p25CorrCount: 8 + # (Note: Changing this value will impact P25 protocol stability, and should not be altered. This is set to + # a default value of 2 for fast synchronization of a P25 signal, if the air modem is having difficulty + # synchronizing to a P25 signal, this value can be increased to 3 or 4, or beyond. It should be noted that + # increasing this value will increase the time it takes to synchronize to a P25 signal, and may cause + # issues with P25 voice calls (especially encrypted voice calls).) + p25CorrCount: 2 # Size (in bytes) of the DMR transmit FIFO buffer. It is not recommended to adjust this unless absolutely # necessary, excessive sizes will cause delays in transmitted frames due to excessive buffering. # Calculation Formula: (DMR Frame Size (33 bytes) * x Frames) + Pad Bytes = FIFO Buffer Size Bytes @@ -626,6 +648,9 @@ 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 whether or not the V.24 modem is operating in full-duplex mode. + # (This enables DVM to repeat incoming V.24 frames back out after processing.) + fullDuplex: false # Flag indicating when operating in V.24 UDP mode, the FSC protocol should be used to negotiate connection. fsc: false # Sets the heartbeat interval for the FSC connection. diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 036a55442..3ef1f78f8 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -48,6 +48,10 @@ master: # Flag indicating whether or not verbose debug logging is enabled. debug: false + # Flag indicating whether or not denied traffic will be logged. + # (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.) + logDenials: false + # Maximum number of concurrent packet processing workers. workers: 16 @@ -70,6 +74,8 @@ master: allowP25Traffic: true # Flag indicating whether or not NXDN traffic will be passed. allowNXDNTraffic: true + # Flag indicating whether or not analog traffic will be passed. + allowAnalogTraffic: false # Flag indicating whether packet data will be passed. disablePacketData: false @@ -87,6 +93,8 @@ master: # Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating. restrictGrantToAffiliatedOnly: false + # Flag indicating whether or not a private call will only be routed to the network peers the RID registers with. + restrictPrivateCallToRegOnly: false # Flag indicating whether or not a adjacent site broadcasts will pass to any peers. disallowAdjStsBcast: false @@ -103,9 +111,14 @@ master: # 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). + # Flag indicating whether or not the FNE will mask all outbound traffic to use the FNE's own peer ID. + # (This is useful for FNEs that are public facing, and the originating traffic peer ID should be masked.) + maskOutboundPeerID: false + # Flag indicating whether or not the FNE will mask only non-Peer-Link outbound traffic to use the FNE's own peer ID. + # (This is useful for networked FNEs that are have a mix of connections, and the originating traffic peer ID to non-Peer-Link FNEs should be masked.) + maskOutboundPeerIDForNonPeerLink: false + + # Flag indicating that P25 terminators will be filtered by destination ID (i.e. valid RID or valid TGID). filterTerminators: true # Flag indicating the FNE will drop all inbound Unit-to-Unit calls. @@ -150,6 +163,15 @@ master: # Amount of time between updates of talkgroup rules file. (minutes) time: 30 + # + # Adj. Site Map Configuration + # + adj_site_map: + # Full path to the Adj. Site Map file. + file: adj_site_map.yml + # Amount of time between updates of Adj. Site Map file. (minutes) + time: 30 + # # External Peers # diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index d2997368b..39954a1ff 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -60,21 +60,62 @@ network: sourceTGID: 1 # Source Slot for received/transmitted audio frames. sourceSlot: 1 + + # Source Traffic Encryption + srcTek: + # Flag indicating whether or not traffic encryption is enabled. + enable: false + # Traffic Encryption Key Algorithm + # aes - AES-256 Encryption + # arc4 - ARC4/ADP Encryption + tekAlgo: "aes" + # Traffic Encryption Key ID + tekKeyId: 1 + # Destination Talkgroup ID for transmitted/received audio frames. destinationTGID: 1 # Destination Slot for received/transmitted audio frames. destinationSlot: 1 + # Destination Traffic Encryption + dstTek: + # Flag indicating whether or not traffic encryption is enabled. + enable: false + # Traffic Encryption Key Algorithm + # aes - AES-256 Encryption + # arc4 - ARC4/ADP Encryption + tekAlgo: "aes" + # Traffic Encryption Key ID + tekKeyId: 1 + # Flag indicating whether or not the patch is two-way. twoWay: false + # Hostname/IP address of MMDVM gateway to connect to. + mmdvmGatewayAddress: 127.0.0.1 + # Port number to connect to. + mmdvmGatewayPort: 42020 + # Local port number for gateway to connect to. + localGatewayPort: 32010 + system: # Textual Name identity: PATCH + # Network ID (WACN). + netId: BB800 + # System ID. + sysId: 001 + # Digital mode (1 - DMR, 2 - P25). digiMode: 1 + # Flag indicating whether a network grant demand packet will be sent before audio. + grantDemand: false + + # Flag indicating whether or not the patch is from/to a MMDVM P25 reflector. + mmdvmP25Reflector: false + # Flag indicating whether or not trace logging is enabled. trace: false # Flag indicating whether or not debug logging is enabled. diff --git a/configs/peer_list.example.dat b/configs/peer_list.example.dat index bfed0ea97..60cf3299b 100644 --- a/configs/peer_list.example.dat +++ b/configs/peer_list.example.dat @@ -1,9 +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),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled)," -#1234,,0,,1, -#5678,MYSECUREPASSWORD,0,,0, -#9876,MYSECUREPASSWORD,1,,0, -#5432,MYSECUREPASSWORD,,Peer Alias 1,0, -#1012,MYSECUREPASSWORD,1,Peer Alias 2,1, +# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled)" +#1234,,0,,1,0, +#5678,MYSECUREPASSWORD,0,,0,0, +#9876,MYSECUREPASSWORD,1,,0,0, +#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0, +#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b110299d..d3e486a4b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,7 @@ if (ENABLE_SETUP_TUI) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util) else () if (COMPILE_WIN32) + target_sources(dvmhost PRIVATE ${dvmhost_RC}) target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) else () target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) @@ -60,7 +61,7 @@ set(CPACK_PACKAGE_VENDOR "DVMProject") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") -set(CPACK_DEBIAN_PACKAGE_VERSION "R04Hxx") +set(CPACK_DEBIAN_PACKAGE_VERSION "R04Jxx") set(CPACK_DEBIAN_PACKAGE_RELEASE "0") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") @@ -76,6 +77,9 @@ include(CPack) # include(src/fne/CMakeLists.txt) add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC}) +if (COMPILE_WIN32) + target_sources(dvmfne PRIVATE ${dvmfne_RC}) +endif (COMPILE_WIN32) target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne) @@ -128,6 +132,9 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) # include(src/remote/CMakeLists.txt) add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC}) +if (COMPILE_WIN32) + target_sources(dvmcmd PRIVATE ${dvmcmd_RC}) +endif (COMPILE_WIN32) target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) @@ -137,6 +144,7 @@ target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) include(src/bridge/CMakeLists.txt) add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC}) if (COMPILE_WIN32) + target_sources(dvmbridge PRIVATE ${bridge_RC}) target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) else () if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") @@ -153,6 +161,7 @@ target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} src src/brid include(src/patch/CMakeLists.txt) add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC}) if (COMPILE_WIN32) + target_sources(dvmpatch PRIVATE ${patch_RC}) target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) else () target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) diff --git a/src/CompilerOptions.cmake b/src/CompilerOptions.cmake index 6c6670718..0c8eb7c41 100644 --- a/src/CompilerOptions.cmake +++ b/src/CompilerOptions.cmake @@ -35,6 +35,7 @@ option(DEBUG_NXDN_SACCH "" off) option(DEBUG_NXDN_UDCH "" off) option(DEBUG_NXDN_LICH "" off) option(DEBUG_NXDN_CAC "" off) +option(DEBUG_NXDN_RTCH "" off) option(DEBUG_P25_PDU_DATA "" off) option(DEBUG_P25_HDU "" off) option(DEBUG_P25_LDU1 "" off) @@ -96,6 +97,10 @@ if (DEBUG_NXDN_CAC) message(CHECK_START "NXDN CAC Debug") add_definitions(-DDEBUG_NXDN_CAC) endif (DEBUG_NXDN_CAC) +if (DEBUG_NXDN_RTCH) + message(CHECK_START "NXDN RTCH Debug") + add_definitions(-DDEBUG_NXDN_RTCH) +endif (DEBUG_NXDN_RTCH) if (DEBUG_P25_PDU_DATA) message(CHECK_START "P25 PDU Data Debug") add_definitions(-DDEBUG_P25_PDU_DATA) diff --git a/src/bridge/CMakeLists.txt b/src/bridge/CMakeLists.txt index 647081a2a..43e372525 100644 --- a/src/bridge/CMakeLists.txt +++ b/src/bridge/CMakeLists.txt @@ -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 # * # */ file(GLOB bridge_SRC @@ -16,6 +16,14 @@ file(GLOB bridge_SRC "src/bridge/network/*.h" "src/bridge/network/*.cpp" + "src/bridge/win32/*.h" "src/bridge/*.h" "src/bridge/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB bridge_RC + "src/bridge/win32/*.rc" +) diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index a6607420f..75b04c3fc 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -9,6 +9,9 @@ * */ #include "Defines.h" +#include "common/analog/AnalogDefines.h" +#include "common/analog/AnalogAudio.h" +#include "common/analog/data/NetData.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/data/EMB.h" #include "common/dmr/data/NetData.h" @@ -30,8 +33,9 @@ #include "bridge/ActivityLog.h" #include "HostBridge.h" #include "BridgeMain.h" -#include "SampleTimeConversion.h" +using namespace analog; +using namespace analog::defines; using namespace network; using namespace network::frame; using namespace network::udp; @@ -59,20 +63,6 @@ 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 - -const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; - #define TEK_AES "aes" #define TEK_ARC4 "arc4" @@ -102,27 +92,34 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 std::lock_guard lock(HostBridge::m_audioMutex); int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; const uint8_t* pcm = (const uint8_t*)input; for (uint32_t pcmIdx = 0; pcmIdx < pcmBytes; pcmIdx += 2) { samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); smpIdx++; } - bridge->m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH); + bridge->m_inputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); } // playback output audio - if (bridge->m_outputAudio.dataSize() >= MBE_SAMPLES_LENGTH) { - short samples[MBE_SAMPLES_LENGTH]; - bridge->m_outputAudio.get(samples, MBE_SAMPLES_LENGTH); + if (bridge->m_outputAudio.dataSize() >= AUDIO_SAMPLES_LENGTH) { + short samples[AUDIO_SAMPLES_LENGTH]; + bridge->m_outputAudio.get(samples, AUDIO_SAMPLES_LENGTH); uint8_t* pcm = (uint8_t*)output; int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } + + // Assert RTS PTT when audio is being sent to output + bridge->assertRtsPtt(); + } + else { + // Deassert RTS PTT when no audio is being sent to output + bridge->deassertRtsPtt(); } } @@ -139,7 +136,7 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit ::LogMessage(LOG_HOST, "Local Traffic, MDC Detect, unitId = $%04X", unitID); // HACK: nasty bullshit to convert MDC unitID to decimal - char* pCharRes = new (char); + char* pCharRes = new char[16]; // enough space for "0xFFFFFFFF" ::sprintf(pCharRes, "0x%X", unitID); uint32_t res = 0U; @@ -151,133 +148,12 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit res = (uint32_t)std::stoi(pCharRes, 0, 16); } + delete[] pCharRes; bridge->m_srcIdOverride = res; ::LogMessage(LOG_HOST, "Local Traffic, MDC Detect, converted srcId = %u", bridge->m_srcIdOverride); } } -/* */ - -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 encodeALaw(short pcm) -{ - short mask; - uint8_t aval; - - pcm = pcm >> 3; - - if (pcm >= 0) { - mask = 0xD5U; // sign (7th) bit = 1 - } else { - mask = 0x55U; // sign bit = 0 - pcm = -pcm - 1; - } - - // convert the scaled magnitude to segment number - short seg = search(pcm, seg_aend, 8); - - /* - ** combine the sign, segment, quantization bits - */ - if (seg >= 8) // out of range, return maximum value - return (uint8_t)(0x7F ^ mask); - else { - aval = (uint8_t) seg << SEG_SHIFT; - if (seg < 2) - aval |= (pcm >> 1) & QUANT_MASK; - else - aval |= (pcm >> seg) & QUANT_MASK; - - return (aval ^ mask); - } -} - -/* Helper to convert G.711 aLaw into PCM. */ - -short decodeALaw(uint8_t alaw) -{ - alaw ^= 0x55U; - - short t = (alaw & QUANT_MASK) << 4; - short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT; - switch (seg) { - case 0: - t += 8; - break; - case 1: - t += 0x108U; - break; - default: - t += 0x108U; - t <<= seg - 1; - } - - return ((alaw & SIGN_BIT) ? t : -t); -} - -/* Helper to convert PCM into G.711 MuLaw. */ - -uint8_t encodeMuLaw(short pcm) -{ - short mask; - - // get the sign and the magnitude of the value - pcm = pcm >> 2; - if (pcm < 0) { - pcm = -pcm; - mask = 0x7FU; - } else { - mask = 0xFFU; - } - - // clip the magnitude - if (pcm > CLIP) - pcm = CLIP; - pcm += (BIAS >> 2); - - // convert the scaled magnitude to segment number - short seg = search(pcm, seg_uend, 8); - - /* - ** combine the sign, segment, quantization bits; - ** and complement the code word - */ - if (seg >= 8) // out of range, return maximum value. - return (uint8_t)(0x7F ^ mask); - else { - uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF); - return (ulaw ^ mask); - } -} - -/* Helper to convert G.711 MuLaw into PCM. */ - -short decodeMuLaw(uint8_t ulaw) -{ - // complement to obtain normal u-law value - ulaw = ~ulaw; - - /* - ** extract and bias the quantization bits; then - ** shift up by the segment number and subtract out the bias - */ - short t = ((ulaw & QUANT_MASK) << 3) + BIAS; - t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT; - - return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); -} - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -303,11 +179,11 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpJitter(200U), m_udpSilenceDuringHang(true), m_lastUdpFrameTime(0U), - m_tekAlgoId(p25::defines::ALGO_UNENCRYPT), + m_tekAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekKeyId(0U), m_requestedTek(false), m_p25Crypto(nullptr), - m_srcId(p25::defines::WUID_FNE), + m_srcId(P25DEF::WUID_FNE), m_srcIdOverride(0U), m_overrideSrcIdFromMDC(false), m_overrideSrcIdFromUDP(false), @@ -338,8 +214,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_maCaptureDevices(nullptr), m_maDeviceConfig(), m_maDevice(), - m_inputAudio(MBE_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Input Audio Buffer"), - m_outputAudio(MBE_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Output Audio Buffer"), + m_inputAudio(AUDIO_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Input Audio Buffer"), + m_outputAudio(AUDIO_SAMPLES_LENGTH * NUMBER_OF_BUFFERS, "Output Audio Buffer"), m_udpPackets(), m_decoder(nullptr), m_encoder(nullptr), @@ -356,13 +232,16 @@ HostBridge::HostBridge(const std::string& confFile) : m_netLDU2(nullptr), m_p25SeqNo(0U), m_p25N(0U), + m_netId(P25DEF::WACN_STD_DEFAULT), + m_sysId(P25DEF::SID_STD_DEFAULT), + m_analogN(0U), m_audioDetect(false), m_trafficFromUDP(false), m_udpSrcId(0U), m_udpDstId(0U), m_callInProgress(false), m_ignoreCall(false), - m_callAlgoId(p25::defines::ALGO_UNENCRYPT), + m_callAlgoId(P25DEF::ALGO_UNENCRYPT), m_rxStartTime(0U), m_rxStreamId(0U), m_txStreamId(0U), @@ -371,6 +250,10 @@ HostBridge::HostBridge(const std::string& confFile) : m_running(false), m_trace(false), m_debug(false), + m_rtsPttEnable(false), + m_rtsPttPort(), + m_rtsPttController(nullptr), + m_rtsPttActive(false), m_rtpSeqNo(0U), m_rtpTimestamp(INVALID_TS), m_usrpSeqNo(0U) @@ -411,6 +294,12 @@ HostBridge::HostBridge(const std::string& confFile) : HostBridge::~HostBridge() { + if (m_rtsPttController != nullptr) { + m_rtsPttController->close(); + delete m_rtsPttController; + m_rtsPttController = nullptr; + } + delete[] m_ambeBuffer; delete[] m_netLDU1; delete[] m_netLDU2; @@ -518,6 +407,11 @@ int HostBridge::run() if (!ret) return EXIT_FAILURE; + // initialize RTS PTT control + ret = initializeRtsPtt(); + if (!ret) + return EXIT_FAILURE; + ma_result result; if (m_localAudio) { // initialize audio devices @@ -552,7 +446,7 @@ int HostBridge::run() m_maDeviceConfig.playback.channels = 1; m_maDeviceConfig.playback.shareMode = ma_share_mode_shared; - m_maDeviceConfig.periodSizeInFrames = MBE_SAMPLES_LENGTH; + m_maDeviceConfig.periodSizeInFrames = AUDIO_SAMPLES_LENGTH; m_maDeviceConfig.dataCallback = audioCallback; m_maDeviceConfig.pUserData = this; @@ -627,6 +521,10 @@ int HostBridge::run() if (m_txMode == TX_MODE_P25) { m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId, 0U); }); } + + if (m_txMode == TX_MODE_ANALOG) { + m_network->setAnalogICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId, 0U); }); + } } /* @@ -823,7 +721,7 @@ int HostBridge::ambeDecode(const uint8_t* codeword, uint32_t codewordLength, sho assert(codeword != nullptr); assert(samples != nullptr); - //samples = new short[MBE_SAMPLES_LENGTH]; + //samples = new short[AUDIO_SAMPLES_LENGTH]; UInt8Array cw = std::make_unique(codewordLength); ::memcpy(cw.get(), codeword, codewordLength); @@ -855,17 +753,17 @@ int HostBridge::ambeDecode(const uint8_t* codeword, uint32_t codewordLength, sho std::unique_ptr codewordBits = std::make_unique(m_frameLengthInBits * 2); unpackBytesToBits(codewordBits.get(), cw.get(), m_frameLengthInBytes, m_frameLengthInBits); - std::unique_ptr n0 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - ambe_voice_dec(n0.get(), MBE_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 0, m_decoderState); + std::unique_ptr n0 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + ambe_voice_dec(n0.get(), AUDIO_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 0, m_decoderState); - std::unique_ptr n1 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - ambe_voice_dec(n1.get(), MBE_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 1, m_decoderState); + std::unique_ptr n1 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + ambe_voice_dec(n1.get(), AUDIO_SAMPLES_LENGTH / 2, codewordBits.get(), NO_BIT_STEAL, m_dcMode, 1, m_decoderState); // combine sample segments into contiguous samples - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) samples[i] = n0[i]; - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) - samples[i + (MBE_SAMPLES_LENGTH / 2)] = n1[i]; + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) + samples[i + (AUDIO_SAMPLES_LENGTH / 2)] = n1[i]; return 0; // this always just returns no errors? } @@ -929,29 +827,29 @@ void HostBridge::ambeEncode(const short* samples, uint32_t sampleLength, uint8_t //codeword = new byte[this.frameLengthInBytes]; - if (sampleLength > MBE_SAMPLES_LENGTH) { - ::LogError(LOG_HOST, "Samples length is > %u", MBE_SAMPLES_LENGTH); + if (sampleLength > AUDIO_SAMPLES_LENGTH) { + ::LogError(LOG_HOST, "Samples length is > %u", AUDIO_SAMPLES_LENGTH); return; } - if (sampleLength < MBE_SAMPLES_LENGTH) { - ::LogError(LOG_HOST, "Samples length is < %u", MBE_SAMPLES_LENGTH); + if (sampleLength < AUDIO_SAMPLES_LENGTH) { + ::LogError(LOG_HOST, "Samples length is < %u", AUDIO_SAMPLES_LENGTH); return; } std::unique_ptr codewordBits = std::make_unique(m_frameLengthInBits * 2); // split samples into 2 segments - std::unique_ptr n0 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) + std::unique_ptr n0 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) n0[i] = samples[i]; - ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n0.get(), MBE_SAMPLES_LENGTH / 2, m_ecMode, 0, 8192, m_encoderState); + ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n0.get(), AUDIO_SAMPLES_LENGTH / 2, m_ecMode, 0, 8192, m_encoderState); - std::unique_ptr n1 = std::make_unique(MBE_SAMPLES_LENGTH / 2); - for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++) - n1[i] = samples[i + (MBE_SAMPLES_LENGTH / 2)]; + std::unique_ptr n1 = std::make_unique(AUDIO_SAMPLES_LENGTH / 2); + for (int i = 0; i < AUDIO_SAMPLES_LENGTH / 2; i++) + n1[i] = samples[i + (AUDIO_SAMPLES_LENGTH / 2)]; - ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n1.get(), MBE_SAMPLES_LENGTH / 2, m_ecMode, 1, 8192, m_encoderState); + ambe_voice_enc(codewordBits.get(), NO_BIT_STEAL, n1.get(), AUDIO_SAMPLES_LENGTH / 2, m_ecMode, 1, 8192, m_encoderState); // is this to be a DMR codeword? if (m_txMode == TX_MODE_DMR) { @@ -977,6 +875,24 @@ bool HostBridge::readParams() m_identity = systemConf["identity"].as(); + m_netId = (uint32_t)::strtoul(systemConf["netId"].as("BB800").c_str(), NULL, 16); + m_netId = p25::P25Utils::netId(m_netId); + if (m_netId == 0xBEE00) { + ::fatal("error 4\n"); + } + + m_sysId = (uint32_t)::strtoul(systemConf["sysId"].as("001").c_str(), NULL, 16); + m_sysId = p25::P25Utils::sysId(m_sysId); + + /* + ** Site Data + */ + int8_t lto = (int8_t)systemConf["localTimeOffset"].as(0); + p25::SiteData siteData = p25::SiteData(m_netId, m_sysId, 1U, 1U, 0U, 0U, 1U, P25DEF::ServiceClass::VOICE, lto); + siteData.setNetActive(true); + + p25::lc::LC::setSiteData(siteData); + m_rxAudioGain = systemConf["rxAudioGain"].as(1.0f); m_vocoderDecoderAudioGain = systemConf["vocoderDecoderAudioGain"].as(3.0f); m_vocoderDecoderAutoGain = systemConf["vocoderDecoderAutoGain"].as(false); @@ -986,8 +902,8 @@ bool HostBridge::readParams() m_txMode = (uint8_t)systemConf["txMode"].as(1U); if (m_txMode < TX_MODE_DMR) m_txMode = TX_MODE_DMR; - if (m_txMode > TX_MODE_P25) - m_txMode = TX_MODE_P25; + if (m_txMode > TX_MODE_ANALOG) + m_txMode = TX_MODE_ANALOG; m_voxSampleLevel = systemConf["voxSampleLevel"].as(30.0f); m_dropTimeMS = (uint16_t)systemConf["dropTimeMs"].as(180U); @@ -1007,6 +923,8 @@ bool HostBridge::readParams() } } break; + case TX_MODE_ANALOG: + break; } m_localDropTime = Timer(1000U, 0U, m_dropTimeMS); @@ -1031,13 +949,25 @@ bool HostBridge::readParams() m_trace = systemConf["trace"].as(false); m_debug = systemConf["debug"].as(false); + // RTS PTT Configuration + m_rtsPttEnable = systemConf["rtsPttEnable"].as(false); + m_rtsPttPort = systemConf["rtsPttPort"].as("/dev/ttyUSB0"); + + std::string txModeStr = "DMR"; + if (m_txMode == TX_MODE_P25) + txModeStr = "P25"; + if (m_txMode == TX_MODE_ANALOG) + txModeStr = "Analog"; + LogInfo("General Parameters"); + LogInfo(" System Id: $%03X", m_sysId); + LogInfo(" P25 Network Id: $%05X", m_netId); LogInfo(" Rx Audio Gain: %.1f", m_rxAudioGain); LogInfo(" Vocoder Decoder Audio Gain: %.1f", m_vocoderDecoderAudioGain); LogInfo(" Vocoder Decoder Auto Gain: %s", m_vocoderDecoderAutoGain ? "yes" : "no"); LogInfo(" Tx Audio Gain: %.1f", m_txAudioGain); LogInfo(" Vocoder Encoder Audio Gain: %.1f", m_vocoderEncoderAudioGain); - LogInfo(" Transmit Mode: %s", m_txMode == TX_MODE_DMR ? "DMR" : "P25"); + LogInfo(" Transmit Mode: %s", txModeStr.c_str()); LogInfo(" VOX Sample Level: %.1f", m_voxSampleLevel); LogInfo(" Drop Time: %ums", m_dropTimeMS); LogInfo(" Detect Analog MDC1200: %s", m_detectAnalogMDC1200 ? "yes" : "no"); @@ -1048,6 +978,10 @@ bool HostBridge::readParams() LogInfo(" Grant Demands: %s", m_grantDemand ? "yes" : "no"); LogInfo(" Local Audio: %s", m_localAudio ? "yes" : "no"); LogInfo(" UDP Audio: %s", m_udpAudio ? "yes" : "no"); + LogInfo(" RTS PTT Enable: %s", m_rtsPttEnable ? "yes" : "no"); + if (m_rtsPttEnable) { + LogInfo(" RTS PTT Port: %s", m_rtsPttPort.c_str()); + } if (m_debug) { LogInfo(" Debug: yes"); @@ -1107,24 +1041,36 @@ bool HostBridge::createNetwork() m_tekKeyId = (uint32_t)::strtoul(tekConf["tekKeyId"].as("0").c_str(), NULL, 16); if (tekEnable && m_tekKeyId > 0U) { if (tekAlgo == TEK_AES) - m_tekAlgoId = p25::defines::ALGO_AES_256; + m_tekAlgoId = P25DEF::ALGO_AES_256; else if (tekAlgo == TEK_ARC4) - m_tekAlgoId = p25::defines::ALGO_ARC4; + m_tekAlgoId = P25DEF::ALGO_ARC4; else { ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); - m_tekAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekKeyId = 0U; } } + if (!tekEnable) + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; + if (m_tekAlgoId == P25DEF::ALGO_UNENCRYPT) + m_tekKeyId = 0U; + // ensure encryption is currently disabled for DMR (its not supported) - if (m_txMode == TX_MODE_DMR && m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U) { + if (m_txMode == TX_MODE_DMR && m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U) { ::LogError(LOG_HOST, "Encryption is not supported for DMR. Disabling."); - m_tekAlgoId = p25::defines::ALGO_UNENCRYPT; + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; + m_tekKeyId = 0U; + } + + // ensure encryption is currently disabled for analog (its not supported) + if (m_txMode == TX_MODE_ANALOG && m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U) { + ::LogError(LOG_HOST, "Encryption is not supported for Analog. Disabling."); + m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekKeyId = 0U; } - m_srcId = (uint32_t)networkConf["sourceId"].as(p25::defines::WUID_FNE); + m_srcId = (uint32_t)networkConf["sourceId"].as(P25DEF::WUID_FNE); m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as(false); m_overrideSrcIdFromUDP = networkConf["overrideSourceIdFromUDP"].as(false); m_resetCallForSourceIdChange = networkConf["resetCallForSourceIdChange"].as(false); @@ -1154,6 +1100,7 @@ bool HostBridge::createNetwork() } break; case TX_MODE_P25: + case TX_MODE_ANALOG: { if (m_dstId > 65535) { ::LogError(LOG_HOST, "Bridge destination ID cannot be greater than 65535."); @@ -1253,7 +1200,7 @@ bool HostBridge::createNetwork() LogInfo(" Debug: yes"); } - bool dmr = false, p25 = false; + bool dmr = false, p25 = false, analog = false; switch (m_txMode) { case TX_MODE_DMR: dmr = true; @@ -1261,10 +1208,13 @@ bool HostBridge::createNetwork() case TX_MODE_P25: p25 = true; break; + case TX_MODE_ANALOG: + analog = true; + break; } // initialize networking - m_network = new PeerNetwork(address, port, local, id, password, true, debug, dmr, p25, false, true, true, true, allowDiagnosticTransfer, true, false); + m_network = new PeerNetwork(address, port, local, id, password, true, debug, dmr, p25, false, analog, true, true, true, allowDiagnosticTransfer, true, false); m_network->setMetadata(m_identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); m_network->setConventional(true); @@ -1317,7 +1267,7 @@ void HostBridge::processUDPAudio() if (length > 0) { if (m_trace) - Utils::dump(1U, "HostBridge()::processUDPAudio() Audio Network Packet", buffer, length); + Utils::dump(1U, "HostBridge()::processUDPAudio(), Audio Network Packet", buffer, length); uint32_t pcmLength = 0; if (m_udpNoIncludeLength) { @@ -1327,7 +1277,7 @@ void HostBridge::processUDPAudio() } if (m_udpRTPFrames || m_udpUsrp) - pcmLength = MBE_SAMPLES_LENGTH * 2U; + pcmLength = AUDIO_SAMPLES_LENGTH * 2U; DECLARE_UINT8_ARRAY(pcm, pcmLength); @@ -1341,7 +1291,7 @@ void HostBridge::processUDPAudio() return; } - ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, MBE_SAMPLES_LENGTH * 2U); + ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, AUDIO_SAMPLES_LENGTH * 2U); } else { if (m_udpNoIncludeLength) { @@ -1362,7 +1312,7 @@ void HostBridge::processUDPAudio() delete[] usrpHeader; } - // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); + // Utils::dump(1U, "HostBridge::processUDPAudio(), PCM RECV BYTE BUFFER", pcm, pcmLength); NetPacketRequest* req = new NetPacketRequest(); req->pcm = new uint8_t[pcmLength]; @@ -1627,7 +1577,7 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst for (uint32_t i = 0; i < RAW_AMBE_LENGTH_BYTES; i++) ambePartial[i] = ambe[i + (n * 9)]; - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; int errs = 0; #if defined(_WIN32) if (m_useExternalVocoder) { @@ -1644,69 +1594,57 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst LogMessage(LOG_HOST, DMR_DT_VOICE ", Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", dmrN, n, srcId, dstId, errs); // post-process: apply gain to decoded audio frames - if (m_rxAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_rxAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); if (m_localAudio) { - m_outputAudio.addData(samples, MBE_SAMPLES_LENGTH); + m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); + // Assert RTS PTT when audio is being sent to output + assertRtsPtt(); } if (m_udpAudio) { int pcmIdx = 0; - uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; if (m_udpUseULaw) { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[smpIdx] = encodeMuLaw(samples[smpIdx]); + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); } if (m_trace) - Utils::dump(1U, "HostBridge()::decodeDMRAudioFrame() Encoded uLaw Audio", pcm, MBE_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::decodeDMRAudioFrame(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_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; + uint32_t length = (AUDIO_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; if (!m_udpUsrp) { if (!m_udpMetadata) { - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) if (m_udpUseULaw) { - length = (MBE_SAMPLES_LENGTH)+4U; + length = (AUDIO_SAMPLES_LENGTH)+4U; if (m_udpNoIncludeLength) { - length = MBE_SAMPLES_LENGTH; - ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + length = AUDIO_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, AUDIO_SAMPLES_LENGTH); } else { - SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); + SET_UINT32(AUDIO_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH); } // are we sending RTP audio frames? if (m_udpRTPFrames) { - uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); + uint8_t* rtpFrame = generateRTPHeaders(AUDIO_SAMPLES_LENGTH, m_rtpSeqNo); if (rtpFrame != nullptr) { length += RTP_HEADER_LENGTH_BYTES; uint8_t* newAudioData = new uint8_t[length]; ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); - ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, AUDIO_SAMPLES_LENGTH); delete[] audioData; audioData = newAudioData; @@ -1716,26 +1654,26 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst } } else { - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); } } else { - length = (MBE_SAMPLES_LENGTH * 2U) + 12U; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + length = (AUDIO_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); // embed destination and source IDs - SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); - SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); + SET_UINT32(dstId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 4U)); + SET_UINT32(srcId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 8U)); } } else { uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; - length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + length = (AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) m_usrpSeqNo++; usrpHeader[15U] = 1; // set PTT state to true @@ -1743,7 +1681,7 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst ::memcpy(usrpHeader, "USRP", 4); ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload - ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, MBE_SAMPLES_LENGTH * 2U); + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, AUDIO_SAMPLES_LENGTH * 2U); } sockaddr_storage addr; @@ -1765,6 +1703,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ assert(pcm != nullptr); using namespace dmr; using namespace dmr::defines; + using namespace dmr::data; uint32_t srcId = m_srcId; if (m_srcIdOverride != 0 && (m_overrideSrcIdFromMDC)) @@ -1805,17 +1744,19 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ fullLC.encode(dmrLC, data, DataType::VOICE_LC_HEADER); // generate DMR network frame - data::NetData dmrData; + NetData dmrData; dmrData.setSlotNo(m_slot); dmrData.setDataType(DataType::VOICE_LC_HEADER); dmrData.setSrcId(srcId); dmrData.setDstId(dstId); dmrData.setFLCO(FLCO::GROUP); - if (m_grantDemand) { - dmrData.setControl(0x80U); // DMR remote grant demand flag - } else { - dmrData.setControl(0U); - } + + uint8_t controlByte = 0U; + if (m_grantDemand) + controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + controlByte |= network::NET_CTRL_SWITCH_OVER; + dmrData.setControl(controlByte); + dmrData.setN(m_dmrN); dmrData.setSeqNo(m_dmrSeqNo); dmrData.setBER(0U); @@ -1850,7 +1791,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); // generated embedded signalling - data::EMB emb = data::EMB(); + EMB emb = EMB(); emb.setColorCode(0U); emb.setLCSS(lcss); emb.encode(data); @@ -1859,7 +1800,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ LogMessage(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); // generate DMR network frame - data::NetData dmrData; + NetData dmrData; dmrData.setSlotNo(m_slot); dmrData.setDataType(dataType); dmrData.setSrcId(srcId); @@ -1881,35 +1822,21 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ } int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; - for (uint32_t pcmIdx = 0; pcmIdx < (MBE_SAMPLES_LENGTH * 2U); pcmIdx += 2) { + short samples[AUDIO_SAMPLES_LENGTH]; + for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) { samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); smpIdx++; } // pre-process: apply gain to PCM audio frames - if (m_txAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_txAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain); // encode PCM samples into AMBE codewords uint8_t ambe[RAW_AMBE_LENGTH_BYTES]; ::memset(ambe, 0x00U, RAW_AMBE_LENGTH_BYTES); #if defined(_WIN32) if (m_useExternalVocoder) { - ambeEncode(samples, MBE_SAMPLES_LENGTH, ambe); + ambeEncode(samples, AUDIO_SAMPLES_LENGTH, ambe); } else { #endif // defined(_WIN32) @@ -1918,7 +1845,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ } #endif // defined(_WIN32) - // Utils::dump(1U, "Encoded AMBE", ambe, RAW_AMBE_LENGTH_BYTES); + // Utils::dump(1U, "HostBridge::encodeDMRAudioFrame(), Encoded AMBE", ambe, RAW_AMBE_LENGTH_BYTES); ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), ambe, RAW_AMBE_LENGTH_BYTES); m_ambeCount++; @@ -1932,13 +1859,14 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) using namespace p25; using namespace p25::defines; using namespace p25::dfsi::defines; + using namespace p25::data; if (m_txMode != TX_MODE_P25) return; - bool grantDemand = (buffer[14U] & 0x80U) == 0x80U; - bool grantDenial = (buffer[14U] & 0x40U) == 0x40U; - bool unitToUnit = (buffer[14U] & 0x01U) == 0x01U; + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; + bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; // process network message header DUID::E duid = (DUID::E)buffer[22U]; @@ -1978,7 +1906,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) uint8_t lsd2 = buffer[21U]; lc::LC control; - data::LowSpeedData lsd; + LowSpeedData lsd; control.setLCO(lco); control.setSrcId(srcId); @@ -2290,14 +2218,14 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI break; } - // Utils::dump(1U, "IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + // Utils::dump(1U, "HostBridge::decodeP25AudioFrame(), IMBE", imbe, RAW_IMBE_LENGTH_BYTES); - if (m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { + if (m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { switch (m_tekAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25Crypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25Crypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -2306,7 +2234,7 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI } } - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; int errs = 0; #if defined(_WIN32) if (m_useExternalVocoder) { @@ -2323,70 +2251,58 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI LogDebug(LOG_HOST, "P25, LDU (Logical Link Data Unit), Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", p25N, n, srcId, dstId, errs); // post-process: apply gain to decoded audio frames - if (m_rxAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_rxAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); if (m_localAudio) { - m_outputAudio.addData(samples, MBE_SAMPLES_LENGTH); + m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); + // Assert RTS PTT when audio is being sent to output + assertRtsPtt(); } if (m_udpAudio) { int pcmIdx = 0; - uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; if (m_udpUseULaw) { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[smpIdx] = encodeMuLaw(samples[smpIdx]); + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); } if (m_trace) - Utils::dump(1U, "HostBridge()::decodeP25AudioFrame() Encoded uLaw Audio", pcm, MBE_SAMPLES_LENGTH); + Utils::dump(1U, "HostBridge()::decodeP25AudioFrame(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); } else { - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_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; + uint32_t length = (AUDIO_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; if (!m_udpUsrp) { if (!m_udpMetadata) { - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) if (m_udpUseULaw) { - length = (MBE_SAMPLES_LENGTH)+4U; + length = (AUDIO_SAMPLES_LENGTH)+4U; if (m_udpNoIncludeLength) { - length = MBE_SAMPLES_LENGTH; - ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + length = AUDIO_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, AUDIO_SAMPLES_LENGTH); } else { - SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); + SET_UINT32(AUDIO_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH); } // are we sending RTP audio frames? if (m_udpRTPFrames) { - uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); + uint8_t* rtpFrame = generateRTPHeaders(AUDIO_SAMPLES_LENGTH, m_rtpSeqNo); if (rtpFrame != nullptr) { length += RTP_HEADER_LENGTH_BYTES; uint8_t* newAudioData = new uint8_t[length]; ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); - ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, AUDIO_SAMPLES_LENGTH); delete[] audioData; audioData = newAudioData; @@ -2396,26 +2312,26 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI } } else { - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); } } else { - length = (MBE_SAMPLES_LENGTH * 2U) + 12U; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) - SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + length = (AUDIO_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); // embed destination and source IDs - SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); - SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); + SET_UINT32(dstId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 4U)); + SET_UINT32(srcId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 8U)); } } else { uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; - length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + length = (AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) m_usrpSeqNo++; usrpHeader[15U] = 1; // set PTT state to true @@ -2423,7 +2339,7 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI ::memcpy(usrpHeader, "USRP", 4); ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload - ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, MBE_SAMPLES_LENGTH * 2U); + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, AUDIO_SAMPLES_LENGTH * 2U); } sockaddr_storage addr; @@ -2445,6 +2361,7 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ assert(pcm != nullptr); using namespace p25; using namespace p25::defines; + using namespace p25::data; if (m_p25N > 17) m_p25N = 0; @@ -2454,35 +2371,21 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ ::memset(m_netLDU2, 0x00U, 9U * 25U); int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; - for (uint32_t pcmIdx = 0; pcmIdx < (MBE_SAMPLES_LENGTH * 2U); pcmIdx += 2) { + short samples[AUDIO_SAMPLES_LENGTH]; + for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) { samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); smpIdx++; } // pre-process: apply gain to PCM audio frames - if (m_txAudioGain != 1.0f) { - for (int n = 0; n < MBE_SAMPLES_LENGTH; n++) { - short sample = samples[n]; - float newSample = sample * m_txAudioGain; - sample = (short)newSample; - - // clip if necessary - if (newSample > 32767) - sample = 32767; - else if (newSample < -32767) - sample = -32767; - - samples[n] = sample; - } - } + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain); // encode PCM samples into IMBE codewords uint8_t imbe[RAW_IMBE_LENGTH_BYTES]; ::memset(imbe, 0x00U, RAW_IMBE_LENGTH_BYTES); #if defined(_WIN32) if (m_useExternalVocoder) { - ambeEncode(samples, MBE_SAMPLES_LENGTH, imbe); + ambeEncode(samples, AUDIO_SAMPLES_LENGTH, imbe); } else { #endif // defined(_WIN32) @@ -2491,9 +2394,9 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ } #endif // defined(_WIN32) - // Utils::dump(1U, "Encoded IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + // Utils::dump(1U, "HostBridge::encodeP25AudioFrame(), Encoded IMBE", imbe, RAW_IMBE_LENGTH_BYTES); - if (m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { + if (m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && m_tekKeyId > 0U && m_p25Crypto->getTEKLength() > 0U) { // generate initial MI for the HDU if (m_p25N == 0U && !m_p25Crypto->hasValidKeystream()) { if (!m_p25Crypto->hasValidMI()) { @@ -2504,10 +2407,10 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ // perform crypto switch (m_tekAlgoId) { - case p25::defines::ALGO_AES_256: + case P25DEF::ALGO_AES_256: m_p25Crypto->cryptAES_IMBE(imbe, (m_p25N < 9U) ? DUID::LDU1 : DUID::LDU2); break; - case p25::defines::ALGO_ARC4: + case P25DEF::ALGO_ARC4: m_p25Crypto->cryptARC4_IMBE(imbe, (m_p25N < 9U) ? DUID::LDU1 : DUID::LDU2); break; default: @@ -2614,28 +2517,295 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ m_p25Crypto->getMI(mi); lc.setMI(mi); - data::LowSpeedData lsd = data::LowSpeedData(); + LowSpeedData lsd = LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_SWITCH_OVER; // send P25 LDU1 if (m_p25N == 8U) { LogMessage(LOG_HOST, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); - m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::HDU_VALID); + m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::HDU_VALID, controlByte); m_txStreamId = m_network->getP25StreamId(); } // send P25 LDU2 if (m_p25N == 17U) { LogMessage(LOG_HOST, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", m_tekAlgoId, m_tekKeyId); - m_network->writeP25LDU2(lc, lsd, m_netLDU2); + m_network->writeP25LDU2(lc, lsd, m_netLDU2, controlByte); } m_p25SeqNo++; m_p25N++; } +/* Helper to process analog network traffic. */ + +void HostBridge::processAnalogNetwork(uint8_t* buffer, uint32_t length) +{ + assert(buffer != nullptr); + using namespace analog; + using namespace analog::defines; + + if (m_txMode != TX_MODE_ANALOG) + return; + + // process network message header + uint8_t seqNo = buffer[4U]; + + uint32_t srcId = GET_UINT24(buffer, 5U); + uint32_t dstId = GET_UINT24(buffer, 8U); + + bool individual = (buffer[15] & 0x40U) == 0x40U; + + AudioFrameType::E frameType = (AudioFrameType::E)(buffer[15U] & 0x0FU); + + data::NetData analogData; + analogData.setSeqNo(seqNo); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setFrameType(frameType); + + analogData.setAudio(buffer + 20U); + + uint8_t frame[AUDIO_SAMPLES_LENGTH_BYTES]; + analogData.getAudio(frame); + + if (m_debug) { + LogDebug(LOG_NET, "Analog, seqNo = %u, srcId = %u, dstId = %u, len = %u", seqNo, srcId, dstId, length); + } + + if (!individual) { + if (srcId == 0) + return; + + // ensure destination ID matches and slot matches + if (dstId != m_dstId) + return; + + // is this a new call stream? + if (m_network->getAnalogStreamId() != m_rxStreamId) { + m_callInProgress = true; + m_callAlgoId = 0U; + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_rxStartTime = now; + + LogMessage(LOG_HOST, "Analog, call start, srcId = %u, dstId = %u", srcId, dstId); + if (m_preambleLeaderTone) + generatePreambleTone(); + } + + if (frameType == AudioFrameType::TERMINATOR) { + m_callInProgress = false; + m_ignoreCall = false; + m_callAlgoId = 0U; + + if (m_rxStartTime > 0U) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t diff = now - m_rxStartTime; + + LogMessage(LOG_HOST, "Analog, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + } + + m_rxStartTime = 0U; + m_rxStreamId = 0U; + + m_rtpSeqNo = 0U; + m_rtpTimestamp = INVALID_TS; + return; + } + + if (m_ignoreCall && m_callAlgoId == 0U) + m_ignoreCall = false; + + if (m_ignoreCall) + return; + + if (frameType == AudioFrameType::VOICE_START || frameType == AudioFrameType::VOICE) { + LogMessage(LOG_NET, ANO_VOICE ", audio, srcId = %u, dstId = %u, seqNo = %u", srcId, dstId, analogData.getSeqNo()); + + short samples[AUDIO_SAMPLES_LENGTH]; + int smpIdx = 0; + for (uint32_t pcmIdx = 0; pcmIdx < AUDIO_SAMPLES_LENGTH; pcmIdx++) { + samples[smpIdx] = AnalogAudio::decodeMuLaw(frame[pcmIdx]); + smpIdx++; + } + + // post-process: apply gain to decoded audio frames + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); + + if (m_localAudio) { + m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH); + } + + if (m_udpAudio) { + int pcmIdx = 0; + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; + if (m_udpUseULaw) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); + } + + if (m_trace) + Utils::dump(1U, "HostBridge()::processAnalogNetwork(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH); + } + else { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_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 = (AUDIO_SAMPLES_LENGTH * 2U) + 4U; + uint8_t* audioData = nullptr; + + if (!m_udpUsrp) { + if (!m_udpMetadata) { + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + if (m_udpUseULaw) { + length = (AUDIO_SAMPLES_LENGTH)+4U; + if (m_udpNoIncludeLength) { + length = AUDIO_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, AUDIO_SAMPLES_LENGTH); + } + else { + SET_UINT32(AUDIO_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH); + } + + // are we sending RTP audio frames? + if (m_udpRTPFrames) { + uint8_t* rtpFrame = generateRTPHeaders(AUDIO_SAMPLES_LENGTH, m_rtpSeqNo); + if (rtpFrame != nullptr) { + length += RTP_HEADER_LENGTH_BYTES; + uint8_t* newAudioData = new uint8_t[length]; + ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, AUDIO_SAMPLES_LENGTH); + delete[] audioData; + + audioData = newAudioData; + } + + m_rtpSeqNo++; + } + } + else { + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); + } + } + else { + length = (AUDIO_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) + SET_UINT32((AUDIO_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, AUDIO_SAMPLES_LENGTH * 2U); + + // embed destination and source IDs + SET_UINT32(dstId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 4U)); + SET_UINT32(srcId, audioData, ((AUDIO_SAMPLES_LENGTH * 2U) + 8U)); + } + } + else { + uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; + + length = (AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(AUDIO_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + + m_usrpSeqNo++; + usrpHeader[15U] = 1; // set PTT state to true + SET_UINT32(m_usrpSeqNo, usrpHeader, 4U); + + ::memcpy(usrpHeader, "USRP", 4); + ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, AUDIO_SAMPLES_LENGTH * 2U); + } + + sockaddr_storage addr; + uint32_t addrLen; + + if (udp::Socket::lookup(m_udpSendAddress, m_udpSendPort, addr, addrLen) == 0) { + m_udpAudioSocket->write(audioData, length, addr, addrLen); + } + + delete[] audioData; + } + } + + m_rxStreamId = m_network->getAnalogStreamId(); + } +} + +/* Helper to encode analog network traffic audio frames. */ + +void HostBridge::encodeAnalogAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_t forcedDstId) +{ + assert(pcm != nullptr); + using namespace analog; + using namespace analog::defines; + using namespace analog::data; + + if (m_analogN == 254U) + m_analogN = 0; + + int smpIdx = 0; + short samples[AUDIO_SAMPLES_LENGTH]; + for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) { + samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); + smpIdx++; + } + + // pre-process: apply gain to PCM audio frames + AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain); + + uint32_t srcId = m_srcId; + if (m_srcIdOverride != 0 && (m_overrideSrcIdFromMDC)) + srcId = m_srcIdOverride; + if (m_overrideSrcIdFromUDP) + srcId = m_udpSrcId; + if (forcedSrcId > 0 && forcedSrcId != m_srcId) + srcId = forcedSrcId; + uint32_t dstId = m_dstId; + if (forcedDstId > 0 && forcedDstId != m_dstId) + dstId = forcedDstId; + + // never allow a source ID of 0 + if (srcId == 0U) + srcId = m_srcId; + + data::NetData analogData; + analogData.setSeqNo(m_analogN); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setControl(0U); + analogData.setFrameType(AudioFrameType::VOICE); + if (m_txStreamId <= 1U) { + analogData.setFrameType(AudioFrameType::VOICE_START); + + if (m_grantDemand) { + analogData.setControl(0x80U); // analog remote grant demand flag + } + } + + int pcmIdx = 0; + uint8_t outPcm[AUDIO_SAMPLES_LENGTH * 2U]; + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + outPcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]); + } + + if (m_trace) + Utils::dump(1U, "HostBridge()::encodeAnalogAudioFrame(), Encoded uLaw Audio", outPcm, AUDIO_SAMPLES_LENGTH); + + analogData.setAudio(outPcm); + + m_network->writeAnalog(analogData); + m_txStreamId = m_network->getAnalogStreamId(); + m_analogN++; +} + /* Helper to send USRP end of transmission */ -void HostBridge::sendUsrpEot() +void HostBridge::sendUsrpEot() { sockaddr_storage addr; uint32_t addrLen; @@ -2656,7 +2826,7 @@ void HostBridge::generatePreambleTone() { std::lock_guard lock(m_audioMutex); - uint64_t frameCount = SampleTimeConvert::ToSamples(SAMPLE_RATE, 1, m_preambleLength); + uint64_t frameCount = AnalogAudio::toSamples(SAMPLE_RATE, 1, m_preambleLength); if (frameCount > m_outputAudio.freeSpace()) { ::LogError(LOG_HOST, "failed to generate preamble tone"); return; @@ -2670,8 +2840,7 @@ void HostBridge::generatePreambleTone() ma_waveform_read_pcm_frames(&m_maSineWaveform, sine, frameCount, NULL); int smpIdx = 0; - std::unique_ptr __UNIQUE_sineSamples = std::make_unique(frameCount); - short* sineSamples = __UNIQUE_sineSamples.get(); + DECLARE_SHORT_ARRAY(sineSamples, frameCount); const uint8_t* pcm = (const uint8_t*)sine; for (uint32_t pcmIdx = 0; pcmIdx < pcmBytes; pcmIdx += 2) { sineSamples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); @@ -2745,47 +2914,67 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) if (!m_callInProgress) { switch (m_txMode) { case TX_MODE_DMR: - { - dmr::defines::DataType::E dataType = dmr::defines::DataType::VOICE_SYNC; - if (m_dmrN == 0) - dataType = dmr::defines::DataType::VOICE_SYNC; - else { - dataType = dmr::defines::DataType::VOICE; - } - - dmr::data::NetData data = dmr::data::NetData(); - data.setSlotNo(m_slot); - data.setDataType(dataType); - data.setSrcId(srcId); - data.setDstId(dstId); - data.setFLCO(dmr::defines::FLCO::GROUP); - data.setN(m_dmrN); - data.setSeqNo(m_dmrSeqNo); - data.setBER(0U); - data.setRSSI(0U); - - LogMessage(LOG_HOST, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot, dstId); + { + DMRDEF::DataType::E dataType = DMRDEF::DataType::VOICE_SYNC; + if (m_dmrN == 0) + dataType = DMRDEF::DataType::VOICE_SYNC; + else { + dataType = DMRDEF::DataType::VOICE; + } - m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData); - m_network->resetDMR(data.getSlotNo()); - } - break; + dmr::data::NetData data = dmr::data::NetData(); + data.setSlotNo(m_slot); + data.setDataType(dataType); + data.setSrcId(srcId); + data.setDstId(dstId); + data.setFLCO(DMRDEF::FLCO::GROUP); + data.setN(m_dmrN); + data.setSeqNo(m_dmrSeqNo); + data.setBER(0U); + data.setRSSI(0U); + + LogMessage(LOG_HOST, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot, dstId); + + m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData); + m_network->resetDMR(data.getSlotNo()); + } + break; case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(dstId); - lc.setSrcId(srcId); + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - LogMessage(LOG_HOST, P25_TDU_STR); + LogMessage(LOG_HOST, P25_TDU_STR); - uint8_t controlByte = 0x00U; - m_network->writeP25TDU(lc, lsd, controlByte); - m_network->resetP25(); - } - break; + uint8_t controlByte = 0x00U; + m_network->writeP25TDU(lc, lsd, controlByte); + m_network->resetP25(); + } + break; + case TX_MODE_ANALOG: + { + LogMessage(LOG_HOST, ANO_TERMINATOR); + + uint8_t controlByte = 0x00U; + + data::NetData analogData; + analogData.setSeqNo(m_analogN); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setFrameType(AudioFrameType::TERMINATOR); + + uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U]; + ::memset(pcm, 0x00U, AUDIO_SAMPLES_LENGTH * 2U); + analogData.setAudio(pcm); + + m_network->writeAnalog(analogData, true); + m_network->resetAnalog(); + } + break; } } @@ -2801,6 +2990,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) m_dmrN = 0U; m_p25SeqNo = 0U; m_p25N = 0U; + m_analogN = 0U; m_rtpSeqNo = 0U; m_rtpTimestamp = INVALID_TS; @@ -2871,13 +3061,13 @@ void* HostBridge::threadAudioProcess(void* arg) { std::lock_guard lock(m_audioMutex); - if (bridge->m_inputAudio.dataSize() >= MBE_SAMPLES_LENGTH) { - short samples[MBE_SAMPLES_LENGTH]; - bridge->m_inputAudio.get(samples, MBE_SAMPLES_LENGTH); + if (bridge->m_inputAudio.dataSize() >= AUDIO_SAMPLES_LENGTH) { + short samples[AUDIO_SAMPLES_LENGTH]; + bridge->m_inputAudio.get(samples, AUDIO_SAMPLES_LENGTH); // process MDC, if necessary if (bridge->m_overrideSrcIdFromMDC) - mdc_decoder_process_samples(bridge->m_mdcDecoder, samples, MBE_SAMPLES_LENGTH); + mdc_decoder_process_samples(bridge->m_mdcDecoder, samples, AUDIO_SAMPLES_LENGTH); float sampleLevel = bridge->m_voxSampleLevel / 1000; @@ -2895,7 +3085,7 @@ void* HostBridge::threadAudioProcess(void* arg) // perform maximum sample detection float maxSample = 0.0f; - for (int i = 0; i < MBE_SAMPLES_LENGTH; i++) { + for (int i = 0; i < (int)AUDIO_SAMPLES_LENGTH; i++) { float sampleValue = fabs((float)samples[i]); maxSample = fmax(maxSample, sampleValue); } @@ -2919,19 +3109,21 @@ void* HostBridge::threadAudioProcess(void* arg) if (bridge->m_grantDemand) { switch (bridge->m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(dstId); - lc.setSrcId(srcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = 0x80U; - bridge->m_network->writeP25TDU(lc, lsd, controlByte); - } - break; + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; } } } @@ -2950,11 +3142,11 @@ void* HostBridge::threadAudioProcess(void* arg) } if (bridge->m_audioDetect && !bridge->m_callInProgress) { - ma_uint32 pcmBytes = MBE_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); + ma_uint32 pcmBytes = AUDIO_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); DECLARE_UINT8_ARRAY(pcm, pcmBytes); int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; @@ -2968,6 +3160,9 @@ void* HostBridge::threadAudioProcess(void* arg) case TX_MODE_P25: bridge->encodeP25AudioFrame(pcm); break; + case TX_MODE_ANALOG: + bridge->encodeAnalogAudioFrame(pcm); + break; } } } @@ -3025,7 +3220,7 @@ void* HostBridge::threadUDPAudioProcess(void* arg) if (req != nullptr) { if (bridge->m_udpInterFrameDelay > 0U) { - int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (req->pktRxTime > now) { Thread::sleep(1U); @@ -3065,25 +3260,25 @@ void* HostBridge::threadUDPAudioProcess(void* arg) std::lock_guard lock(m_audioMutex); int smpIdx = 0; - short samples[MBE_SAMPLES_LENGTH]; + short samples[AUDIO_SAMPLES_LENGTH]; if (bridge->m_udpUseULaw) { if (bridge->m_trace) - Utils::dump(1U, "HostBridge()::threadUDPAudioProcess() uLaw Audio", req->pcm, MBE_SAMPLES_LENGTH * 2U); + Utils::dump(1U, "HostBridge()::threadUDPAudioProcess(), uLaw Audio", req->pcm, AUDIO_SAMPLES_LENGTH * 2U); - for (uint32_t pcmIdx = 0; pcmIdx < MBE_SAMPLES_LENGTH; pcmIdx++) { - samples[smpIdx] = decodeMuLaw(req->pcm[pcmIdx]); + for (uint32_t pcmIdx = 0; pcmIdx < AUDIO_SAMPLES_LENGTH; pcmIdx++) { + samples[smpIdx] = AnalogAudio::decodeMuLaw(req->pcm[pcmIdx]); smpIdx++; } int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { req->pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); req->pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } } else { - for (uint32_t pcmIdx = 0; pcmIdx < req->pcmLength; pcmIdx += 2) { + for (int pcmIdx = 0; pcmIdx < req->pcmLength; pcmIdx += 2) { samples[smpIdx] = (short)((req->pcm[pcmIdx + 1] << 8) + req->pcm[pcmIdx + 0]); smpIdx++; } @@ -3099,19 +3294,22 @@ void* HostBridge::threadUDPAudioProcess(void* arg) LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, bridge->m_udpSrcId, bridge->m_udpDstId); if (bridge->m_grantDemand) { switch (bridge->m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); - lc.setDstId(bridge->m_udpDstId); - lc.setSrcId(bridge->m_udpSrcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = 0x80U; - bridge->m_network->writeP25TDU(lc, lsd, controlByte); - } - break; + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(bridge->m_udpDstId); + lc.setSrcId(bridge->m_udpSrcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; + controlByte |= network::NET_CTRL_SWITCH_OVER; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; } } } @@ -3136,6 +3334,9 @@ void* HostBridge::threadUDPAudioProcess(void* arg) case TX_MODE_P25: bridge->encodeP25AudioFrame(req->pcm, bridge->m_udpSrcId); break; + case TX_MODE_ANALOG: + bridge->encodeAnalogAudioFrame(req->pcm, bridge->m_udpSrcId); + break; } } @@ -3198,7 +3399,7 @@ void* HostBridge::threadNetworkProcess(void* arg) } if (bridge->m_network->getStatus() == NET_STAT_RUNNING) { - if (bridge->m_tekAlgoId != p25::defines::ALGO_UNENCRYPT && bridge->m_tekKeyId > 0U) { + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && bridge->m_tekKeyId > 0U) { if (bridge->m_p25Crypto->getTEKLength() == 0U && !bridge->m_requestedTek) { bridge->m_requestedTek = true; LogMessage(LOG_HOST, "Bridge encryption enabled, requesting TEK from network."); @@ -3225,6 +3426,14 @@ void* HostBridge::threadNetworkProcess(void* arg) } } + if (bridge->m_txMode == TX_MODE_ANALOG) { + std::lock_guard lock(HostBridge::m_networkMutex); + UInt8Array analogBuffer = bridge->m_network->readAnalog(netReadRet, length); + if (netReadRet) { + bridge->processAnalogNetwork(analogBuffer.get(), length); + } + } + Thread::sleep(1U); } @@ -3280,113 +3489,115 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) { switch (m_txMode) { case TX_MODE_DMR: - { - using namespace dmr; - using namespace dmr::defines; + { + using namespace dmr; + using namespace dmr::defines; + using namespace dmr::data; - m_dmrN = (uint8_t)(m_dmrSeqNo % 6); + m_dmrN = (uint8_t)(m_dmrSeqNo % 6); - // send DMR voice - uint8_t data[DMR_FRAME_LENGTH_BYTES]; + // send DMR voice + uint8_t data[DMR_FRAME_LENGTH_BYTES]; - m_ambeCount = 0U; + m_ambeCount = 0U; - ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); - m_ambeCount++; - ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); - m_ambeCount++; - ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); - m_ambeCount++; + ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); + m_ambeCount++; + ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); + m_ambeCount++; + ::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES); + m_ambeCount++; - ::memcpy(data, m_ambeBuffer, 13U); - data[13U] = (uint8_t)(m_ambeBuffer[13U] & 0xF0); - data[19U] = (uint8_t)(m_ambeBuffer[13U] & 0x0F); - ::memcpy(data + 20U, m_ambeBuffer + 14U, 13U); + ::memcpy(data, m_ambeBuffer, 13U); + data[13U] = (uint8_t)(m_ambeBuffer[13U] & 0xF0); + data[19U] = (uint8_t)(m_ambeBuffer[13U] & 0x0F); + ::memcpy(data + 20U, m_ambeBuffer + 14U, 13U); - DataType::E dataType = DataType::VOICE_SYNC; - if (m_dmrN == 0) - dataType = DataType::VOICE_SYNC; - else { - dataType = DataType::VOICE; + DataType::E dataType = DataType::VOICE_SYNC; + if (m_dmrN == 0) + dataType = DataType::VOICE_SYNC; + else { + dataType = DataType::VOICE; - uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); + uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN); - // generated embedded signalling - data::EMB emb = data::EMB(); - emb.setColorCode(0U); - emb.setLCSS(lcss); - emb.encode(data); - } + // generated embedded signalling + EMB emb = EMB(); + emb.setColorCode(0U); + emb.setLCSS(lcss); + emb.encode(data); + } - LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); + LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); - // generate DMR network frame - data::NetData dmrData; - dmrData.setSlotNo(m_slot); - dmrData.setDataType(dataType); - dmrData.setSrcId(srcId); - dmrData.setDstId(dstId); - dmrData.setFLCO(FLCO::GROUP); - dmrData.setN(m_dmrN); - dmrData.setSeqNo(m_dmrSeqNo); - dmrData.setBER(0U); - dmrData.setRSSI(0U); + // generate DMR network frame + NetData dmrData; + dmrData.setSlotNo(m_slot); + dmrData.setDataType(dataType); + dmrData.setSrcId(srcId); + dmrData.setDstId(dstId); + dmrData.setFLCO(FLCO::GROUP); + dmrData.setN(m_dmrN); + dmrData.setSeqNo(m_dmrSeqNo); + dmrData.setBER(0U); + dmrData.setRSSI(0U); - dmrData.setData(data); + dmrData.setData(data); - m_network->writeDMR(dmrData, false); - m_txStreamId = m_network->getDMRStreamId(m_slot); + m_network->writeDMR(dmrData, false); + m_txStreamId = m_network->getDMRStreamId(m_slot); - m_dmrSeqNo++; - } - break; + m_dmrSeqNo++; + } + break; case TX_MODE_P25: - { - using namespace p25; - using namespace p25::defines; - - // fill the LDU buffers appropriately - switch (m_p25N) { - // LDU1 - case 0: - resetWithNullAudio(m_netLDU1, false); - break; + { + using namespace p25; + using namespace p25::defines; + using namespace p25::data; + + // fill the LDU buffers appropriately + switch (m_p25N) { + // LDU1 + case 0: + resetWithNullAudio(m_netLDU1, false); + break; - // LDU2 - case 1: - resetWithNullAudio(m_netLDU2, false); - break; - } + // LDU2 + case 1: + resetWithNullAudio(m_netLDU2, false); + break; + } - lc::LC lc = lc::LC(); - lc.setLCO(LCO::GROUP); - lc.setGroup(true); - lc.setPriority(4U); - lc.setDstId(dstId); - lc.setSrcId(srcId); + lc::LC lc = lc::LC(); + lc.setLCO(LCO::GROUP); + lc.setGroup(true); + lc.setPriority(4U); + lc.setDstId(dstId); + lc.setSrcId(srcId); - lc.setAlgId(ALGO_UNENCRYPT); - lc.setKId(0); + lc.setAlgId(ALGO_UNENCRYPT); + lc.setKId(0); - data::LowSpeedData lsd = data::LowSpeedData(); + LowSpeedData lsd = LowSpeedData(); - // send P25 LDU1 - if (m_p25N == 0U) { - LogMessage(LOG_HOST, P25_LDU1_STR " silence audio, srcId = %u, dstId = %u", srcId, dstId); - m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::DATA_UNIT); - m_p25N++; - break; - } + // send P25 LDU1 + if (m_p25N == 0U) { + LogMessage(LOG_HOST, P25_LDU1_STR " silence audio, srcId = %u, dstId = %u", srcId, dstId); + m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::DATA_UNIT); + m_p25N++; + break; + } - // send P25 LDU2 - if (m_p25N == 1U) { - LogMessage(LOG_HOST, P25_LDU2_STR " silence audio, algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U); - m_network->writeP25LDU2(lc, lsd, m_netLDU2); - m_p25N = 0U; - break; + // send P25 LDU2 + if (m_p25N == 1U) { + LogMessage(LOG_HOST, P25_LDU2_STR " silence audio, algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U); + m_network->writeP25LDU2(lc, lsd, m_netLDU2); + m_p25N = 0U; + break; + } } - } - break; + break; } } @@ -3467,6 +3678,7 @@ void* HostBridge::threadCallWatchdog(void* arg) bridge->m_dmrN = 0U; bridge->m_p25N = 0U; + bridge->m_analogN = 0U; } if (bridge->m_udpHangTime.isRunning()) @@ -3512,3 +3724,53 @@ void* HostBridge::threadCallWatchdog(void* arg) return nullptr; } + +/* Helper to initialize RTS PTT control. */ + +bool HostBridge::initializeRtsPtt() +{ + if (!m_rtsPttEnable) + return true; + + if (m_rtsPttPort.empty()) { + ::LogError(LOG_HOST, "RTS PTT port is not specified"); + return false; + } + + m_rtsPttController = new RtsPttController(m_rtsPttPort); + if (!m_rtsPttController->open()) { + ::LogError(LOG_HOST, "Failed to open RTS PTT port %s", m_rtsPttPort.c_str()); + delete m_rtsPttController; + m_rtsPttController = nullptr; + return false; + } + + ::LogInfo(LOG_HOST, "RTS PTT Controller initialized on %s", m_rtsPttPort.c_str()); + return true; +} + +/* Helper to assert RTS PTT (start transmission). */ + +void HostBridge::assertRtsPtt() +{ + if (!m_rtsPttEnable || m_rtsPttController == nullptr || m_rtsPttActive) + return; + + if (m_rtsPttController->setPTT()) { + m_rtsPttActive = true; + ::LogDebug(LOG_HOST, "RTS PTT asserted"); + } +} + +/* Helper to deassert RTS PTT (stop transmission). */ + +void HostBridge::deassertRtsPtt() +{ + if (!m_rtsPttEnable || m_rtsPttController == nullptr || !m_rtsPttActive) + return; + + if (m_rtsPttController->clearPTT()) { + m_rtsPttActive = false; + ::LogDebug(LOG_HOST, "RTS PTT deasserted"); + } +} diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 398935e8c..dbd6485fc 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -32,6 +32,7 @@ #include "audio/miniaudio.h" #include "mdc/mdc_decode.h" #include "network/PeerNetwork.h" +#include "RtsPttController.h" #include #include @@ -47,7 +48,6 @@ // Constants // --------------------------------------------------------------------------- -#define MBE_SAMPLES_LENGTH 160 #define NO_BIT_STEAL 0 #define ECMODE_NOISE_SUPPRESS 0x40 @@ -63,6 +63,7 @@ const uint8_t HALF_RATE_MODE = 0x01U; const uint8_t TX_MODE_DMR = 1U; const uint8_t TX_MODE_P25 = 2U; +const uint8_t TX_MODE_ANALOG = 3U; // --------------------------------------------------------------------------- @@ -93,31 +94,6 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unitID, mdc_u8_t extra0, mdc_u8_t extra1, mdc_u8_t extra2, mdc_u8_t extra3, void* context); -/** - * @brief Helper to convert PCM into G.711 aLaw. - * @param pcm PCM value. - * @return uint8_t aLaw value. - */ -uint8_t encodeALaw(short pcm); -/** - * @brief Helper to convert G.711 aLaw into PCM. - * @param alaw aLaw value. - * @return short PCM value. - */ -short decodeALaw(uint8_t alaw); -/** - * @brief Helper to convert PCM into G.711 MuLaw. - * @param pcm PCM value. - * @return uint8_t MuLaw value. - */ -uint8_t encodeMuLaw(short pcm); -/** - * @brief Helper to convert G.711 MuLaw into PCM. - * @param ulaw MuLaw value. - * @return short PCM value. - */ -short decodeMuLaw(uint8_t ulaw); - // --------------------------------------------------------------------------- // Structure Declaration // --------------------------------------------------------------------------- @@ -260,6 +236,11 @@ class HOST_SW_API HostBridge { uint32_t m_p25SeqNo; uint8_t m_p25N; + uint32_t m_netId; + uint32_t m_sysId; + + uint8_t m_analogN; + bool m_audioDetect; bool m_trafficFromUDP; uint32_t m_udpSrcId; @@ -278,6 +259,12 @@ class HOST_SW_API HostBridge { bool m_trace; bool m_debug; + // RTS PTT Control + bool m_rtsPttEnable; + std::string m_rtsPttPort; + RtsPttController* m_rtsPttController; + bool m_rtsPttActive; + uint16_t m_rtpSeqNo; uint32_t m_rtpTimestamp; @@ -485,12 +472,18 @@ class HOST_SW_API HostBridge { void encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U); /** - * @brief Helper to generate outgoing RTP headers. - * @param msgLen Message Length. - * @param rtpSeq RTP Sequence. - * @returns uint8_t* Buffer containing the encoded RTP headers. + * @brief Helper to process analog network traffic. + * @param buffer + * @param length */ - uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); + void processAnalogNetwork(uint8_t* buffer, uint32_t length); + /** + * @brief Helper to encode analog network traffic audio frames. + * @param pcm + * @param forcedSrcId + * @param forcedDstId + */ + void encodeAnalogAudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U); /** * @brief Helper to generate USRP end of transmission @@ -502,6 +495,14 @@ class HOST_SW_API HostBridge { */ void generatePreambleTone(); + /** + * @brief Helper to generate outgoing RTP headers. + * @param msgLen Message Length. + * @param rtpSeq RTP Sequence. + * @returns uint8_t* Buffer containing the encoded RTP headers. + */ + uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); + /** * @brief Helper to end a local or UDP call. * @param srcId @@ -517,6 +518,20 @@ class HOST_SW_API HostBridge { */ void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); + /** + * @brief Helper to initialize RTS PTT control. + * @returns bool True, if RTS PTT was initialized successfully, otherwise false. + */ + bool initializeRtsPtt(); + /** + * @brief Helper to assert RTS PTT (start transmission). + */ + void assertRtsPtt(); + /** + * @brief Helper to deassert RTS PTT (stop transmission). + */ + void deassertRtsPtt(); + /** * @brief Entry point to audio processing thread. * @param arg Instance of the thread_t structure. diff --git a/src/bridge/RtsPttController.cpp b/src/bridge/RtsPttController.cpp new file mode 100644 index 000000000..e82be9c99 --- /dev/null +++ b/src/bridge/RtsPttController.cpp @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + * + */ +#include "Defines.h" +#include "RtsPttController.h" + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the RtsPttController class. */ + +RtsPttController::RtsPttController(const std::string& port) : + m_port(port), + m_isOpen(false), +#if defined(_WIN32) + m_fd(INVALID_HANDLE_VALUE) +#else + m_fd(-1) +#endif // defined(_WIN32) +{ + assert(!port.empty()); +} + +/* Finalizes a instance of the RtsPttController class. */ + +RtsPttController::~RtsPttController() +{ + close(); +} + +/* Opens the serial port for RTS control. */ + +bool RtsPttController::open() +{ + if (m_isOpen) + return true; + +#if defined(_WIN32) + assert(m_fd == INVALID_HANDLE_VALUE); + + std::string deviceName = m_port; + if (deviceName.find("\\\\.\\") == std::string::npos) { + deviceName = "\\\\.\\" + m_port; + } + + m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (m_fd == INVALID_HANDLE_VALUE) { + ::LogError(LOG_HOST, "Cannot open RTS PTT device - %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } + + DCB dcb; + if (::GetCommState(m_fd, &dcb) == 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + + dcb.BaudRate = 9600; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + dcb.StopBits = ONESTOPBIT; + dcb.fInX = FALSE; + dcb.fOutX = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDsrSensitivity = FALSE; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + + if (::SetCommState(m_fd, &dcb) == 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + + // Clear RTS initially + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + +#else + assert(m_fd == -1); + + m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + ::LogError(LOG_HOST, "Cannot open RTS PTT device - %s", m_port.c_str()); + return false; + } + + if (::isatty(m_fd) == 0) { + ::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str()); + ::close(m_fd); + m_fd = -1; + return false; + } + + if (!setTermios()) { + ::close(m_fd); + m_fd = -1; + return false; + } +#endif // defined(_WIN32) + + ::LogInfo(LOG_HOST, "RTS PTT Controller opened on %s", m_port.c_str()); + m_isOpen = true; + return true; +} + +/* Closes the serial port. */ + +void RtsPttController::close() +{ + if (!m_isOpen) + return; + + // Clear RTS before closing + clearPTT(); + +#if defined(_WIN32) + if (m_fd != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + } +#else + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +#endif // defined(_WIN32) + + m_isOpen = false; + ::LogInfo(LOG_HOST, "RTS PTT Controller closed"); +} + +/* Sets RTS signal high (asserts RTS) to trigger PTT. */ + +bool RtsPttController::setPTT() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, SETRTS) == 0) { + ::LogError(LOG_HOST, "Cannot set RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set RTS PTT for %s", m_port.c_str()); + return false; + } +#endif // defined(_WIN32) + + ::LogDebug(LOG_HOST, "RTS PTT asserted on %s", m_port.c_str()); + return true; +} + +/* Sets RTS signal low (clears RTS) to release PTT. */ + +bool RtsPttController::clearPTT() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS PTT for %s", m_port.c_str()); + return false; + } +#endif // defined(_WIN32) + + ::LogDebug(LOG_HOST, "RTS PTT cleared on %s", m_port.c_str()); + return true; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Sets the termios settings on the serial port. */ + +bool RtsPttController::setTermios() +{ +#if !defined(_WIN32) + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str()); + return false; + } + + termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); + termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); + termios.c_iflag &= ~(IXON | IXOFF | IXANY); + termios.c_oflag &= ~(OPOST); + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= (CS8 | CLOCAL | CREAD); + termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); + termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; + + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str()); + return false; + } + + // Clear RTS initially + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s", m_port.c_str()); + return false; + } +#endif // !defined(_WIN32) + + return true; +} diff --git a/src/bridge/RtsPttController.h b/src/bridge/RtsPttController.h new file mode 100644 index 000000000..5cc8860c4 --- /dev/null +++ b/src/bridge/RtsPttController.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + * + */ +/** + * @file RtsPttController.h + * @ingroup bridge + * @file RtsPttController.cpp + * @ingroup bridge + */ +#if !defined(__RTS_PTT_CONTROLLER_H__) +#define __RTS_PTT_CONTROLLER_H__ + +#include "Defines.h" +#include "common/Log.h" + +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#include +#include +#endif // defined(_WIN32) + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements RTS PTT control for the bridge. + * @ingroup bridge + */ +class HOST_SW_API RtsPttController { +public: + /** + * @brief Initializes a new instance of the RtsPttController class. + * @param port Serial port device (e.g., /dev/ttyUSB0). + */ + RtsPttController(const std::string& port); + /** + * @brief Finalizes a instance of the RtsPttController class. + */ + ~RtsPttController(); + + /** + * @brief Opens the serial port for RTS control. + * @returns bool True, if port was opened successfully, otherwise false. + */ + bool open(); + /** + * @brief Closes the serial port. + */ + void close(); + + /** + * @brief Sets RTS signal high (asserts RTS) to trigger PTT. + * @returns bool True, if RTS was set successfully, otherwise false. + */ + bool setPTT(); + /** + * @brief Sets RTS signal low (clears RTS) to release PTT. + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + bool clearPTT(); + +private: + std::string m_port; + bool m_isOpen; +#if defined(_WIN32) + HANDLE m_fd; +#else + int m_fd; +#endif // defined(_WIN32) + + /** + * @brief Sets the termios settings on the serial port. + * @returns bool True, if settings are set, otherwise false. + */ + bool setTermios(); +}; + +#endif // __RTS_PTT_CONTROLLER_H__ diff --git a/src/bridge/SampleTimeConversion.h b/src/bridge/SampleTimeConversion.h deleted file mode 100644 index 662547996..000000000 --- a/src/bridge/SampleTimeConversion.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Bridge - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ - /** - * @file SampleTimeConversion.h - * @ingroup bridge - */ -#if !defined(__SAMPLE_TIME_CONVERSION_H__) -#define __SAMPLE_TIME_CONVERSION_H__ - -#include "Defines.h" - -// --------------------------------------------------------------------------- -// Class Declaration -// --------------------------------------------------------------------------- - -/** -* @brief -* @ingroup bridge -*/ -class HOST_SW_API SampleTimeConvert { -public: - /** - * @brief (ms) to sample count conversion - * @param sampleRate Sample rate. - * @param channels Number of audio channels. - * @param ms Number of milliseconds. - * @returns int Number of samples. - */ - static int ToSamples(uint32_t sampleRate, uint8_t channels, int ms) - { - return (int)(((long)ms) * sampleRate * channels / 1000); - } - - /** - * @brief Sample count to (ms) conversion - * @param sampleRate Sample rate. - * @param channels Number of audio channels. - * @param samples Number of samples. - * @returns int Number of milliseconds. - */ - static int ToMS(uint32_t sampleRate, uint8_t channels, int samples) - { - return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); - } -}; - -#endif // __SAMPLE_TIME_CONVERSION_H__ diff --git a/src/bridge/network/PeerNetwork.cpp b/src/bridge/network/PeerNetwork.cpp index 80f980e2a..449d68b8c 100644 --- a/src/bridge/network/PeerNetwork.cpp +++ b/src/bridge/network/PeerNetwork.cpp @@ -28,8 +28,8 @@ using namespace network; /* Initializes a new instance of the PeerNetwork class. */ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, analog, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) { assert(!address.empty()); assert(port > 0U); @@ -38,7 +38,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc /* Writes P25 LDU1 frame data to the network. */ -bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) +bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + P25DEF::FrameType::E frameType, uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -50,7 +51,7 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType); + UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType, controlByte); if (message == nullptr) { return false; } @@ -60,7 +61,8 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS /* Writes P25 LDU2 frame data to the network. */ -bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -72,7 +74,7 @@ bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data); + UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data, controlByte); if (message == nullptr) { return false; } @@ -211,7 +213,7 @@ bool PeerNetwork::writeConfig() /* Creates an P25 LDU1 frame message. */ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType) + const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -223,7 +225,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -286,7 +288,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l /* Creates an P25 LDU2 frame message. */ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data) + const uint8_t* data, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -298,7 +300,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; diff --git a/src/bridge/network/PeerNetwork.h b/src/bridge/network/PeerNetwork.h index 0d58146a7..8fe3b8f84 100644 --- a/src/bridge/network/PeerNetwork.h +++ b/src/bridge/network/PeerNetwork.h @@ -48,6 +48,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -55,7 +56,7 @@ namespace network * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Writes P25 LDU1 frame data to the network. @@ -63,18 +64,21 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - p25::defines::FrameType::E frameType) override; + P25DEF::FrameType::E frameType, uint8_t controlByte = 0U) override; /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ - bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) override; + bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte = 0U) override; /** * @brief Helper to send a DMR terminator with LC message. @@ -104,10 +108,11 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType); + const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte); /** * @brief Creates an P25 LDU2 frame message. * @@ -118,10 +123,11 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data); + const uint8_t* data, uint8_t controlByte); }; } // namespace network diff --git a/src/bridge/win32/project.ico b/src/bridge/win32/project.ico new file mode 100644 index 000000000..7557168b5 Binary files /dev/null and b/src/bridge/win32/project.ico differ diff --git a/src/bridge/win32/resource.h b/src/bridge/win32/resource.h new file mode 100644 index 000000000..7ebe3a27a --- /dev/null +++ b/src/bridge/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/bridge/win32/resource.rc b/src/bridge/win32/resource.rc new file mode 100644 index 000000000..964be84fb Binary files /dev/null and b/src/bridge/win32/resource.rc differ diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a7d394f4c..ea4983dbb 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -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 # * # */ file(GLOB common_SRC @@ -39,6 +39,10 @@ file(GLOB common_SRC "src/common/nxdn/lc/*.cpp" "src/common/nxdn/lc/rcch/*.cpp" + # Analog Module + "src/common/analog/*.cpp" + "src/common/analog/data/*.cpp" + # Core "src/common/edac/*.cpp" "src/common/lookups/*.cpp" @@ -87,6 +91,10 @@ file(GLOB common_INCLUDE "src/common/nxdn/lc/*.h" "src/common/nxdn/lc/rcch/*.h" + # Analog Module + "src/common/analog/*.h" + "src/common/analog/data/*.h" + # Core "src/common/concurrent/*.h" "src/common/edac/*.h" diff --git a/src/common/Defines.h b/src/common/Defines.h index 2c4b29be3..bb9db1e26 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -117,8 +117,8 @@ typedef unsigned long long ulong64_t; #define __EXE_NAME__ "" #define VERSION_MAJOR "04" -#define VERSION_MINOR "31" -#define VERSION_REV "H" +#define VERSION_MINOR "32" +#define VERSION_REV "J" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR #define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" diff --git a/src/common/Log.cpp b/src/common/Log.cpp index b1668f062..10a835a1f 100644 --- a/src/common/Log.cpp +++ b/src/common/Log.cpp @@ -54,6 +54,7 @@ uint32_t g_logDisplayLevel = 2U; bool g_disableTimeDisplay = false; bool g_useSyslog = false; +bool g_disableNetworkLog = false; static struct tm m_tm; @@ -314,7 +315,7 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo, m_outStream << buffer << std::endl; } - if (m_network != nullptr) { + if (m_network != nullptr && !g_disableNetworkLog) { // don't transfer debug data... if (level > 1U) { m_network->writeDiagLog(buffer); diff --git a/src/common/Log.h b/src/common/Log.h index 349c18166..2ee85a2f2 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -139,6 +139,10 @@ extern bool g_disableTimeDisplay; * @brief (Global) Flag indicating whether or not logging goes to the syslog. */ extern bool g_useSyslog; +/** + * @brief (Global) Flag indicating whether or not network logging is disabled. + */ +extern bool g_disableNetworkLog; // --------------------------------------------------------------------------- // Global Functions diff --git a/src/common/RC4Crypto.cpp b/src/common/RC4Crypto.cpp index f8b425531..78c9e6cf1 100644 --- a/src/common/RC4Crypto.cpp +++ b/src/common/RC4Crypto.cpp @@ -22,7 +22,7 @@ using namespace crypto; // Public Class Members // --------------------------------------------------------------------------- -/* Initializes a new instance of the AES class. */ +/* Initializes a new instance of the RC4 class. */ RC4::RC4() = default; diff --git a/src/common/VariableLengthArray.h b/src/common/VariableLengthArray.h index 243648397..afe62707b 100644 --- a/src/common/VariableLengthArray.h +++ b/src/common/VariableLengthArray.h @@ -41,6 +41,12 @@ typedef std::unique_ptr UInt8Array; */ typedef std::unique_ptr CharArray; +/** + * @brief Unique char array. + * @ingroup common + */ +typedef std::unique_ptr ShortArray; + /** @} */ // --------------------------------------------------------------------------- @@ -66,7 +72,7 @@ typedef std::unique_ptr CharArray; /** * @brief Declares a unique char array/buffer. - * This macro creates a unique pointer to a uint8_t array of the specified length and initializes it to zero. + * This macro creates a unique pointer to a char array of the specified length and initializes it to zero. * The resulting pointer is named after the parameter name passed to the macro. * @ingroup common * @param name Name of array/buffer. @@ -77,6 +83,19 @@ typedef std::unique_ptr CharArray; char* name = __##name##__CharArray.get(); \ ::memset(name, 0, len); +/** + * @brief Declares a unique short array/buffer. + * This macro creates a unique pointer to a short array of the specified length and initializes it to zero. + * The resulting pointer is named after the parameter name passed to the macro. + * @ingroup common + * @param name Name of array/buffer. + * @param len Length of array/buffer. + */ +#define DECLARE_SHORT_ARRAY(name, len) \ + ShortArray __##name##__ShortArray = std::make_unique(len); \ + short* name = __##name##__ShortArray.get(); \ + ::memset(name, 0, len); + /** @} */ #endif // __VARIABLE_LENGTH_ARRAY_H__ \ No newline at end of file diff --git a/src/common/analog/AnalogAudio.cpp b/src/common/analog/AnalogAudio.cpp new file mode 100644 index 000000000..1d1dab26f --- /dev/null +++ b/src/common/analog/AnalogAudio.cpp @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "analog/AnalogAudio.h" +#include "Log.h" +#include "Utils.h" + +using namespace analog; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define SIGN_BIT (0x80) // sign bit for a A-law byte +#define QUANT_MASK (0xf) // quantization field mask +#define NSEGS (8) // number of A-law segments +#define SEG_SHIFT (4) // left shift for segment number +#define SEG_MASK (0x70) // segment field mask + +static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF }; +static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF }; + +#define BIAS (0x84) // bias for linear code +#define CLIP 8159 + +#define SHORT_CLIP_SMPL_MIN -32767 +#define SHORT_CLIP_SMPL_MAX 32767 + +// --------------------------------------------------------------------------- +// Public Static Class Members +// --------------------------------------------------------------------------- + +/* Helper to convert PCM into G.711 aLaw. */ + +uint8_t AnalogAudio::encodeALaw(short pcm) +{ + short mask; + uint8_t aval; + + pcm = pcm >> 3; + + if (pcm >= 0) { + mask = 0xD5U; // sign (7th) bit = 1 + } else { + mask = 0x55U; // sign bit = 0 + pcm = -pcm - 1; + } + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_aend, 8); + + /* + ** combine the sign, segment, quantization bits + */ + if (seg >= 8) // out of range, return maximum value + return (uint8_t)(0x7F ^ mask); + else { + aval = (uint8_t) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm >> 1) & QUANT_MASK; + else + aval |= (pcm >> seg) & QUANT_MASK; + + return (aval ^ mask); + } +} + +/* Helper to convert G.711 aLaw into PCM. */ + +short AnalogAudio::decodeALaw(uint8_t alaw) +{ + alaw ^= 0x55U; + + short t = (alaw & QUANT_MASK) << 4; + short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108U; + break; + default: + t += 0x108U; + t <<= seg - 1; + } + + return ((alaw & SIGN_BIT) ? t : -t); +} + +/* Helper to convert PCM into G.711 MuLaw. */ + +uint8_t AnalogAudio::encodeMuLaw(short pcm) +{ + short mask; + + // get the sign and the magnitude of the value + pcm = pcm >> 2; + if (pcm < 0) { + pcm = -pcm; + mask = 0x7FU; + } else { + mask = 0xFFU; + } + + // clip the magnitude + if (pcm > CLIP) + pcm = CLIP; + pcm += (BIAS >> 2); + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_uend, 8); + + /* + ** combine the sign, segment, quantization bits; + ** and complement the code word + */ + if (seg >= 8) // out of range, return maximum value. + return (uint8_t)(0x7F ^ mask); + else { + uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF); + return (ulaw ^ mask); + } +} + +/* Helper to convert G.711 MuLaw into PCM. */ + +short AnalogAudio::decodeMuLaw(uint8_t ulaw) +{ + // complement to obtain normal u-law value + ulaw = ~ulaw; + + /* + ** extract and bias the quantization bits; then + ** shift up by the segment number and subtract out the bias + */ + short t = ((ulaw & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT; + + return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* Helper to apply linear gain to PCM samples. */ + +void AnalogAudio::gain(short* pcm, int length, float gain) +{ + assert(pcm != nullptr); + assert(length > 0); + + if (gain == 1.0f) + return; + + for (int i = 0; i < length; i++) { + int sample = static_cast(pcm[i] * gain); + if (sample > SHORT_CLIP_SMPL_MAX) + sample = SHORT_CLIP_SMPL_MAX; + else if (sample < SHORT_CLIP_SMPL_MIN) + sample = SHORT_CLIP_SMPL_MIN; + + pcm[i] = static_cast(sample); + } +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- + +/* Searches for the segment in the given array. */ + +short AnalogAudio::search(short val, short* table, short size) +{ + for (short i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + + return (size); +} diff --git a/src/common/analog/AnalogAudio.h b/src/common/analog/AnalogAudio.h new file mode 100644 index 000000000..dfcbf955e --- /dev/null +++ b/src/common/analog/AnalogAudio.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file AnalogAudio.h + * @ingroup analog + * @file AnalogAudio.cpp + * @ingroup analog + */ +#if !defined(__ANALOG_AUDIO_H__) +#define __ANALOG_AUDIO_H__ + +#include "common/Defines.h" +#include "common/analog/AnalogDefines.h" + +namespace analog +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements aLaw and uLaw audio codecs, along with helper routines for analog audio. + * @ingroup analog + */ + class HOST_SW_API AnalogAudio { + public: + /** + * @brief Helper to convert PCM into G.711 aLaw. + * @param pcm PCM value. + * @return uint8_t aLaw value. + */ + static uint8_t encodeALaw(short pcm); + /** + * @brief Helper to convert G.711 aLaw into PCM. + * @param alaw aLaw value. + * @return short PCM value. + */ + static short decodeALaw(uint8_t alaw); + /** + * @brief Helper to convert PCM into G.711 MuLaw. + * @param pcm PCM value. + * @return uint8_t MuLaw value. + */ + static uint8_t encodeMuLaw(short pcm); + /** + * @brief Helper to convert G.711 MuLaw into PCM. + * @param ulaw MuLaw value. + * @return short PCM value. + */ + static short decodeMuLaw(uint8_t ulaw); + + /** + * @brief Helper to apply linear gain to PCM samples. + * @param pcm Buffer containing PCM samples. + * @param length Length of the PCM buffer in samples. + * @param gain Gain factor to apply to the PCM samples (1.0f = no change). + */ + static void gain(short *pcm, int length, float gain); + + /** + * @brief (ms) to sample count conversion. + * @param sampleRate Sample rate. + * @param channels Number of audio channels. + * @param ms Number of milliseconds. + * @returns int Number of samples. + */ + static int toSamples(uint32_t sampleRate, uint8_t channels, int ms) { return (int)(((long)ms) * sampleRate * channels / 1000); } + + /** + * @brief Sample count to (ms) conversion. + * @param sampleRate Sample rate. + * @param channels Number of audio channels. + * @param samples Number of samples. + * @returns int Number of milliseconds. + */ + static int toMS(uint32_t sampleRate, uint8_t channels, int samples) { return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); } + + private: + /** + * @brief Searches for the segment in the given array. + * @param val PCM value to search for. + * @param table Array of segment values. + * @param size Size of the segment array. + * @return short + */ + static short search(short val, short* table, short size); + }; +} // namespace analog + +#endif // __ANALOG_AUDIO_H__ diff --git a/src/common/analog/AnalogDefines.h b/src/common/analog/AnalogDefines.h new file mode 100644 index 000000000..ee604b16f --- /dev/null +++ b/src/common/analog/AnalogDefines.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup analog Digital Mobile Radio + * @brief Defines and implements aLaw and uLaw audio codecs, along with helper routines for analog audio. + * @ingroup common + * + * @file AnalogDefines.h + * @ingroup analog + */ +#if !defined(__ANALOG_DEFINES_H__) +#define __ANALOG_DEFINES_H__ + +#include "common/Defines.h" + +// Shorthand macro to analog::defines -- keeps source code that doesn't use "using" concise +#if !defined(ANODEF) +#define ANODEF analog::defines +#endif // DMRDEF +namespace analog +{ + namespace defines + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + /** + * @addtogroup analog + * @{ + */ + + const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //! Sample size for 20ms of 16-bit audio at 8kHz. + const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //! Sample size for 20ms of 16-bit audio at 8kHz in bytes. + /** @} */ + + /** @brief Audio Frame Type(s) */ + namespace AudioFrameType { + /** @brief Audio Frame Type(s) */ + enum E : uint8_t { + VOICE_START = 0x00U, //! Voice Start Frame + VOICE = 0x01U, //! Voice Continuation Frame + TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator + }; + } + + #define ANO_TERMINATOR "Analog, TERMINATOR (Terminator)" + #define ANO_VOICE "Analog, VOICE (Voice Data)" + } // namespace defines +} // namespace analog + +#endif // __ANALOG_DEFINES_H__ diff --git a/src/common/analog/data/NetData.cpp b/src/common/analog/data/NetData.cpp new file mode 100644 index 000000000..f6a509fe7 --- /dev/null +++ b/src/common/analog/data/NetData.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "analog/AnalogAudio.h" +#include "analog/data/NetData.h" + +using namespace analog; +using namespace analog::defines; +using namespace analog::data; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the NetData class. */ + +NetData::NetData(const NetData& data) : + m_srcId(data.m_srcId), + m_dstId(data.m_dstId), + m_control(data.m_control), + m_seqNo(data.m_seqNo), + m_group(true), + m_frameType(data.m_frameType), + m_audio(nullptr) +{ + m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES]; + ::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES); +} + +/* Initializes a new instance of the NetData class. */ + +NetData::NetData() : + m_srcId(0U), + m_dstId(0U), + m_control(0U), + m_seqNo(0U), + m_group(true), + m_frameType(AudioFrameType::TERMINATOR), + m_audio(nullptr) +{ + m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES]; +} + +/* Finalizes a instance of the NetData class. */ + +NetData::~NetData() +{ + delete[] m_audio; +} + +/* Equals operator. */ + +NetData& NetData::operator=(const NetData& data) +{ + if (this != &data) { + ::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES); + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + m_control = data.m_control; + m_frameType = data.m_frameType; + m_seqNo = data.m_seqNo; + m_group = data.m_group; + } + + return *this; +} + +/* Sets audio data. */ + +void NetData::setAudio(const uint8_t* buffer) +{ + assert(buffer != nullptr); + + ::memcpy(m_audio, buffer, AUDIO_SAMPLES_LENGTH_BYTES); +} + +/* Gets audio data. */ + +uint32_t NetData::getAudio(uint8_t* buffer) const +{ + assert(buffer != nullptr); + + ::memcpy(buffer, m_audio, AUDIO_SAMPLES_LENGTH_BYTES); + + return AUDIO_SAMPLES_LENGTH_BYTES; +} diff --git a/src/common/analog/data/NetData.h b/src/common/analog/data/NetData.h new file mode 100644 index 000000000..2691c5ef3 --- /dev/null +++ b/src/common/analog/data/NetData.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file NetData.h + * @ingroup analog + * @file NetData.cpp + * @ingroup analog + */ +#if !defined(__ANALOG_DATA__NET_DATA_H__) +#define __ANALOG_DATA__NET_DATA_H__ + +#include "common/Defines.h" +#include "common/analog/AnalogDefines.h" + +namespace analog +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents network analog data. When setting audio data, it is expected to be 20ms of 16-bit + * audio at 8kHz, or 320 bytes in length. + * @ingroup analog + */ + class HOST_SW_API NetData { + public: + /** + * @brief Initializes a new instance of the NetData class. + * @param data Instance of NetData class to copy from. + */ + NetData(const NetData& data); + /** + * @brief Initializes a new instance of the NetData class. + */ + NetData(); + /** + * @brief Finalizes a instance of the NetData class. + */ + ~NetData(); + + /** + * @brief Equals operator. + * @param data Instance of NetData class to copy from. + */ + NetData& operator=(const NetData& data); + + /** + * @brief Sets audio data. This data buffer should be MuLaw encoded. + * @param[in] buffer Audio data buffer. + */ + void setAudio(const uint8_t* buffer); + /** + * @brief Gets audio data. + * @param[out] buffer Audio data buffer. + */ + uint32_t getAudio(uint8_t* buffer) const; + + public: + /** + * @brief Source ID. + */ + DECLARE_PROPERTY(uint32_t, srcId, SrcId); + /** + * @brief Destination ID. + */ + DECLARE_PROPERTY(uint32_t, dstId, DstId); + + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, control, Control); + + /** + * @brief Sequence number. + */ + DECLARE_PROPERTY(uint8_t, seqNo, SeqNo); + + /** + * @brief Flag indicatin if this group audio or individual audio. + */ + DECLARE_PROPERTY(bool, group, Group); + + /** + * @brief Audio frame type. + */ + DECLARE_PROPERTY(defines::AudioFrameType::E, frameType, FrameType); + + private: + uint8_t* m_audio; + }; + } // namespace data +} // namespace analog + +#endif // __ANALOG_DATA__NET_DATA_H__ diff --git a/src/common/dmr/DMRDefines.h b/src/common/dmr/DMRDefines.h index 30f84b2a0..2277faa29 100644 --- a/src/common/dmr/DMRDefines.h +++ b/src/common/dmr/DMRDefines.h @@ -200,11 +200,14 @@ namespace dmr }; }; - /** @name Feature IDs */ + /** @name Feature Set IDs */ /** @brief ETSI Standard Feature Set */ const uint8_t FID_ETSI = 0x00U; + /** @brief Motorola */ - const uint8_t FID_DMRA = 0x10U; + const uint8_t FID_MOT = 0x10U; + /** @brief Kenwood */ + const uint8_t FID_KENWOOD = 0x20U; /** @brief DVM; Omaha Communication Systems, LLC ($9C) */ const uint8_t FID_DVM_OCS = 0x9CU; /** @} */ @@ -259,9 +262,9 @@ namespace dmr }; } - /** @brief FID_DMRA Extended Functions. */ + /** @brief FID_MOT Extended Functions. */ namespace ExtendedFunctions { - /** @brief FID_DMRA Extended Functions. */ + /** @brief FID_MOT Extended Functions. */ enum : uint16_t { CHECK = 0x0000U, //! Radio Check UNINHIBIT = 0x007EU, //! Radio Uninhibit diff --git a/src/common/dmr/DMRUtils.cpp b/src/common/dmr/DMRUtils.cpp new file mode 100644 index 000000000..964f68cf2 --- /dev/null +++ b/src/common/dmr/DMRUtils.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/DMRUtils.h" +#include "Utils.h" + +using namespace dmr; +using namespace dmr::defines; + +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +/* Helper to convert a denial reason code to a string. */ + +std::string DMRUtils::rsnToString(uint8_t reason) +{ + switch (reason) { + case ReasonCode::TS_ACK_RSN_MSG: + return std::string("TS_ACK_RSN_MSG (Message Accepted)"); + case ReasonCode::TS_ACK_RSN_REG: + return std::string("TS_ACK_RSN_REG (Registration Accepted)"); + case ReasonCode::TS_ACK_RSN_AUTH_RESP: + return std::string("TS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)"); + case ReasonCode::TS_ACK_RSN_REG_SUB_ATTACH: + return std::string("TS_ACK_RSN_REG_SUB_ATTACH (Registration Response with subscription)"); + case ReasonCode::MS_ACK_RSN_MSG: + return std::string("MS_ACK_RSN_MSG (Message Accepted)"); + case ReasonCode::MS_ACK_RSN_AUTH_RESP: + return std::string("MS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)"); + + case ReasonCode::TS_DENY_RSN_SYS_UNSUPPORTED_SVC: + return std::string("TS_DENY_RSN_SYS_UNSUPPORTED_SVC (System Unsupported Service)"); + case ReasonCode::TS_DENY_RSN_PERM_USER_REFUSED: + return std::string("TS_DENY_RSN_PERM_USER_REFUSED (User Permenantly Refused)"); + case ReasonCode::TS_DENY_RSN_TEMP_USER_REFUSED: + return std::string("TS_DENY_RSN_TEMP_USER_REFUSED (User Temporarily Refused)"); + case ReasonCode::TS_DENY_RSN_TRSN_SYS_REFUSED: + return std::string("TS_DENY_RSN_TRSN_SYS_REFUSED (System Refused)"); + case ReasonCode::TS_DENY_RSN_TGT_NOT_REG: + return std::string("TS_DENY_RSN_TGT_NOT_REG (Target Not Registered)"); + case ReasonCode::TS_DENY_RSN_TGT_UNAVAILABLE: + return std::string("TS_DENY_RSN_TGT_UNAVAILABLE (Target Unavailable)"); + case ReasonCode::TS_DENY_RSN_SYS_BUSY: + return std::string("TS_DENY_RSN_SYS_BUSY (System Busy)"); + case ReasonCode::TS_DENY_RSN_SYS_NOT_READY: + return std::string("TS_DENY_RSN_SYS_NOT_READY (System Not Ready)"); + case ReasonCode::TS_DENY_RSN_CALL_CNCL_REFUSED: + return std::string("TS_DENY_RSN_CALL_CNCL_REFUSED (Call Cancel Refused)"); + case ReasonCode::TS_DENY_RSN_REG_REFUSED: + return std::string("TS_DENY_RSN_REG_REFUSED (Registration Refused)"); + case ReasonCode::TS_DENY_RSN_REG_DENIED: + return std::string("TS_DENY_RSN_REG_DENIED (Registration Denied)"); + case ReasonCode::TS_DENY_RSN_MS_NOT_REG: + return std::string("TS_DENY_RSN_MS_NOT_REG (MS Not Registered)"); + case ReasonCode::TS_DENY_RSN_TGT_BUSY: + return std::string("TS_DENY_RSN_TGT_BUSY (Target Busy)"); + case ReasonCode::TS_DENY_RSN_TGT_GROUP_NOT_VALID: + return std::string("TS_DENY_RSN_TGT_GROUP_NOT_VALID (Group Not Valid)"); + + case ReasonCode::TS_QUEUED_RSN_NO_RESOURCE: + return std::string("TS_QUEUED_RSN_NO_RESOURCE (No Resources Available)"); + case ReasonCode::TS_QUEUED_RSN_SYS_BUSY: + return std::string("TS_QUEUED_RSN_SYS_BUSY (System Busy)"); + + case ReasonCode::TS_WAIT_RSN: + return std::string("TS_WAIT_RSN (Wait)"); + + case ReasonCode::MS_DENY_RSN_UNSUPPORTED_SVC: + return std::string("MS_DENY_RSN_UNSUPPORTED_SVC (Service Unsupported)"); + + default: + return std::string(); + } +} diff --git a/src/common/dmr/DMRUtils.h b/src/common/dmr/DMRUtils.h index 471c4447c..aa4aaa9a6 100644 --- a/src/common/dmr/DMRUtils.h +++ b/src/common/dmr/DMRUtils.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) 2021,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -136,6 +136,13 @@ namespace dmr return id; } + + /** + * @brief Helper to convert a reason code to a string. + * @param reason Reason code. + * @returns std::string Reason code string. + */ + static std::string rsnToString(uint8_t reason); }; } // namespace dmr diff --git a/src/common/dmr/lc/CSBK.cpp b/src/common/dmr/lc/CSBK.cpp index ca54cf2ad..9c8a655dd 100644 --- a/src/common/dmr/lc/CSBK.cpp +++ b/src/common/dmr/lc/CSBK.cpp @@ -272,7 +272,7 @@ bool CSBK::decode(const uint8_t* data, uint8_t* payload) } if (m_verbose) { - Utils::dump(2U, "Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); + Utils::dump(2U, "CSBK::decode(), Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } m_raw = new uint8_t[DMR_CSBK_LENGTH_BYTES]; @@ -340,7 +340,7 @@ void CSBK::encode(uint8_t* data, const uint8_t* payload) } if (m_verbose) { - Utils::dump(2U, "Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); + Utils::dump(2U, "CSBK::encode(), Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } // encode BPTC (196,96) FEC diff --git a/src/common/dmr/lc/csbk/CSBKFactory.cpp b/src/common/dmr/lc/csbk/CSBKFactory.cpp index c16dc6203..bdb4dc47b 100644 --- a/src/common/dmr/lc/csbk/CSBKFactory.cpp +++ b/src/common/dmr/lc/csbk/CSBKFactory.cpp @@ -93,10 +93,10 @@ std::unique_ptr CSBKFactory::createCSBK(const uint8_t* data, DataType::E d return decode(new CSBK_UU_ANS_RSP(), data); case CSBKO::PRECCSBK: return decode(new CSBK_PRECCSBK(), data); - case CSBKO::RAND: // CSBKO::CALL_ALRT when FID == FID_DMRA + case CSBKO::RAND: // CSBKO::CALL_ALRT when FID == FID_MOT switch (FID) { - case FID_DMRA: + case FID_MOT: return decode(new CSBK_CALL_ALRT(), data); case FID_ETSI: default: diff --git a/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp b/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp index 98b9ab0d2..8872452af 100644 --- a/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp +++ b/src/common/dmr/lc/csbk/CSBK_CALL_ALRT.cpp @@ -26,7 +26,7 @@ using namespace dmr::lc::csbk; CSBK_CALL_ALRT::CSBK_CALL_ALRT() : CSBK() { m_CSBKO = CSBKO::RAND; - m_FID = FID_DMRA; + m_FID = FID_MOT; } /* Decode a control signalling block. */ diff --git a/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp b/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp index 745343191..0f20d561e 100644 --- a/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp +++ b/src/common/dmr/lc/csbk/CSBK_EXT_FNCT.cpp @@ -27,7 +27,7 @@ CSBK_EXT_FNCT::CSBK_EXT_FNCT() : CSBK(), m_extendedFunction(ExtendedFunctions::CHECK) { m_CSBKO = CSBKO::EXT_FNCT; - m_FID = FID_DMRA; + m_FID = FID_MOT; } /* Decode a control signalling block. */ diff --git a/src/common/lookups/AdjSiteMapLookup.cpp b/src/common/lookups/AdjSiteMapLookup.cpp new file mode 100644 index 000000000..9d5686ff0 --- /dev/null +++ b/src/common/lookups/AdjSiteMapLookup.cpp @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "lookups/AdjSiteMapLookup.h" +#include "Log.h" +#include "Timer.h" +#include "Utils.h" + +using namespace lookups; + +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex AdjSiteMapLookup::m_mutex; +bool AdjSiteMapLookup::m_locked = false; + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +// Lock the table. +#define __LOCK_TABLE() \ + std::lock_guard lock(m_mutex); \ + m_locked = true; + +// Unlock the table. +#define __UNLOCK_TABLE() m_locked = false; + +// Spinlock wait for table to be read unlocked. +#define __SPINLOCK() \ + if (m_locked) { \ + while (m_locked) \ + Thread::sleep(2U); \ + } + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the AdjSiteMapLookup class. */ + +AdjSiteMapLookup::AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime) : Thread(), + m_rulesFile(filename), + m_reloadTime(reloadTime), + m_rules(), + m_stop(false), + m_adjPeerMap() +{ + /* stub */ +} + +/* Finalizes a instance of the AdjSiteMapLookup class. */ + +AdjSiteMapLookup::~AdjSiteMapLookup() = default; + +/* Thread entry point. This function is provided to run the thread for the lookup table. */ + +void AdjSiteMapLookup::entry() +{ + if (m_reloadTime == 0U) { + return; + } + + Timer timer(1U, 60U * m_reloadTime); + timer.start(); + + while (!m_stop) { + sleep(1000U); + + timer.clock(); + if (timer.hasExpired()) { + load(); + timer.start(); + } + } +} + +/* Stops and unloads this lookup table. */ + +void AdjSiteMapLookup::stop(bool noDestroy) +{ + if (m_reloadTime == 0U) { + if (!noDestroy) + delete this; + return; + } + + m_stop = true; + + wait(); +} + +/* Reads the lookup table from the specified lookup table file. */ + +bool AdjSiteMapLookup::read() +{ + bool ret = load(); + + if (m_reloadTime > 0U) + run(); + setName("host:adj-site-map"); + + return ret; +} + +/* Clears all entries from the lookup table. */ + +void AdjSiteMapLookup::clear() +{ + __LOCK_TABLE(); + + m_adjPeerMap.clear(); + + __UNLOCK_TABLE(); +} + +/* Adds a new entry to the lookup table by the specified unique ID. */ + +void AdjSiteMapLookup::addEntry(AdjPeerMapEntry entry) +{ + uint32_t id = entry.peerId(); + + __LOCK_TABLE(); + + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), + [&](AdjPeerMapEntry x) + { + return x.peerId() == id; + }); + if (it != m_adjPeerMap.end()) { + m_adjPeerMap[it - m_adjPeerMap.begin()] = entry; + } + else { + m_adjPeerMap.push_back(entry); + } + + __UNLOCK_TABLE(); +} + +/* Erases an existing entry from the lookup table by the specified unique ID. */ + +void AdjSiteMapLookup::eraseEntry(uint32_t id) +{ + __LOCK_TABLE(); + + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), [&](AdjPeerMapEntry x) { return x.peerId() == id; }); + if (it != m_adjPeerMap.end()) { + m_adjPeerMap.erase(it); + } + + __UNLOCK_TABLE(); +} + +/* Finds a table entry in this lookup table. */ + +AdjPeerMapEntry AdjSiteMapLookup::find(uint32_t id) +{ + AdjPeerMapEntry entry; + + __SPINLOCK(); + + std::lock_guard lock(m_mutex); + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), + [&](AdjPeerMapEntry x) + { + return x.peerId() == id; + }); + if (it != m_adjPeerMap.end()) { + entry = *it; + } else { + entry = AdjPeerMapEntry(); + } + + return entry; +} + +/* Saves loaded talkgroup rules. */ + +bool AdjSiteMapLookup::commit() +{ + return save(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Loads the table from the passed lookup table file. */ + +bool AdjSiteMapLookup::load() +{ + if (m_rulesFile.length() <= 0) { + return false; + } + + try { + bool ret = yaml::Parse(m_rules, m_rulesFile.c_str()); + if (!ret) { + LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s - error parsing YML", m_rulesFile.c_str()); + return false; + } + } + catch (yaml::OperationException const& e) { + LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + return false; + } + + // clear table + clear(); + + __LOCK_TABLE(); + + yaml::Node& peerList = m_rules["peers"]; + + if (peerList.size() == 0U) { + ::LogError(LOG_HOST, "No adj site map peer list defined!"); + m_locked = false; + return false; + } + + for (size_t i = 0; i < peerList.size(); i++) { + AdjPeerMapEntry entry = AdjPeerMapEntry(peerList[i]); + m_adjPeerMap.push_back(entry); + } + + __UNLOCK_TABLE(); + + size_t size = m_adjPeerMap.size(); + if (size == 0U) { + return false; + } + + LogInfoEx(LOG_HOST, "Loaded %lu entries into adjacent site map table", size); + + return true; +} + +/* Saves the table to the passed lookup table file. */ + +bool AdjSiteMapLookup::save() +{ + // Make sure file is valid + if (m_rulesFile.length() <= 0) { + return false; + } + + std::lock_guard lock(m_mutex); + + // New list for our new group voice rules + yaml::Node peerList; + yaml::Node newRules; + + for (auto entry : m_adjPeerMap) { + yaml::Node& gv = peerList.push_back(); + entry.getYaml(gv); + } + + // Set the new rules + newRules["peers"] = peerList; + + // Make sure we actually did stuff right + if (newRules["peers"].size() != m_adjPeerMap.size()) { + LogError(LOG_HOST, "Generated YAML node for group lists did not match loaded map size! (%u != %u)", newRules["peers"].size(), m_adjPeerMap.size()); + return false; + } + + try { + LogMessage(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str()); + yaml::Serialize(newRules, m_rulesFile.c_str()); + LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str()); + } + catch (yaml::OperationException const& e) { + LogError(LOG_HOST, "Cannot save the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + return false; + } + + return true; +} diff --git a/src/common/lookups/AdjSiteMapLookup.h b/src/common/lookups/AdjSiteMapLookup.h new file mode 100644 index 000000000..ca196f6cf --- /dev/null +++ b/src/common/lookups/AdjSiteMapLookup.h @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup lookups_asm Adjacent Site Map Lookups + * @brief Implementation for adjacent site map lookup tables. + * @ingroup lookups + * + * @file AdjSiteMapLookup.h + * @ingroup lookups_asm + * @file AdjSiteMapLookup.cpp + * @ingroup lookups_asm + */ +#if !defined(__ADJ_SITE_MAP_LOOKUP_H__) +#define __ADJ_SITE_MAP_LOOKUP_H__ + +#include "common/Defines.h" +#include "common/lookups/LookupTable.h" +#include "common/yaml/Yaml.h" +#include "common/Utils.h" + +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents an adjacent peer map entry. + * @ingroup lookups_asm + */ + class HOST_SW_API AdjPeerMapEntry { + public: + /** + * @brief Initializes a new instance of the AdjPeerMapEntry class. + */ + AdjPeerMapEntry() : + m_active(false), + m_peerId(0U), + m_neighbors() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the AdjPeerMapEntry class. + * @param node YAML node for this configuration block. + */ + AdjPeerMapEntry(yaml::Node& node) : + AdjPeerMapEntry() + { + m_active = node["active"].as(false); + m_peerId = node["peer_id"].as(0U); + + yaml::Node& neighborList = node["neighbors"]; + if (neighborList.size() > 0U) { + for (size_t i = 0; i < neighborList.size(); i++) { + uint32_t peerId = neighborList[i].as(0U); + m_neighbors.push_back(peerId); + } + } + } + + /** + * @brief Equals operator. Copies this AdjPeerMapEntry to another AdjPeerMapEntry. + * @param data Instance of AdjPeerMapEntry to copy. + */ + virtual AdjPeerMapEntry& operator= (const AdjPeerMapEntry& data) + { + if (this != &data) { + m_active = data.m_active; + m_peerId = data.m_peerId; + m_neighbors = data.m_neighbors; + } + + return *this; + } + + /** + * @brief Gets the count of neighbors. + * @returns uint8_t Total count of peer neighbors. + */ + uint8_t neighborSize() const { return m_neighbors.size(); } + + /** + * @brief Helper to quickly determine if a entry is valid. + * @returns bool True, if entry is valid, otherwise false. + */ + bool isEmpty() const + { + if (m_neighbors.size() > 0U) + return true; + return false; + } + + /** + * @brief Return the YAML structure for this TalkgroupRuleConfig. + * @param[out] node YAML node. + */ + void getYaml(yaml::Node &node) + { + // We have to convert the bools back to strings to pass to the yaml node + node["active"] = __BOOL_STR(m_active); + node["peerId"] = __INT_STR(m_peerId); + + // Get the lists + yaml::Node neighborList; + if (m_neighbors.size() > 0U) { + for (auto neighbor : m_neighbors) { + yaml::Node& newNeighbor = neighborList.push_back(); + newNeighbor = __INT_STR(neighbor); + } + } + } + + public: + /** + * @brief Flag indicating whether the rule is active. + */ + DECLARE_PROPERTY_PLAIN(bool, active); + /** + * @brief Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, peerId); + + /** + * @brief List of neighbor peers. + */ + DECLARE_PROPERTY_PLAIN(std::vector, neighbors); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a threading lookup table class that contains adjacent site map + * information. + * @ingroup lookups_asm + */ + class HOST_SW_API AdjSiteMapLookup : public Thread { + public: + /** + * @brief Initializes a new instance of the AdjSiteMapLookup class. + * @param filename Full-path to the routing rules file. + * @param reloadTime Interval of time to reload the routing rules. + */ + AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime); + /** + * @brief Finalizes a instance of the AdjSiteMapLookup class. + */ + ~AdjSiteMapLookup() override; + + /** + * @brief Thread entry point. This function is provided to run the thread + * for the lookup table. + */ + void entry() override; + + /** + * @brief Stops and unloads this lookup table. + * @param noDestroy Flag indicating the lookup table should remain resident in memory after stopping. + */ + void stop(bool noDestroy = false); + /** + * @brief Reads the lookup table from the specified lookup table file. + * (NOTE: If the reload time for this lookup table is set to 0, a call to stop will also delete the object.) + * @returns bool True, if lookup table was read, otherwise false. + */ + bool read(); + /** + * @brief Reads the lookup table from the specified lookup table file. + * @returns bool True, if lookup table was read, otherwise false. + */ + bool reload() { return load(); } + /** + * @brief Clears all entries from the lookup table. + */ + void clear(); + + /** + * @brief Adds a new entry to the lookup table. + * @param entry Peer map entry. + */ + void addEntry(AdjPeerMapEntry entry); + /** + * @brief Erases an existing entry from the lookup table by the specified unique ID. + * @param id Unique ID to erase. + */ + void eraseEntry(uint32_t id); + /** + * @brief Finds a table entry in this lookup table. + * @param id Unique identifier for table entry. + * @returns AdjPeerMapEntry Table entry. + */ + virtual AdjPeerMapEntry find(uint32_t id); + + /** + * @brief Saves loaded talkgroup rules. + */ + bool commit(); + + /** + * @brief Returns the filename used to load this lookup table. + * @return std::string Full-path to the lookup table file. + */ + const std::string filename() { return m_rulesFile; }; + /** + * @brief Sets the filename used to load this lookup table. + * @param filename Full-path to the routing rules file. + */ + void filename(std::string filename) { m_rulesFile = filename; }; + + /** + * @brief Helper to set the reload time of this lookup table. + * @param reloadTime Lookup time in seconds. + */ + void setReloadTime(uint32_t reloadTime) { m_reloadTime = reloadTime; } + + private: + std::string m_rulesFile; + uint32_t m_reloadTime; + yaml::Node m_rules; + + bool m_stop; + + static std::mutex m_mutex; //! Mutex used for change locking. + static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + + /** + * @brief Loads the table from the passed lookup table file. + * @return True, if lookup table was loaded, otherwise false. + */ + bool load(); + /** + * @brief Saves the table to the passed lookup table file. + * @return True, if lookup table was saved, otherwise false. + */ + bool save(); + + public: + /** + * @brief List of adjacent site map entries. + */ + DECLARE_PROPERTY_PLAIN(std::vector, adjPeerMap); + }; +} // namespace lookups + +#endif // __ADJ_SITE_MAP_LOOKUP_H__ diff --git a/src/common/lookups/IdenTableLookup.cpp b/src/common/lookups/IdenTableLookup.cpp index 0c38016aa..50bb082e2 100644 --- a/src/common/lookups/IdenTableLookup.cpp +++ b/src/common/lookups/IdenTableLookup.cpp @@ -113,22 +113,13 @@ bool IdenTableLookup::load() continue; // tokenize line - std::string next; std::vector parsed; + std::stringstream ss(line); + std::string field; char delim = ','; - for (auto it = line.begin(); it != line.end(); it++) { - if (*it == delim) { - if (!next.empty()) { - parsed.push_back(next); - next.clear(); - } - } - else - next += *it; - } - if (!next.empty()) - parsed.push_back(next); + while (std::getline(ss, field, delim)) + parsed.push_back(field); // ensure we have at least 5 fields if (parsed.size() < 5) { diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index d96d291c0..9215e3b9c 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -69,17 +69,15 @@ void PeerListLookup::clear() /* Adds a new entry to the list. */ -void PeerListLookup::addEntry(uint32_t id, const std::string& alias, const std::string& password, bool peerLink, bool canRequestKeys) +void PeerListLookup::addEntry(uint32_t id, PeerId entry) { - PeerId entry = PeerId(id, alias, password, peerLink, canRequestKeys, false); - __LOCK_TABLE(); 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, alias, password, peerLink, canRequestKeys, false); + _entry = entry; m_table[id] = _entry; } } catch (...) { @@ -117,7 +115,7 @@ PeerId PeerListLookup::find(uint32_t id) try { entry = m_table.at(id); } catch (...) { - entry = PeerId(0U, "", "", false, false, true); + entry = PeerId(0U, "", "", true); } return entry; @@ -208,22 +206,13 @@ bool PeerListLookup::load() continue; // tokenize line - std::string next; std::vector parsed; + std::stringstream ss(line); + std::string field; char delim = ','; - for (char c : line) { - if (c == delim) { - //if (!next.empty()) { - parsed.push_back(next); - next.clear(); - //} - } - else - next += c; - } - if (!next.empty()) - parsed.push_back(next); + while (std::getline(ss, field, delim)) + parsed.push_back(field); // parse tokenized line uint32_t id = ::atoi(parsed[0].c_str()); @@ -243,20 +232,31 @@ bool PeerListLookup::load() if (parsed.size() >= 5) canRequestKeys = ::atoi(parsed[4].c_str()) == 1; + // parse can issue inhibit flag + bool canIssueInhibit = false; + if (parsed.size() >= 6) + canIssueInhibit = ::atoi(parsed[5].c_str()) == 1; + // parse optional password std::string password = ""; if (parsed.size() >= 2) password = parsed[1].c_str(); // load into table - m_table[id] = PeerId(id, alias, password, peerLink, canRequestKeys, false); + PeerId entry = PeerId(id, alias, password, false); + entry.peerLink(peerLink); + entry.canRequestKeys(canRequestKeys); + entry.canIssueInhibit(canIssueInhibit); + + m_table[id] = entry; // log depending on what was loaded LogMessage(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s", id, (!alias.empty() ? (" (" + alias + ")").c_str() : ""), (!password.empty() ? "using unique peer password" : "using master password"), (peerLink) ? ", Peer-Link Enabled" : "", - (canRequestKeys) ? ", Can Request Keys" : ""); + (canRequestKeys) ? ", Can Request Keys" : "", + (canIssueInhibit) ? ", Can Issue Inhibit" : ""); } } @@ -334,6 +334,14 @@ bool PeerListLookup::save() line += "0,"; } + // add canIssueInhibit flag + bool canIssueInhibit = entry.second.canIssueInhibit(); + if (canIssueInhibit) { + line += "1,"; + } else { + line += "0,"; + } + line += "\n"; file << line; lines++; diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index d4ae371ec..5b7dd15b8 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -51,6 +51,7 @@ namespace lookups m_peerPassword(), m_peerLink(false), m_canRequestKeys(false), + m_canIssueInhibit(false), m_peerDefault(false) { /* stub */ @@ -61,16 +62,15 @@ namespace lookups * @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 peerLink lag indicating if the peer participates in peer link and should be sent configuration. - * @param canRequestKeys Flag indicating if the peer can request encryption keys. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. */ - PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool canRequestKeys, bool peerDefault) : + PeerId(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerDefault) : m_peerId(peerId), m_peerAlias(peerAlias), m_peerPassword(peerPassword), - m_peerLink(peerLink), - m_canRequestKeys(canRequestKeys), + m_peerLink(false), + m_canRequestKeys(false), + m_canIssueInhibit(false), m_peerDefault(peerDefault) { /* stub */ @@ -88,6 +88,7 @@ namespace lookups m_peerPassword = data.m_peerPassword; m_peerLink = data.m_peerLink; m_canRequestKeys = data.m_canRequestKeys; + m_canIssueInhibit = data.m_canIssueInhibit; m_peerDefault = data.m_peerDefault; } @@ -100,17 +101,13 @@ namespace lookups * @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 peerLink lag indicating if the peer participates in peer link and should be sent configuration. - * @param canRequestKeys Flag indicating if the peer can request encryption keys. * @param peerDefault Flag indicating this is a "default" (i.e. undefined) peer. */ - void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerLink, bool canRequestKeys, bool peerDefault) + void set(uint32_t peerId, const std::string& peerAlias, const std::string& peerPassword, bool peerDefault) { m_peerId = peerId; m_peerAlias = peerAlias; m_peerPassword = peerPassword; - m_peerLink = peerLink; - m_canRequestKeys = canRequestKeys; m_peerDefault = peerDefault; } @@ -135,6 +132,10 @@ namespace lookups * @brief Flag indicating if the peer can request encryption keys. */ DECLARE_PROPERTY_PLAIN(bool, canRequestKeys); + /** + * @brief Flag indicating if the peer can issue inhibit/uninhibit packets. + */ + DECLARE_PROPERTY_PLAIN(bool, canIssueInhibit); /** * @brief Flag indicating if the peer is default. */ @@ -167,15 +168,13 @@ namespace lookups /** * @brief Adds a new entry to the list. - * @param peerId Unique peer ID to add. - * @param password Per Peer Password. - * @param peerLink Flag indicating this peer will participate in peer link and should be sent configuration. - * @param canRequestKeys Flag indicating if the peer can request encryption keys. + * @param id Unique peer ID to add. + * @param entry Peer ID entry to add. */ - void addEntry(uint32_t id, const std::string& alias = "", const std::string& password = "", bool peerLink = false, bool canRequestKeys = false); + void addEntry(uint32_t id, PeerId entry); /** * @brief Removes an existing entry from the list. - * @param peerId Unique peer ID to remove. + * @param id Unique peer ID to remove. */ void eraseEntry(uint32_t id); /** diff --git a/src/common/lookups/RadioIdLookup.cpp b/src/common/lookups/RadioIdLookup.cpp index 55c168899..8cb5fc129 100644 --- a/src/common/lookups/RadioIdLookup.cpp +++ b/src/common/lookups/RadioIdLookup.cpp @@ -192,22 +192,13 @@ bool RadioIdLookup::load() continue; // tokenize line - std::string next; std::vector parsed; + std::stringstream ss(line); + std::string field; char delim = ','; - for (char c : line) { - if (c == delim) { - if (!next.empty()) { - parsed.push_back(next); - next.clear(); - } - } - else - next += c; - } - if (!next.empty()) - parsed.push_back(next); + while (std::getline(ss, field, delim)) + parsed.push_back(field); // ensure we have at least 2 fields if (parsed.size() < 2) { @@ -232,6 +223,7 @@ bool RadioIdLookup::load() } m_table[id] = RadioId(radioEnabled, false, alias, ipAddress); + //::LogInfoEx(LOG_HOST, "Radio NAME: %s RID: %u ENABLED: %u IPADDR: %s", alias.c_str(), id, radioEnabled, ipAddress.c_str()); } } diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index 90fa01484..5e6d175cd 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -420,7 +420,7 @@ bool TalkgroupRulesLookup::save() LogDebug(LOG_HOST, "Saved TGID config file to %s", m_rulesFile.c_str()); } catch (yaml::OperationException const& e) { - LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + LogError(LOG_HOST, "Cannot save the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); return false; } diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index ead726ed1..b3f6e2298 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -10,6 +10,7 @@ * */ #include "Defines.h" +#include "common/analog/AnalogDefines.h" #include "common/dmr/DMRDefines.h" #include "common/p25/P25Defines.h" #include "common/nxdn/NXDNDefines.h" @@ -53,10 +54,12 @@ BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, b m_rxDMRData(NET_RING_BUF_SIZE, "DMR Net Buffer"), m_rxP25Data(NET_RING_BUF_SIZE, "P25 Net Buffer"), m_rxNXDNData(NET_RING_BUF_SIZE, "NXDN Net Buffer"), + m_rxAnalogData(NET_RING_BUF_SIZE, "Analog Net Buffer"), m_random(), m_dmrStreamId(nullptr), m_p25StreamId(0U), m_nxdnStreamId(0U), + m_analogStreamId(0U), m_pktSeq(0U), m_audio() { @@ -74,6 +77,7 @@ BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, b m_dmrStreamId[1U] = createStreamId(); m_p25StreamId = createStreamId(); m_nxdnStreamId = createStreamId(); + m_analogStreamId = createStreamId(); } /* Finalizes a instance of the BaseNetwork class. */ @@ -139,7 +143,7 @@ bool BaseNetwork::writeKeyReq(const uint16_t kId, const uint8_t algId) modifyKeyCmd.encode(buffer + 11U); - //Utils::dump("writeKeyReq", buffer, modifyKeyCmd.length() + 11U); + //Utils::dump("BaseNetwork::writeKeyReq(), KMM Buffer", buffer, modifyKeyCmd.length() + 11U); return writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, buffer, modifyKeyCmd.length() + 11U, RTP_END_OF_CALL_SEQ, 0U); } @@ -346,6 +350,15 @@ void BaseNetwork::resetNXDN() m_rxNXDNData.clear(); } +/* Resets the analog ring buffer. */ + +void BaseNetwork::resetAnalog() +{ + m_analogStreamId = createStreamId(); + m_pktSeq = 0U; + m_rxAnalogData.clear(); +} + /* Gets the current DMR stream ID. */ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const @@ -363,10 +376,12 @@ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const /* Helper to send a data message to the master. */ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, - bool queueOnly, bool useAlternatePort, uint32_t peerId) + bool queueOnly, bool useAlternatePort, uint32_t peerId, uint32_t ssrc) { if (peerId == 0U) peerId = m_peerId; + if (ssrc == 0U) + ssrc = m_peerId; if (useAlternatePort) { sockaddr_storage addr; @@ -377,14 +392,14 @@ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data if (udp::Socket::lookup(address, port, addr, addrLen) == 0) { if (!queueOnly) - return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); else m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, addr, addrLen); } } else { if (!queueOnly) - return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen); else m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); } @@ -500,6 +515,11 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength) return nullptr; } + if (length == 254U) { + m_rxP25Data.get(&length, 1U); // read the next byte for the actual length + length += 254U; // a packet length of 254 is a special case for P25 frames, so we need to add the 254 to the length + } + UInt8Array buffer; frameLength = length; buffer = std::unique_ptr(new uint8_t[length]); @@ -511,7 +531,8 @@ UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength) /* Writes P25 LDU1 frame data to the network. */ -bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) +bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + P25DEF::FrameType::E frameType, uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -523,7 +544,7 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType); + UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType, controlByte); if (message == nullptr) { return false; } @@ -533,7 +554,8 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS /* Writes P25 LDU2 frame data to the network. */ -bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -545,7 +567,7 @@ bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data); + UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data, controlByte); if (message == nullptr) { return false; } @@ -560,9 +582,7 @@ bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSp if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - bool resetSeq = false; if (m_p25StreamId == 0U) { - resetSeq = true; m_p25StreamId = createStreamId(); } @@ -653,6 +673,108 @@ bool BaseNetwork::hasP25Data() const return true; } +/* Helper to validate a P25 network frame length. */ + +bool BaseNetwork::validateP25FrameLength(uint8_t& frameLength, uint32_t len, const P25DEF::DUID::E duid) +{ + using namespace p25::defines; + + // P25 network frame should never be less then 24 bytes + if (len < 24U) { + LogError(LOG_NET, "malformed P25 packet, len < 24, shouldn't happen"); + return false; + } + + // frame length should never be 0 + if (frameLength == 0U) { + LogError(LOG_NET, "DUID $%02X, sent with frame length of 0?", duid); + return false; + } + + // frame length should never be larger then the network packet length + if (frameLength > len) { + LogError(LOG_NET, "malformed P25 packet, frameLength > len (%u > %u), shouldn't happen", frameLength, len); + return false; + } + + // validate frame length, because P25 has variable network frame lengths we should be validating + // the actual frame length to ensure we don't have buffer overflow vulnerabilities + switch (duid) { + case DUID::HDU: + // HDU's aren't actually ever sent over the network, they are packaged with the first LDU1 for the + // initating superframe + return false; + case DUID::TDU: + // TDUs are sent with the P25 message header only + if (frameLength != 24U) { + LogError(LOG_NET, P25_TDU_STR ", malformed TDU, discard."); + break; + } + break; + + case DUID::LDU1: + // LDU1 with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_LDU1_STR ", malformed LDU1, discard."); + break; + } + break; + case DUID::VSELP1: + // VSELP1 frames aren't actually sent over the network right now + return false; + + case DUID::TSDU: + // oversized TSDU -- this shouldn't happen, truncate and only handle the size of the TSDU frame length + if (frameLength > P25_TSDU_FRAME_LENGTH_BYTES) + frameLength = P25_TSDU_FRAME_LENGTH_BYTES; + + // TSDU with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_TSDU_STR ", malformed TSDU, discard."); + break; + } + break; + + case DUID::LDU2: + // LDU2 with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_LDU2_STR ", malformed LDU2, discard."); + break; + } + break; + case DUID::VSELP2: + // VSELP2 frames aren't actually sent over the network right now + return false; + + case DUID::PDU: + // PDU with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_PDU_STR ", malformed PDU, discard."); + break; + } + break; + + + case DUID::TDULC: + // oversized TDULC -- this shouldn't happen, truncate and only handle the size of the TSDU frame length + if (frameLength > P25_TDULC_FRAME_LENGTH_BYTES) + frameLength = P25_TDULC_FRAME_LENGTH_BYTES; + + // TDULC with message header only, this shouldn't happen + if (frameLength <= 24U) { + LogError(LOG_NET, P25_TSDU_STR ", malformed TDULC, discard."); + break; + } + break; + + default: + LogError(LOG_NET, "unsupported DUID $%02X", duid); + return false; + } + + return true; +} + /* Reads NXDN raw frame data from the NXDN ring buffer. */ UInt8Array BaseNetwork::readNXDN(bool& ret, uint32_t& frameLength) @@ -725,6 +847,91 @@ bool BaseNetwork::hasNXDNData() const return true; } +/* Reads analog raw frame data from the analog ring buffer. */ + +UInt8Array BaseNetwork::readAnalog(bool& ret, uint32_t& frameLength) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return nullptr; + + ret = true; + if (m_rxAnalogData.isEmpty()) { + ret = false; + return nullptr; + } + + uint8_t length = 0U; + m_rxAnalogData.get(&length, 1U); + if (length == 0U) { + ret = false; + return nullptr; + } + + if (length < 254U) { + // if the length is less than 254, the analog packet is malformed, analog packets should never be less than 254 bytes + LogError(LOG_NET, "malformed analog packet, length < 254 (%u), shouldn't happen", length); + ret = false; + return nullptr; + } + + if (length == 254U) { + m_rxAnalogData.get(&length, 1U); // read the next byte for the actual length + length += 254U; // a packet length of 254 is a special case for P25 frames, so we need to add the 254 to the length + } + + UInt8Array buffer; + frameLength = length; + buffer = std::unique_ptr(new uint8_t[length]); + ::memset(buffer.get(), 0x00U, length); + m_rxAnalogData.get(buffer.get(), length); + + return buffer; +} + +/* Writes analog frame data to the network. */ + +bool BaseNetwork::writeAnalog(const analog::data::NetData& data, bool noSequence) +{ + using namespace analog::defines; + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + AudioFrameType::E frameType = data.getFrameType(); + + bool resetSeq = false; + if (m_analogStreamId == 0U) { + resetSeq = true; + m_analogStreamId = createStreamId(); + } + + uint32_t messageLength = 0U; + UInt8Array message = createAnalog_Message(messageLength, m_analogStreamId, data); + if (message == nullptr) { + return false; + } + + uint16_t seq = pktSeq(resetSeq); + if (frameType == AudioFrameType::TERMINATOR) { + seq = RTP_END_OF_CALL_SEQ; + } + + if (noSequence) { + seq = RTP_END_OF_CALL_SEQ; + } + + return writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, message.get(), messageLength, seq, m_analogStreamId); +} + +/* Helper to test if the analog ring buffer has data. */ + +bool BaseNetwork::hasAnalogData() const +{ + if (m_rxAnalogData.isEmpty()) + return false; + + return true; +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- @@ -802,7 +1009,7 @@ UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t strea data.getData(buffer + 20U); if (m_debug) - Utils::dump(1U, "Network Message, DMR", buffer, (DMR_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createDMR_Message(), Message", buffer, (DMR_PACKET_LENGTH + PACKET_PAD)); length = (DMR_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -811,7 +1018,7 @@ UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t strea /* Creates an P25 frame message header. */ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - p25::defines::FrameType::E frameType) + p25::defines::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; assert(buffer != nullptr); @@ -830,7 +1037,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du uint16_t sysId = control.getSiteData().sysId(); // System ID SET_UINT16(sysId, buffer, 11U); - buffer[14U] = 0U; // Control Bits + buffer[14U] = controlByte; // Control Bits buffer[15U] = control.getMFId(); // MFId @@ -859,7 +1066,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du control.getMI(mi); if (m_debug) { - Utils::dump(1U, "P25 HDU MI written to network", mi, MI_LENGTH_BYTES); + Utils::dump(1U, "BaseNetwork::createP25_Message(), HDU MI", mi, MI_LENGTH_BYTES); } for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { @@ -871,7 +1078,7 @@ void BaseNetwork::createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E du /* Creates an P25 LDU1 frame message. */ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType) + const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -883,7 +1090,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -937,7 +1144,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_LDU1Message(), Message, LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -946,7 +1153,7 @@ UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::L /* Creates an P25 LDU2 frame message. */ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data) + const uint8_t* data, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -958,7 +1165,7 @@ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::L ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -1012,7 +1219,7 @@ UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::L buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_LDU2Message(), Message, LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1033,7 +1240,7 @@ UInt8Array BaseNetwork::createP25_TDUMessage(uint32_t& length, const p25::lc::LC buffer[23U] = MSG_HDR_SIZE; if (m_debug) - Utils::dump(1U, "Network Message, P25 TDU", buffer, (MSG_HDR_SIZE + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_TDUMessage(), Message, TDU", buffer, (MSG_HDR_SIZE + PACKET_PAD)); length = (MSG_HDR_SIZE + PACKET_PAD); return UInt8Array(buffer); @@ -1054,15 +1261,12 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L createP25_MessageHdr(buffer, DUID::TSDU, control, lsd, FrameType::TERMINATOR); // pack raw P25 TSDU bytes - uint32_t count = MSG_HDR_SIZE; - ::memcpy(buffer + 24U, data, P25_TSDU_FRAME_LENGTH_BYTES); - count += P25_TSDU_FRAME_LENGTH_BYTES; - buffer[23U] = count; + buffer[23U] = P25_TSDU_FRAME_LENGTH_BYTES; if (m_debug) - Utils::dump(1U, "Network Message, P25 TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_TSDUMessage(), Message, TDSU", buffer, (P25_TSDU_PACKET_LENGTH + PACKET_PAD)); length = (P25_TSDU_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1083,15 +1287,12 @@ UInt8Array BaseNetwork::createP25_TDULCMessage(uint32_t& length, const p25::lc:: createP25_MessageHdr(buffer, DUID::TDULC, control, lsd, FrameType::TERMINATOR); // 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; + buffer[23U] = P25_TDULC_FRAME_LENGTH_BYTES; if (m_debug) - Utils::dump(1U, "Network Message, P25 TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_TDULCMessage(), Message, TDULC", buffer, (P25_TDULC_PACKET_LENGTH + PACKET_PAD)); length = (P25_TDULC_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -1139,7 +1340,7 @@ UInt8Array BaseNetwork::createP25_PDUMessage(uint32_t& length, const p25::data:: buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 PDU", buffer, (count + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createP25_PDUMessage(), Message, PDU", buffer, (count + PACKET_PAD)); length = (count + PACKET_PAD); return UInt8Array(buffer); @@ -1151,8 +1352,8 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC { assert(data != nullptr); - uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; - ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + uint8_t* buffer = new uint8_t[NXDN_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, NXDN_PACKET_LENGTH + PACKET_PAD); // construct NXDN message header ::memcpy(buffer + 0U, TAG_NXDN_DATA, 4U); @@ -1178,8 +1379,43 @@ UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTC buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, NXDN", buffer, (count + PACKET_PAD)); + Utils::dump(1U, "BaseNetwork::createNXDN_Message(), Message", buffer, (NXDN_PACKET_LENGTH + PACKET_PAD)); - length = (count + PACKET_PAD); + length = (NXDN_PACKET_LENGTH + PACKET_PAD); + return UInt8Array(buffer); +} + +/* Creates an analog frame message. */ + +UInt8Array BaseNetwork::createAnalog_Message(uint32_t& length, const uint32_t streamId, const analog::data::NetData& data) +{ + using namespace analog::defines; + uint8_t* buffer = new uint8_t[ANALOG_PACKET_LENGTH + PACKET_PAD]; + ::memset(buffer, 0x00U, ANALOG_PACKET_LENGTH + PACKET_PAD); + + // construct analog message header + ::memcpy(buffer + 0U, TAG_ANALOG_DATA, 4U); + + uint32_t srcId = data.getSrcId(); // Source Address + SET_UINT24(srcId, buffer, 5U); + + uint32_t dstId = data.getDstId(); // Target Address + SET_UINT24(dstId, buffer, 8U); + + buffer[14U] = data.getControl(); // Control Bits + + AudioFrameType::E frameType = data.getFrameType(); + buffer[15U] = (uint8_t)frameType; // Audio Frame Type + buffer[15U] |= data.getGroup() ? 0x00U : 0x40U; // Group + + buffer[4U] = data.getSeqNo(); // Sequence Number + + // pack raw audio message bytes + data.getAudio(buffer + 20U); + + if (m_debug) + Utils::dump(1U, "BaseNetwork::createAnalog_Message(), Message", buffer, (ANALOG_PACKET_LENGTH + PACKET_PAD)); + + length = (ANALOG_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); } diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index ccb2092bc..79cdb8f3b 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -25,6 +25,7 @@ #define __BASE_NETWORK_H__ #include "common/Defines.h" +#include "common/analog/data/NetData.h" #include "common/dmr/data/NetData.h" #include "common/p25/data/DataHeader.h" #include "common/p25/data/LowSpeedData.h" @@ -52,6 +53,7 @@ #define TAG_DMR_DATA "DMRD" #define TAG_P25_DATA "P25D" #define TAG_NXDN_DATA "NXDD" +#define TAG_ANALOG_DATA "ANOD" #define TAG_REPEATER_LOGIN "RPTL" #define TAG_REPEATER_AUTH "RPTK" @@ -88,6 +90,8 @@ namespace network const uint32_t P25_LDU2_PACKET_LENGTH = 181U; // 24 byte header + DFSI data + 1 byte frame type const uint32_t P25_TSDU_PACKET_LENGTH = 69U; // 24 byte header + TSDU data const uint32_t P25_TDULC_PACKET_LENGTH = 78U; // 24 byte header + TDULC data + const uint32_t NXDN_PACKET_LENGTH = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer + const uint32_t ANALOG_PACKET_LENGTH = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer /** * @brief Network Peer Connection Status @@ -131,6 +135,19 @@ namespace network NET_CONN_NAK_INVALID = 0xFFFF //! Invalid }; + /** + * @brief Network Control Enumerations + * @note These values are used typically for terminators to specify specific DVM in-band control operations. + * @ingroup network_core + */ + enum CONTROL_BYTE { + NET_CTRL_GRANT_DEMAND = 0x80U, //! Grant Demand + NET_CTRL_GRANT_DENIAL = 0x40U, //! Grant Denial + NET_CTRL_SWITCH_OVER = 0x20U, //! Call Source RID Switch Over + NET_CTRL_GRANT_ENCRYPT = 0x08U, //! Grant Encrypt + NET_CTRL_U2U = 0x01U, //! Unit-to-Unit + }; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -297,6 +314,10 @@ namespace network * @brief Resets the NXDN ring buffer. */ virtual void resetNXDN(); + /** + * @brief Resets the analog ring buffer. + */ + virtual void resetAnalog(); /** * @brief Gets the current DMR stream ID. @@ -314,6 +335,11 @@ namespace network * @return uint32_t Stream ID. */ uint32_t getNXDNStreamId() const { return m_nxdnStreamId; } + /** + * @brief Gets the current analog stream ID. + * @return uint32_t Stream ID. + */ + uint32_t getAnalogStreamId() const { return m_analogStreamId; } /** * @brief Helper to send a data message to the master. @@ -325,10 +351,12 @@ namespace network * @param queueOnly Flag indicating this message should be queued instead of send immediately. * @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics). * @param peerId If non-zero, overrides the peer ID sent in the packet to the master. + * @param ssrc If non-zero, overrides the RTP synchronization source ID sent in the packet to the master. * @returns bool True, if message was sent, otherwise false. */ bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, - uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U); + uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U, + uint32_t ssrc = 0U); // Digital Mobile Radio /** @@ -366,18 +394,21 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - p25::defines::FrameType::E frameType); + P25DEF::FrameType::E frameType, uint8_t controlByte = 0U); /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ - virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte = 0U); /** * @brief Writes P25 TDU frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. @@ -418,6 +449,14 @@ namespace network */ bool hasP25Data() const; + /** + * @brief Helper to validate a P25 network frame length. + * @param frameLength P25 encapsulated frame length. + * @param len Network packet length. + * @return bool True, if validated, otherwise false. + */ + bool validateP25FrameLength(uint8_t& frameLength, const uint32_t len, const P25DEF::DUID::E duid); + // Next Generation Digital Narrowband /** * @brief Reads NXDN raw frame data from the NXDN ring buffer. @@ -442,6 +481,28 @@ namespace network */ bool hasNXDNData() const; + // Analog Audio + /** + * @brief Reads analog MuLaw audio frame data from the analog ring buffer. + * @param[out] ret Flag indicating whether or not data was received. + * @param[out] frameLength Length in bytes of received frame. + * @returns UInt8Array Buffer containing received frame. + */ + virtual UInt8Array readAnalog(bool& ret, uint32_t& frameLength); + /** + * @brief Writes analog MuLaw audio frame data to the network. + * @param[in] data Instance of the analog::data::NetData class containing the analog message. + * @param noSequence Flag indicating the message should be sent with no RTP sequence (65535). + * @returns bool True, if message was sent, otherwise false. + */ + virtual bool writeAnalog(const analog::data::NetData& data, bool noSequence = false); + + /** + * @brief Helper to test if the analog ring buffer has data. + * @returns bool True, if the network analog ring buffer has data, otherwise false. + */ + bool hasAnalogData() const; + public: /** * @brief Gets the peer ID of the network. @@ -489,12 +550,14 @@ namespace network RingBuffer m_rxDMRData; RingBuffer m_rxP25Data; RingBuffer m_rxNXDNData; + RingBuffer m_rxAnalogData; std::mt19937 m_random; uint32_t* m_dmrStreamId; uint32_t m_p25StreamId; uint32_t m_nxdnStreamId; + uint32_t m_analogStreamId; /** * @brief Helper to update the RTP packet sequence. @@ -529,7 +592,7 @@ namespace network * | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * - * The data starting at offset 20 for 33 bytes if the raw DMR frame. + * The data starting at offset 20 for 33 bytes of the raw DMR frame. * * DMR frame message has 2 trailing bytes: * @@ -591,9 +654,10 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. */ void createP25_MessageHdr(uint8_t* buffer, p25::defines::DUID::E duid, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT); + p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT, uint8_t controlByte = 0U); /** * @brief Creates an P25 LDU1 frame message. @@ -606,10 +670,11 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType); + const uint8_t* data, p25::defines::FrameType::E frameType, uint8_t controlByte = 0U); /** * @brief Creates an P25 LDU2 frame message. * @@ -620,10 +685,11 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data); + const uint8_t* data, uint8_t controlByte = 0U); /** * @brief Creates an P25 TDU frame message. @@ -700,7 +766,7 @@ namespace network */ UInt8Array createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, const uint8_t currentBlock, const uint8_t* data, const uint32_t len); - + /** * @brief Creates an NXDN frame message. * \code{.unparsed} @@ -732,7 +798,36 @@ namespace network * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createNXDN_Message(uint32_t& length, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); - + + /** + * @brief Creates an analog frame message. + * \code{.unparsed} + * Below is the representation of the data layout for the analog frame + * message header. The header is 20 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (ANOD) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Seq No. | Source ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination ID | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | Control Flags | R | Data Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The data starting at offset 20 for 320 bytes of the raw analog frame. + * \endcode + * @param[out] length Length of network message buffer. + * @param streamId Stream ID. + * @param data Instance of the analog::data::Data class containing the analog message. + * @returns UInt8Array Buffer containing the built network message. + */ + UInt8Array createAnalog_Message(uint32_t& length, const uint32_t streamId, const analog::data::NetData& data); + private: uint16_t m_pktSeq; diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 1d5b04228..0ca7eeabc 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -24,6 +24,12 @@ using namespace network::frame; #include #include +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::vector FrameQueue::m_streamTimestamps; + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -32,10 +38,7 @@ using namespace network::frame; FrameQueue::FrameQueue(udp::Socket* socket, uint32_t peerId, bool debug) : RawFrameQueue(socket, debug), m_peerId(peerId), -#if defined(_WIN32) - m_streamTSMtx(), -#endif // defined(_WIN32) - m_streamTimestamps() + m_timestampMtx() { assert(peerId < 999999999U); } @@ -67,7 +70,7 @@ UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint3 if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Packet", buffer, length); + Utils::dump(1U, "FrameQueue::read(), Network Packet", buffer, length); m_failedReadCnt = 0U; @@ -211,6 +214,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ void FrameQueue::clearTimestamps() { + std::lock_guard lock(m_timestampMtx); m_streamTimestamps.clear(); } @@ -218,6 +222,63 @@ void FrameQueue::clearTimestamps() // Private Class Members // --------------------------------------------------------------------------- +/* Search for a timestamp entry by stream ID. */ + +FrameQueue::Timestamp* FrameQueue::findTimestamp(uint32_t streamId) +{ + std::lock_guard lock(m_timestampMtx); + for (size_t i = 0; i < m_streamTimestamps.size(); i++) { + if (m_streamTimestamps[i].streamId == streamId) + return &m_streamTimestamps[i]; + } + + return nullptr; +} + +/* Insert a timestamp for a stream ID. */ + +void FrameQueue::insertTimestamp(uint32_t streamId, uint32_t timestamp) +{ + std::lock_guard lock(m_timestampMtx); + if (streamId == 0U || timestamp == INVALID_TS) { + LogError(LOG_NET, "FrameQueue::insertTimestamp(), invalid streamId or timestamp"); + return; + } + + Timestamp entry = { streamId, timestamp }; + m_streamTimestamps.push_back(entry); +} + +/* Update a timestamp for a stream ID. */ + +void FrameQueue::updateTimestamp(uint32_t streamId, uint32_t timestamp) +{ + std::lock_guard lock(m_timestampMtx); + if (streamId == 0U || timestamp == INVALID_TS) { + LogError(LOG_NET, "FrameQueue::updateTimestamp(), invalid streamId or timestamp"); + return; + } + + // find the timestamp entry and update it + for (size_t i = 0; i < m_streamTimestamps.size(); i++) { + if (m_streamTimestamps[i].streamId == streamId) { + m_streamTimestamps[i].timestamp = timestamp; + break; + } + } +} + +/* Erase a timestamp for a stream ID. */ + +void FrameQueue::eraseTimestamp(uint32_t streamId) +{ + std::lock_guard lock(m_timestampMtx); + m_streamTimestamps.erase( + std::remove_if(m_streamTimestamps.begin(), m_streamTimestamps.end(), + [streamId](const Timestamp& entry) { return entry.streamId == streamId; }), + m_streamTimestamps.end()); +} + /* Generate RTP message for the frame queue. */ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, @@ -234,25 +295,17 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui uint32_t timestamp = INVALID_TS; if (streamId != 0U) { -#if defined(_WIN32) - std::lock_guard lock(m_streamTSMtx); -#else - m_streamTimestamps.lock(false); -#endif // defined(_WIN32) - auto entry = m_streamTimestamps.find(streamId); - if (entry != m_streamTimestamps.end()) { - timestamp = entry->second; + auto entry = findTimestamp(streamId); + if (entry != nullptr) { + timestamp = entry->timestamp; } if (timestamp != INVALID_TS) { timestamp += (RTP_GENERIC_CLOCK_RATE / 133); if (m_debug) LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, previous TS = %u, TS = %u, rtpSeq = %u", streamId, m_streamTimestamps[streamId], timestamp, rtpSeq); - m_streamTimestamps[streamId] = timestamp; + updateTimestamp(streamId, timestamp); } -#if !defined(_WIN32) - m_streamTimestamps.unlock(); -#endif // defined(_WIN32) } uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES + length; @@ -274,34 +327,18 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui timestamp = (uint32_t)system_clock::ntp::now(); header.setTimestamp(timestamp); -#if defined(_WIN32) - std::lock_guard lock(m_streamTSMtx); - m_streamTimestamps.insert({ streamId, timestamp }); -#else - m_streamTimestamps.insert(streamId, timestamp); -#endif // defined(_WIN32) + insertTimestamp(streamId, timestamp); } header.encode(buffer); if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) { -#if defined(_WIN32) - std::lock_guard lock(m_streamTSMtx); -#else - m_streamTimestamps.lock(false); -#endif // defined(_WIN32) - auto entry = m_streamTimestamps.find(streamId); - if (entry != m_streamTimestamps.end()) { + auto entry = findTimestamp(streamId); + if (entry != nullptr) { if (m_debug) LogDebugEx(LOG_NET, "FrameQueue::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq); -#if !defined(_WIN32) - m_streamTimestamps.unlock(); -#endif // defined(_WIN32) - m_streamTimestamps.erase(streamId); + eraseTimestamp(streamId); } -#if !defined(_WIN32) - m_streamTimestamps.unlock(); -#endif // defined(_WIN32) } RTPFNEHeader fneHeader = RTPFNEHeader(); @@ -318,7 +355,7 @@ uint8_t* FrameQueue::generateMessage(const uint8_t* message, uint32_t length, ui ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES, message, length); if (m_debug) - Utils::dump(1U, "FrameQueue::generateMessage() Buffered Message", buffer, bufferLen); + Utils::dump(1U, "FrameQueue::generateMessage(), Buffered Message", buffer, bufferLen); if (outBufferLen != nullptr) { *outBufferLen = bufferLen; diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index 041863f14..b4411ee0e 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -17,17 +17,21 @@ #define __FRAME_QUEUE_H__ #include "common/Defines.h" -#include "common/concurrent/unordered_map.h" #include "common/network/RTPHeader.h" #include "common/network/RTPFNEHeader.h" #include "common/network/RawFrameQueue.h" +#include +#include + namespace network { // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- - + + const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; + const uint8_t DVM_RTP_PAYLOAD_TYPE = 0x56U; // --------------------------------------------------------------------------- @@ -41,6 +45,11 @@ namespace network class HOST_SW_API FrameQueue : public RawFrameQueue { public: typedef std::pair OpcodePair; public: + typedef struct { + uint32_t streamId; + uint32_t timestamp; + } Timestamp; + auto operator=(FrameQueue&) -> FrameQueue& = delete; auto operator=(FrameQueue&&) -> FrameQueue& = delete; FrameQueue(FrameQueue&) = delete; @@ -115,12 +124,33 @@ namespace network private: uint32_t m_peerId; -#if defined(_WIN32) - std::mutex m_streamTSMtx; - std::unordered_map m_streamTimestamps; -#else - concurrent::unordered_map m_streamTimestamps; -#endif // defined(_WIN32) + std::mutex m_timestampMtx; + + static std::vector m_streamTimestamps; + + /** + * @brief Search for a timestamp entry by stream ID. + * @param streamId Stream ID to find. + * @return Timestamp* Table entry. + */ + Timestamp* findTimestamp(uint32_t streamId); + /** + * @brief Insert a timestamp for a stream ID. + * @param streamId Stream ID. + * @param timestamp Timestamp. + */ + void insertTimestamp(uint32_t streamId, uint32_t timestamp); + /** + * @brief Update a timestamp for a stream ID. + * @param streamId Stream ID. + * @param timestamp Timestamp. + */ + void updateTimestamp(uint32_t streamId, uint32_t timestamp); + /** + * @brief Erase a timestamp for a stream ID. + * @param streamId Stream ID. + */ + void eraseTimestamp(uint32_t streamId); /** * @brief Generate RTP message for the frame queue. diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 7581c239a..fee310144 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -9,8 +9,6 @@ */ #include "Defines.h" #include "common/edac/SHA256.h" -#include "common/network/RTPHeader.h" -#include "common/network/RTPFNEHeader.h" #include "common/network/json/json.h" #include "common/p25/kmm/KMMFactory.h" #include "common/Log.h" @@ -27,6 +25,7 @@ using namespace network; // Constants // --------------------------------------------------------------------------- +#define MAX_RETRY_BEFORE_RECONNECT 4U #define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds // --------------------------------------------------------------------------- @@ -36,7 +35,8 @@ using namespace network; /* Initializes a new instance of the Network class. */ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, + bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : BaseNetwork(peerId, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, localPort), m_pktLastSeq(0U), m_address(address), @@ -46,12 +46,14 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_dmrEnabled(dmr), m_p25Enabled(p25), m_nxdnEnabled(nxdn), + m_analogEnabled(analog), m_updateLookup(updateLookup), m_saveLookup(saveLookup), m_ridLookup(nullptr), m_tidLookup(nullptr), m_salt(nullptr), m_retryTimer(1000U, 10U), + m_retryCount(0U), m_timeoutTimer(1000U, MAX_PEER_PING_TIME), m_pktSeq(0U), m_loginStreamId(0U), @@ -65,6 +67,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_dmrInCallCallback(nullptr), m_p25InCallCallback(nullptr), m_nxdnInCallCallback(nullptr), + m_analogInCallCallback(nullptr), m_keyRespCallback(nullptr) { assert(!address.empty()); @@ -78,6 +81,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_rxDMRStreamId[1U] = 0U; m_rxP25StreamId = 0U; m_rxNXDNStreamId = 0U; + m_rxAnalogStreamId = 0U; m_metadata = new PeerMetadata(); } @@ -122,6 +126,14 @@ void Network::resetNXDN() m_rxNXDNStreamId = 0U; } +/* Resets the analog ring buffer. */ + +void Network::resetAnalog() +{ + BaseNetwork::resetAnalog(); + m_rxAnalogStreamId = 0U; +} + /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) @@ -175,11 +187,23 @@ void Network::clock(uint32_t ms) m_retryTimer.clock(ms); if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { if (m_enabled) { + if (m_retryCount > MAX_RETRY_BEFORE_RECONNECT) { + m_retryCount = 0U; + LogError(LOG_NET, "PEER %u connection to the master has timed out, retrying connection, remotePeerId = %u", m_peerId, m_remotePeerId); + + close(); + open(); + + m_retryTimer.start(); + return; + } + bool ret = m_socket->open(m_addr.ss_family); if (ret) { ret = writeLogin(); if (!ret) { m_retryTimer.start(); + m_retryCount++; return; } @@ -189,6 +213,7 @@ void Network::clock(uint32_t ms) } m_retryTimer.start(); + m_retryCount++; } return; @@ -236,16 +261,10 @@ void Network::clock(uint32_t ms) } if (m_debug) { - LogDebugEx(LOG_NET, "Network::clock()", "RTP, peerId = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSequence(), + LogDebugEx(LOG_NET, "Network::clock()", "RTP, peerId = %u, ssrc = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSSRC(), rtpHeader.getSequence(), fneHeader.getStreamId(), fneHeader.getFunction(), fneHeader.getSubFunction()); } - // ensure the RTP synchronization source ID matches the FNE peer ID - if (m_remotePeerId != 0U && rtpHeader.getSSRC() != m_remotePeerId) { - LogWarning(LOG_NET, "RTP header and traffic session do not agree on remote peer ID? %u != %u", rtpHeader.getSSRC(), m_remotePeerId); - // should this be a fatal error? - } - // is this RTP packet destined for us? uint32_t peerId = fneHeader.getPeerId(); if ((m_peerId != peerId) && !m_promiscuousPeer) { @@ -273,7 +292,7 @@ void Network::clock(uint32_t ms) // are protocol messages being user handled? if (m_userHandleProtocol) { userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, - buffer.get(), length, fneHeader.getStreamId()); + buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader); break; } @@ -322,8 +341,8 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length); - if (length > 255) + Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer.get(), length); + if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD)) LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); uint8_t len = length; @@ -374,12 +393,21 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length); - if (length > 255) + Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length); + if (length > 512) LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + // P25 frames can be up to 512 bytes, but we need to handle the case where the frame is larger than 255 bytes uint8_t len = length; - m_rxP25Data.addData(&len, 1U); + if (length > 254) { + len = 254U; + m_rxP25Data.addData(&len, 1U); + len = length - 254U; + m_rxP25Data.addData(&len, 1U); + } else { + m_rxP25Data.addData(&len, 1U); + } + m_rxP25Data.addData(buffer.get(), len); } } @@ -426,8 +454,8 @@ void Network::clock(uint32_t ms) } if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length); - if (length > 255) + Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer.get(), length); + if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD)) LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); uint8_t len = length; @@ -437,6 +465,66 @@ void Network::clock(uint32_t ms) } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated Analog data frame + { + if (m_enabled && m_analogEnabled) { + if (m_debug) { + LogDebug(LOG_NET, "Analog, peer = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, length, rtpHeader.getSequence(), streamId); + } + + if (m_promiscuousPeer) { + m_rxAnalogStreamId = streamId; + m_pktLastSeq = m_pktSeq; + } + else { + if (m_rxAnalogStreamId == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxAnalogStreamId = 0U; + } + else { + m_rxAnalogStreamId = streamId; + } + + m_pktLastSeq = m_pktSeq; + } + else { + if (m_rxAnalogStreamId == streamId) { + if (m_pktSeq != 0U && m_pktLastSeq != 0U) { + if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { + LogWarning(LOG_NET, "Analog Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } + + m_pktLastSeq = m_pktSeq; + } + + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxAnalogStreamId = 0U; + } + } + } + + if (m_debug) + Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer.get(), length); + if (length < (int)ANALOG_PACKET_LENGTH) { + LogError(LOG_NET, "Analog Stream %u, frame too short? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + } else { + if (length > 512) + LogError(LOG_NET, "Analog Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + + // Analog frames are larger then 254 bytes, but we need to handle the case where the frame is larger than 255 bytes + uint8_t len = 254U; + m_rxAnalogData.addData(&len, 1U); + len = length - 254U; + m_rxAnalogData.addData(&len, 1U); + + m_rxAnalogData.addData(buffer.get(), len); + } + } + } + break; + default: Utils::dump("unknown protocol opcode from the master", buffer.get(), length); break; @@ -452,7 +540,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, WL RID", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, WL RID", buffer.get(), length); if (m_ridLookup != nullptr) { // update RID lists @@ -478,7 +566,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, BL RID", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, BL RID", buffer.get(), length); if (m_ridLookup != nullptr) { // update RID lists @@ -505,7 +593,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, ACTIVE TGS", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, ACTIVE TGS", buffer.get(), length); if (m_tidLookup != nullptr) { // update TGID lists @@ -555,7 +643,7 @@ void Network::clock(uint32_t ms) { if (m_enabled && m_updateLookup) { if (m_debug) - Utils::dump(1U, "Network Received, DEACTIVE TGS", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, DEACTIVE TGS", buffer.get(), length); if (m_tidLookup != nullptr) { // update TGID lists @@ -636,6 +724,19 @@ void Network::clock(uint32_t ms) } } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Analog In-Call Control + { + if (m_enabled && m_nxdnEnabled) { + NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; + uint32_t dstId = GET_UINT24(buffer, 11U); + + // fire off analog in-call callback if we have one + if (m_analogInCallCallback != nullptr) { + m_analogInCallCallback(command, dstId); + } + } + } + break; default: Utils::dump("unknown incall control opcode from the master", buffer.get(), length); @@ -816,7 +917,7 @@ void Network::clock(uint32_t ms) m_timeoutTimer.start(); if (length >= 14) { if (m_debug) - Utils::dump(1U, "Network Received, PONG", buffer.get(), length); + Utils::dump(1U, "Network::clock(), Network Rx, PONG", buffer.get(), length); ulong64_t serverNow = 0U; @@ -838,7 +939,7 @@ void Network::clock(uint32_t ms) break; default: userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, - buffer.get(), length, fneHeader.getStreamId()); + buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader); break; } } @@ -889,15 +990,16 @@ bool Network::open() if (m_debug) LogMessage(LOG_NET, "PEER %u opening network", m_peerId); + m_status = NET_STAT_WAITING_CONNECT; + m_timeoutTimer.start(); + m_retryTimer.start(); + m_retryCount = 0U; + if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) { LogMessage(LOG_NET, "!!! Could not lookup the address of the master!"); return false; } - m_status = NET_STAT_WAITING_CONNECT; - m_timeoutTimer.start(); - m_retryTimer.start(); - return true; } @@ -936,9 +1038,10 @@ void Network::enable(bool enabled) /* User overrideable handler that allows user code to process network packets not handled by this class. */ -void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { - Utils::dump("unknown opcode from the master", data, length); + Utils::dump("Unknown opcode from the master", data, length); } /* Writes login request to the network. */ @@ -954,7 +1057,7 @@ bool Network::writeLogin() SET_UINT32(m_peerId, buffer, 4U); // Peer ID if (m_debug) - Utils::dump(1U, "Network Message, Login", buffer, 8U); + Utils::dump(1U, "Network::writeLogin(), Message, Login", buffer, 8U); m_loginStreamId = createStreamId(); m_remotePeerId = 0U; @@ -987,7 +1090,7 @@ bool Network::writeAuthorisation() delete[] in; if (m_debug) - Utils::dump(1U, "Network Message, Authorisation", out, 40U); + Utils::dump(1U, "Network::writeAuthorisation(), Message, Authorisation", out, 40U); return writeMaster({ NET_FUNC::RPTK, NET_SUBFUNC::NOP }, out, 40U, pktSeq(), m_loginStreamId); } @@ -1048,7 +1151,7 @@ bool Network::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "Network::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 290b77e2e..740bfbc04 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -18,6 +18,8 @@ #include "common/Defines.h" #include "common/network/BaseNetwork.h" +#include "common/network/RTPHeader.h" +#include "common/network/RTPFNEHeader.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/p25/kmm/KeysetItem.h" @@ -90,6 +92,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -97,7 +100,8 @@ namespace network * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, + bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Finalizes a instance of the Network class. */ @@ -116,7 +120,11 @@ namespace network * @brief Resets the NXDN ring buffer. */ void resetNXDN() override; - + /** + * @brief Resets the analog ring buffer. + */ + void resetAnalog() override; + /** * @brief Sets the instances of the Radio ID and Talkgroup ID lookup tables. * @param ridLookup Radio ID Lookup Table Instance @@ -211,6 +219,11 @@ namespace network * @param callback */ void setNXDNICCCallback(std::function&& callback) { m_nxdnInCallCallback = callback; } + /** + * @brief Helper to set the analog In-Call Control callback. + * @param callback + */ + void setAnalogICCCallback(std::function&& callback) { m_analogInCallCallback = callback; } /** * @brief Helper to set the enc. key response callback. @@ -235,6 +248,7 @@ namespace network bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_analogEnabled; bool m_updateLookup; bool m_saveLookup; @@ -245,11 +259,13 @@ namespace network uint8_t* m_salt; Timer m_retryTimer; + uint8_t m_retryCount; Timer m_timeoutTimer; uint32_t* m_rxDMRStreamId; uint32_t m_rxP25StreamId; uint32_t m_rxNXDNStreamId; + uint32_t m_rxAnalogStreamId; uint16_t m_pktSeq; uint32_t m_loginStreamId; @@ -298,6 +314,11 @@ namespace network * (This is called once the master sends a In-Call Control request.) */ std::function m_nxdnInCallCallback; + /** + * @brief Analog In-Call Control Function Callback. + * (This is called once the master sends a In-Call Control request.) + */ + std::function m_analogInCallCallback; /** * @brief Encryption Key Response Function Callback. @@ -312,9 +333,11 @@ namespace network * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. * @param streamId Stream ID. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. */ virtual void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, - uint32_t streamId = 0U); + uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader()); /** * @brief Writes login request to the network. diff --git a/src/common/network/PacketBuffer.cpp b/src/common/network/PacketBuffer.cpp index 5bb9fe8a1..686a34b6c 100644 --- a/src/common/network/PacketBuffer.cpp +++ b/src/common/network/PacketBuffer.cpp @@ -72,7 +72,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL frag->data = new uint8_t[frag->size + 1U]; ::memcpy(frag->data, data + FRAG_HDR_SIZE, FRAG_BLOCK_SIZE); - // Utils::dump(1U, "Block Payload", frag->data, FRAG_BLOCK_SIZE); + // Utils::dump(1U, "PacketBuffer::decode(), Block Payload", frag->data, FRAG_BLOCK_SIZE); fragments.insert(curBlock, frag); } @@ -106,7 +106,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL uint32_t compressedLen = fragments[0]->compressedSize; uint32_t len = fragments[0]->size; - buffer = new uint8_t[len +1U]; + buffer = new uint8_t[len + 1U]; ::memset(buffer, 0x00U, len + 1U); if (fragments.size() == 1U) { ::memcpy(buffer, fragments[0U]->data, len); @@ -119,7 +119,10 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL if (m_compression) { uint32_t decompressedLen = 0U; - *message = Compression::decompress(buffer, compressedLen, &decompressedLen); + UInt8Array decompressed = Compression::decompress(buffer, compressedLen, &decompressedLen); + *message = new uint8_t[decompressedLen]; + ::memset(*message, 0x00U, decompressedLen); + ::memcpy(*message, decompressed.get(), decompressedLen); // check that we got the appropriate data if (decompressedLen == len && message != nullptr) { @@ -161,13 +164,13 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length) // create temporary buffer uint32_t compressedLen = 0U; - uint8_t* buffer = nullptr; + UInt8Array buffer = nullptr; if (m_compression) { buffer = Compression::compress(data, length, &compressedLen); } else { - buffer = new uint8_t[length]; - ::memset(buffer, 0x00U, length); - ::memcpy(buffer, data, length); + buffer = std::make_unique(length + 1U); + ::memset(buffer.get(), 0x00U, length + 1U); + ::memcpy(buffer.get(), data, length); compressedLen = length; } @@ -198,15 +201,13 @@ void PacketBuffer::encode(uint8_t* data, uint32_t length) if (offs + FRAG_BLOCK_SIZE > compressedLen) blockSize = FRAG_BLOCK_SIZE - ((offs + FRAG_BLOCK_SIZE) - compressedLen); - ::memcpy(frag->data + FRAG_HDR_SIZE, buffer + offs, blockSize); + ::memcpy(frag->data + FRAG_HDR_SIZE, buffer.get() + offs, blockSize); offs += FRAG_BLOCK_SIZE; fragments.insert(i, frag); LogInfoEx(LOG_NET, "%s, Outbound Packet Fragment, block %u of %u, txFragments = %u", m_name, i, blockCnt - 1U, fragments.size()); } - - delete[] buffer; } /* Helper to clear currently buffered fragments. */ diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 02d106fe7..8719f8dcc 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -87,6 +87,7 @@ namespace network PROTOCOL_SUBFUNC_DMR = 0x00U, //! DMR PROTOCOL_SUBFUNC_P25 = 0x01U, //! P25 PROTOCOL_SUBFUNC_NXDN = 0x02U, //! NXDN + PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //! Analog MASTER_SUBFUNC_WL_RID = 0x00U, //! Whitelist RIDs MASTER_SUBFUNC_BL_RID = 0x01U, //! Blacklist RIDs diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index fbd874595..efbd3ad5a 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.cpp @@ -71,7 +71,7 @@ UInt8Array RawFrameQueue::read(int& messageLength, sockaddr_storage& address, ui if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Packet", buffer, length); + Utils::dump(1U, "RawFrameQueue::read(), Network Packet", buffer, length); m_failedReadCnt = 0U; @@ -104,7 +104,7 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor ::memcpy(buffer, message, length); if (m_debug) - Utils::dump(1U, "RawFrameQueue::write() Message", buffer, length); + Utils::dump(1U, "RawFrameQueue::write(), Message", buffer, length); // bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about // this message @@ -153,7 +153,7 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock ::memcpy(buffer, message, length); if (m_debug) - Utils::dump(1U, "RawFrameQueue::enqueueMessage() Buffered Message", buffer, length); + Utils::dump(1U, "RawFrameQueue::enqueueMessage(), Buffered Message", buffer, length); udp::UDPDatagram* dgram = new udp::UDPDatagram; dgram->buffer = buffer; @@ -169,29 +169,34 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock bool RawFrameQueue::flushQueue() { bool ret = true; - std::lock_guard lock(m_queueMutex); - m_queueFlushing = true; - if (m_buffers.empty()) { - return false; - } + // scope is intentional + { + std::lock_guard lock(m_queueMutex); + m_queueFlushing = true; - // bryanb: this is the same as above -- but for some assinine reason prevents - // weirdness - if (m_buffers.size() == 0U) { - return false; - } + if (m_buffers.empty()) { + return false; + } - // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); + // bryanb: this is the same as above -- but for some assinine reason prevents + // weirdness + if (m_buffers.size() == 0U) { + return false; + } - ret = true; - if (!m_socket->write(m_buffers)) { - // LogError(LOG_NET, "Failed writing data to the network"); - ret = false; + // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); + + ret = true; + if (!m_socket->write(m_buffers)) { + // LogError(LOG_NET, "Failed writing data to the network"); + ret = false; + } + + m_queueFlushing = false; } deleteBuffers(); - m_queueFlushing = false; return ret; } @@ -203,6 +208,9 @@ bool RawFrameQueue::flushQueue() void RawFrameQueue::deleteBuffers() { + std::lock_guard lock(m_queueMutex); + m_queueFlushing = true; + for (auto& buffer : m_buffers) { if (buffer != nullptr) { // LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length); @@ -217,4 +225,5 @@ void RawFrameQueue::deleteBuffers() } } m_buffers.clear(); + m_queueFlushing = false; } diff --git a/src/common/network/rest/http/HTTPPayload.cpp b/src/common/network/rest/http/HTTPPayload.cpp index ca2fb0f00..088db94b2 100644 --- a/src/common/network/rest/http/HTTPPayload.cpp +++ b/src/common/network/rest/http/HTTPPayload.cpp @@ -311,7 +311,7 @@ std::vector HTTPPayload::toBuffers() #if DEBUG_HTTP_PAYLOAD ::LogDebugEx(LOG_REST, "HTTPPayload::toBuffers()", "content = %s", content.c_str()); for (auto buffer : buffers) - Utils::dump("[HTTPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); + Utils::dump("HTTPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size()); #endif return buffers; diff --git a/src/common/network/rest/http/SecureServerConnection.h b/src/common/network/rest/http/SecureServerConnection.h index 3b98033c8..38c22bc6e 100644 --- a/src/common/network/rest/http/SecureServerConnection.h +++ b/src/common/network/rest/http/SecureServerConnection.h @@ -154,7 +154,7 @@ namespace network ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { if (m_debug) { LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } result = HTTPLexer::INDETERMINATE; @@ -163,7 +163,7 @@ namespace network } else { if (m_debug) { LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } if (m_contResult == HTTPLexer::INDETERMINATE) { @@ -180,7 +180,7 @@ namespace network if (result == HTTPLexer::GOOD) { if (m_debug) { - Utils::dump(1U, "HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); + Utils::dump(1U, "SecureServerConnection::read(), HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); } m_continue = false; @@ -188,7 +188,7 @@ namespace network m_requestHandler.handleRequest(m_request, m_reply); if (m_debug) { - Utils::dump(1U, "HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); + Utils::dump(1U, "SecureServerConnection::read(), HTTPS Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); } write(); diff --git a/src/common/network/rest/http/ServerConnection.h b/src/common/network/rest/http/ServerConnection.h index da5f32ad3..c7b932a32 100644 --- a/src/common/network/rest/http/ServerConnection.h +++ b/src/common/network/rest/http/ServerConnection.h @@ -135,7 +135,7 @@ namespace network ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { if (m_debug) { LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } m_contResult = result = HTTPLexer::INDETERMINATE; @@ -144,7 +144,7 @@ namespace network } else { if (m_debug) { LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); - Utils::dump(1U, "m_buffer", (uint8_t*)m_buffer.data(), recvLength); + Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); } if (m_contResult == HTTPLexer::INDETERMINATE) { @@ -161,7 +161,7 @@ namespace network if (result == HTTPLexer::GOOD) { if (m_debug) { - Utils::dump(1U, "HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); + Utils::dump(1U, "ServerConnection::read(), HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); } m_continue = false; @@ -169,7 +169,7 @@ namespace network m_requestHandler.handleRequest(m_request, m_reply); if (m_debug) { - Utils::dump(1U, "HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); + Utils::dump(1U, "ServerConnection::read(), HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); } write(); diff --git a/src/common/network/sip/SIPPayload.cpp b/src/common/network/sip/SIPPayload.cpp index ffc7b012e..29e20bdcc 100644 --- a/src/common/network/sip/SIPPayload.cpp +++ b/src/common/network/sip/SIPPayload.cpp @@ -139,7 +139,7 @@ std::vector SIPPayload::toBuffers() #if DEBUG_SIP_PAYLOAD ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "content = %s", content.c_str()); for (auto buffer : buffers) - Utils::dump("[SIPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); + Utils::dump("SIPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size()); #endif return buffers; diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h index 5e9ae4158..657ce8c8d 100644 --- a/src/common/network/tcp/SecureTcpClient.h +++ b/src/common/network/tcp/SecureTcpClient.h @@ -70,12 +70,12 @@ namespace network // setup socket for non-blocking operations int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } @@ -139,12 +139,12 @@ namespace network if (!nonBlocking) { flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot accept SSL client"); } } @@ -176,7 +176,7 @@ namespace network ssize_t ret = ::connect(m_fd, reinterpret_cast(&addr), sizeof(addr)); if (ret < 0) { - LogError(LOG_NET, "Failed to connect to server, err: %d", errno); + LogError(LOG_NET, "Failed to connect to server, err: %d (%s)", errno, strerror(errno)); } initSsl(m_pSSLCtx); @@ -189,12 +189,12 @@ namespace network if (nonBlocking) { int flags = fcntl(m_fd, F_GETFL, 0); if (flags < 0) { - LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Failed to set SSL server connection to non-blocking"); } if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) < 0) { - LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d", errno); + LogError(LOG_NET, "failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Failed to set SSL server connection to non-blocking"); } } @@ -260,13 +260,13 @@ namespace network { m_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot create the TCP socket"); } int reuse = 1; if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char*)& reuse, sizeof(reuse)) != 0) { - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot set the TCP socket option"); } } diff --git a/src/common/network/tcp/SecureTcpListener.h b/src/common/network/tcp/SecureTcpListener.h index 0b8ad9127..978c4bcd6 100644 --- a/src/common/network/tcp/SecureTcpListener.h +++ b/src/common/network/tcp/SecureTcpListener.h @@ -72,13 +72,13 @@ namespace network m_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot create the TCP socket"); } int reuse = 1; if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot set the TCP socket option"); } @@ -94,7 +94,7 @@ namespace network SecureTcpListener(const std::string& keyFile, const std::string& certFile, const uint16_t port, const std::string& address = "0.0.0.0") : SecureTcpListener(keyFile, certFile) { if (!bind(address, port)) { - LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d", errno); + LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot to bind secure TCP server"); } } diff --git a/src/common/network/tcp/Socket.cpp b/src/common/network/tcp/Socket.cpp index 6a6537a69..17b5f3b87 100644 --- a/src/common/network/tcp/Socket.cpp +++ b/src/common/network/tcp/Socket.cpp @@ -110,7 +110,7 @@ int Socket::accept(sockaddr* address, socklen_t* addrlen) noexcept #if defined(_WIN32) LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from TCP poll, err: %d", errno); + LogError(LOG_NET, "Error returned from TCP poll, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) return -1; } @@ -180,7 +180,7 @@ ssize_t Socket::listen(const std::string& ipAddr, const uint16_t port, int backl #if defined(_WIN32) LogError(LOG_NET, "Error returned from TCP poll, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from TCP poll, err: %d", errno); + LogError(LOG_NET, "Error returned from TCP poll, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) return -1; } @@ -316,7 +316,7 @@ bool Socket::initSocket(const int domain, const int type, const int protocol) } #else if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the TCP socket, err: %d (%s)", errno, strerror(errno)); return false; } #endif // defined(_WIN32) @@ -340,7 +340,7 @@ bool Socket::bind(const std::string& ipAddr, const uint16_t port) #if defined(_WIN32) LogError(LOG_NET, "Cannot bind the TCP address, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot bind the TCP address, err: %d", errno); + LogError(LOG_NET, "Cannot bind the TCP address, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) retval = false; } diff --git a/src/common/network/tcp/TcpClient.h b/src/common/network/tcp/TcpClient.h index 3d1c14e46..8f2421017 100644 --- a/src/common/network/tcp/TcpClient.h +++ b/src/common/network/tcp/TcpClient.h @@ -82,7 +82,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Failed to connect to server, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Failed to connect to server, err: %d", errno); + LogError(LOG_NET, "Failed to connect to server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) } } @@ -106,7 +106,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Cannot set the TCP socket option, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) throw std::runtime_error("Cannot set the TCP socket option"); } diff --git a/src/common/network/tcp/TcpListener.h b/src/common/network/tcp/TcpListener.h index 6da63e70b..9201957fd 100644 --- a/src/common/network/tcp/TcpListener.h +++ b/src/common/network/tcp/TcpListener.h @@ -52,7 +52,7 @@ namespace network } #else if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { - LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d (%s)", errno, strerror(errno)); throw std::runtime_error("Cannot set the TCP socket option"); } #endif // defined(_WIN32) @@ -68,7 +68,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Cannot to bind TCP server, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot to bind TCP server, err: %d", errno); + LogError(LOG_NET, "Cannot to bind TCP server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) throw std::runtime_error("Cannot to bind TCP server"); } @@ -85,7 +85,7 @@ namespace network #if defined(_WIN32) LogError(LOG_NET, "Failed to listen on TCP server, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Failed to listen on TCP server, err: %d", errno); + LogError(LOG_NET, "Failed to listen on TCP server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) throw std::runtime_error("Failed to listen on TCP server."); } diff --git a/src/common/network/udp/Socket.cpp b/src/common/network/udp/Socket.cpp index 0d55a210a..8ec1f2cf9 100644 --- a/src/common/network/udp/Socket.cpp +++ b/src/common/network/udp/Socket.cpp @@ -145,7 +145,11 @@ bool Socket::open(const uint32_t af, const std::string& address, const uint16_t if (port > 0U) { int reuse = 1; if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char*)& reuse, sizeof(reuse)) == -1) { - LogError(LOG_NET, "Cannot set the UDP socket option, err: %d", errno); +#if defined(_WIN32) + LogError(LOG_NET, "Cannot bind the UDP socket option, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot set the UDP socket option, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 return false; } @@ -205,7 +209,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address #if defined(_WIN32) LogError(LOG_NET, "Error returned from UDP poll, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from UDP poll, err: %d", errno); + LogError(LOG_NET, "Error returned from UDP poll, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) return -1; } @@ -219,7 +223,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address #if defined(_WIN32) LogError(LOG_NET, "Error returned from recvfrom, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from recvfrom, err: %d", errno); + LogError(LOG_NET, "Error returned from recvfrom, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) if (len == -1 && errno == ENOTSOCK) { @@ -255,12 +259,12 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address ::memcpy(cryptoBuffer, buffer + 2U, len - 2U); } - // Utils::dump(1U, "Socket::read() crypted", cryptoBuffer, cryptedLen); + // Utils::dump(1U, "Socket::read(), crypted", cryptoBuffer, cryptedLen); // decrypt uint8_t* decrypted = m_aes->decryptECB(cryptoBuffer, cryptedLen, m_presharedKey); - // Utils::dump(1U, "Socket::read() decrypted", decrypted, cryptedLen); + // Utils::dump(1U, "Socket::read(), decrypted", decrypted, cryptedLen); // finalize, cleanup buffers and replace with new if (decrypted != nullptr) { @@ -340,7 +344,7 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag // encrypt uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); - // Utils::dump(1U, "Socket::write() crypted", crypted, cryptedLen); + // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen); // finalize, cleanup buffers and replace with new out = std::unique_ptr(new uint8_t[cryptedLen + 2U]); @@ -365,10 +369,16 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag ssize_t sent = ::sendto(m_fd, (char*)out.get(), length, 0, (sockaddr*)& address, addrLen); if (sent < 0) { + if (errno == ENETUNREACH || errno == EHOSTUNREACH) { + // if we were not able to send a frame and the network logging is enabled -- disable network logging + if (!g_disableNetworkLog) + g_disableNetworkLog = true; + } + #if defined(_WIN32) LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Error returned from sendto, err: %d", errno); + LogError(LOG_NET, "Error returned from sendto, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) if (lenWritten != nullptr) { @@ -376,6 +386,10 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag } } else { + // if we were able to send a frame and the network logging is disabled -- reenable network logging + if (g_disableNetworkLog) + g_disableNetworkLog = false; + if (sent == ssize_t(length)) result = true; @@ -491,7 +505,7 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept continue; } - // Utils::dump(1U, "Socket::write() crypted", crypted, cryptedLen); + // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen); // finalize DECLARE_UINT8_ARRAY(out, cryptedLen + 2U); @@ -553,14 +567,22 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept } if (sendmmsg(m_fd, headers, size, 0) < 0) { - LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); +#if defined(_WIN32) + LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from sendmmsg, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 if (lenWritten != nullptr) { *lenWritten = -1; } } if (sent < 0) { - LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); +#if defined(_WIN32) + LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from sendmmsg, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 if (lenWritten != nullptr) { *lenWritten = -1; } @@ -655,7 +677,7 @@ std::string Socket::getLocalAddress() err = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if (err != 0) { - LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d", errno); + LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d (%s)", errno, strerror(errno)); break; } @@ -800,7 +822,7 @@ bool Socket::initSocket(const int domain, const int type, const int protocol) no } #else if (m_fd < 0) { - LogError(LOG_NET, "Cannot create the UDP socket, err: %d", errno); + LogError(LOG_NET, "Cannot create the UDP socket, err: %d (%s)", errno, strerror(errno)); return false; } #endif // defined(_WIN32) @@ -825,7 +847,7 @@ bool Socket::bind(const std::string& ipAddr, const uint16_t port) noexcept(false #if defined(_WIN32) LogError(LOG_NET, "Cannot bind the UDP address, err: %lu", ::GetLastError()); #else - LogError(LOG_NET, "Cannot bind the UDP address, err: %d", errno); + LogError(LOG_NET, "Cannot bind the UDP address, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) retval = false; } diff --git a/src/common/network/viface/VIFace.cpp b/src/common/network/viface/VIFace.cpp index 58c1f3995..530b2b133 100644 --- a/src/common/network/viface/VIFace.cpp +++ b/src/common/network/viface/VIFace.cpp @@ -91,7 +91,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // creates the socket fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (fd < 0) { - LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -102,7 +102,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // obtains the network index number if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { - LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -115,7 +115,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // binds the socket to the 'socket_addr' address if (bind(fd, (struct sockaddr*) &socket_addr, sizeof(socket_addr)) != 0) { - LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -128,7 +128,7 @@ void hookVirtualInterface(std::string name, struct viface_queues* queues) // Rollback close file descriptors for (--i; i >= 0; i--) { if (close(((int *)queues)[i]) < 0) { - LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); } } @@ -171,15 +171,15 @@ std::string allocateVirtualInterface(std::string name, bool tap, struct viface_q // open TUN/TAP device fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); if (fd < 0) { - LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } // register a network device with the kernel if (ioctl(fd, TUNSETIFF, (void *)&ifr) != 0) { - LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); if (close(fd) < 0) { - LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); } goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... @@ -194,7 +194,7 @@ std::string allocateVirtualInterface(std::string name, bool tap, struct viface_q // rollback close file descriptors for (--i; i >= 0; i--) { if (close(((int *)queues)[i]) < 0) { - LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d (%s)", name.c_str(), i, errno, strerror(errno)); } } @@ -217,7 +217,7 @@ void readVIFlags(int sockfd, std::string name, struct ifreq& ifr) // read interface flags if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) { - LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); } } @@ -235,7 +235,7 @@ uint32_t readMTU(std::string name, size_t size) // opens MTU file fd = open(("/sys/class/net/" + name + "/mtu").c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) { - LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -245,12 +245,12 @@ uint32_t readMTU(std::string name, size_t size) // Handles errors if (nread == -1) { - LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } if (close(fd) < 0) { - LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... } @@ -307,7 +307,7 @@ VIFace::VIFace(std::string name, bool tap, int id) : // epoll create m_epollFd = epoll_create1(0); if (m_epollFd == -1) { - LogError(LOG_NET, "Unable to initialize epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to initialize epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to initialize epoll."); } @@ -317,20 +317,20 @@ VIFace::VIFace(std::string name, bool tap, int id) : }; if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { - LogError(LOG_NET, "Unable to configure epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to configure epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to configure epoll."); } ev.data.fd = m_queues.txFd; if (epoll_ctl(m_epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { - LogError(LOG_NET, "Unable to configure epoll %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to configure epoll %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to configure epoll."); } // create socket channels to the NET kernel for later ioctl m_ksFd = -1; m_ksFd = socket(AF_INET, SOCK_STREAM, 0); if (m_ksFd < 0) { - LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d (%s)", name.c_str(), errno, strerror(errno)); throw std::runtime_error("Unable to create IPv4 socket channel to the NET kernel."); } @@ -378,7 +378,7 @@ void VIFace::up() } if (ioctl(m_ksFd, SIOCSIFHWADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to set MAC address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set MAC address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -390,12 +390,12 @@ void VIFace::up() // address if (!m_ipv4Address.empty()) { if (!inet_pton(AF_INET, m_ipv4Address.c_str(), &addr->sin_addr)) { - LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } if (ioctl(m_ksFd, SIOCSIFADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -403,12 +403,12 @@ void VIFace::up() // netmask if (!m_ipv4Netmask.empty()) { if (!inet_pton(AF_INET, m_ipv4Netmask.c_str(), &addr->sin_addr)) { - LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } if (ioctl(m_ksFd, SIOCSIFNETMASK, &ifr) != 0) { - LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -416,12 +416,12 @@ void VIFace::up() // broadcast if (!m_ipv4Broadcast.empty()) { if (!inet_pton(AF_INET, m_ipv4Broadcast.c_str(), &addr->sin_addr)) { - LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } if (ioctl(m_ksFd, SIOCSIFBRDADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } } @@ -429,14 +429,14 @@ void VIFace::up() // MTU ifr.ifr_mtu = m_mtu; if (ioctl(m_ksFd, SIOCSIFMTU, &ifr) != 0) { - LogError(LOG_NET, "Unable to set MTU %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to set MTU %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } // bring-up interface ifr.ifr_flags |= IFF_UP; if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { - LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); } } @@ -451,7 +451,7 @@ void VIFace::down() const // bring-down interface ifr.ifr_flags &= ~IFF_UP; if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { - LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); } } @@ -477,7 +477,7 @@ ssize_t VIFace::read(uint8_t* buffer) int ret = epoll_wait(m_epollFd, &wait_event, 1, 0); if ((ret < 0) && (errno != EINTR)) { - LogError(LOG_NET, "Error returned from epoll_wait, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Error returned from epoll_wait, err: %d (%s)", errno, strerror(errno)); return -1; } @@ -490,7 +490,7 @@ ssize_t VIFace::read(uint8_t* buffer) // Read packet into our buffer ssize_t len = ::read(wait_event.data.fd, buffer, m_mtu); if (len == -1) { - LogError(LOG_NET, "Error returned from read, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Error returned from read, err: %d (%s)", errno, strerror(errno)); } return len; @@ -510,7 +510,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) *lenWritten = -1; } - LogError(LOG_NET, "Packet is too small, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Packet is too small, err: %d (%s)", errno, strerror(errno)); return false; } @@ -519,7 +519,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) *lenWritten = -1; } - LogError(LOG_NET, "Packet is too large, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Packet is too large, err: %d (%s)", errno, strerror(errno)); return false; } @@ -527,7 +527,7 @@ bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) bool result = false; ssize_t sent = ::write(m_queues.txFd, buffer, length); if (sent < 0) { - LogError(LOG_NET, "Error returned from write, err: %d, error: %s", errno, strerror(errno)); + LogError(LOG_NET, "Error returned from write, err: %d (%s)", errno, strerror(errno)); if (lenWritten != nullptr) { *lenWritten = -1; @@ -585,7 +585,7 @@ std::string VIFace::getMAC() const readVIFlags(m_ksFd, m_name, ifr); if (ioctl(m_ksFd, SIOCGIFHWADDR, &ifr) != 0) { - LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return std::string(); } @@ -608,7 +608,7 @@ void VIFace::setIPv4(std::string address) { struct in_addr addr; if (!inet_pton(AF_INET, address.c_str(), &addr)) { - LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } @@ -628,7 +628,7 @@ void VIFace::setIPv4Netmask(std::string netmask) { struct in_addr addr; if (!inet_pton(AF_INET, netmask.c_str(), &addr)) { - LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } @@ -648,7 +648,7 @@ void VIFace::setIPv4Broadcast(std::string broadcast) { struct in_addr addr; if (!inet_pton(AF_INET, broadcast.c_str(), &addr)) { - LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return; } @@ -667,14 +667,14 @@ std::string VIFace::getIPv4Broadcast() const void VIFace::setMTU(uint32_t mtu) { if (mtu < ETH_HLEN) { - LogError(LOG_NET, "MTU %d is too small %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "MTU %d is too small %s, err: %d (%s)", mtu, m_name.c_str(), errno, strerror(errno)); return; } // are we sure about this upper validation? // lo interface reports this number for its MTU if (mtu > 65536) { - LogError(LOG_NET, "MTU %d is too large %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "MTU %d is too large %s, err: %d (%s)", mtu, m_name.c_str(), errno, strerror(errno)); return; } @@ -690,7 +690,7 @@ uint32_t VIFace::getMTU() const readVIFlags(m_ksFd, m_name, ifr); if (ioctl(m_ksFd, SIOCGIFMTU, &ifr) != 0) { - LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return 0U; } @@ -710,7 +710,7 @@ std::string VIFace::ioctlGetIPv4(uint64_t request) const readVIFlags(m_ksFd, m_name, ifr); if (ioctl(m_ksFd, request, &ifr) != 0) { - LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return std::string(); } @@ -720,7 +720,7 @@ std::string VIFace::ioctlGetIPv4(uint64_t request) const struct sockaddr_in* ipaddr = (struct sockaddr_in*) &ifr.ifr_addr; if (inet_ntop(AF_INET, &(ipaddr->sin_addr), addr, sizeof(addr)) == NULL) { - LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d (%s)", m_name.c_str(), errno, strerror(errno)); return std::string(); } diff --git a/src/common/nxdn/NXDNDefines.h b/src/common/nxdn/NXDNDefines.h index 077c28e5d..a9e4c3632 100644 --- a/src/common/nxdn/NXDNDefines.h +++ b/src/common/nxdn/NXDNDefines.h @@ -284,6 +284,17 @@ namespace nxdn }; } + /** @brief Remote Control Commands */ + namespace RemoteControlCommand { + /** @brief Remote Control Commands */ + enum : uint8_t { + STUN = 0x00U, //! Stun + REVIVE = 0x01U, //! Revive + KILL = 0x02U, //! Kill + REMOTE_MONITOR = 0x04U //! Remote Monitor + }; + } + /** @brief Channel Access - Step */ namespace ChAccessStep { /** @brief Channel Access - Step */ @@ -340,6 +351,8 @@ namespace nxdn SRV_INFO = 0x19U, //! SRV_INFO - Service Information CCH_INFO = 0x1AU, //! CCH_INFO - Control Channel Information ADJ_SITE_INFO = 0x1BU, //! ADJ_SITE_INFO - Adjacent Site Information + REM_CON_REQ = 0x34U, //! REM_CON_REQ - Remote Control Request + REM_CON_RESP = 0x35U, //! REM_CON_RESP - Remote Control Response // Traffic Channel Message Types RTCH_VCALL = 0x01U, //! VCALL - Voice Call diff --git a/src/common/nxdn/NXDNUtils.cpp b/src/common/nxdn/NXDNUtils.cpp index c94e32afd..0c0691149 100644 --- a/src/common/nxdn/NXDNUtils.cpp +++ b/src/common/nxdn/NXDNUtils.cpp @@ -55,3 +55,46 @@ void NXDNUtils::addPostBits(uint8_t* data) WRITE_BIT(data, n, b); } } + +/* Helper to convert a cause code to a string. */ + +std::string NXDNUtils::causeToString(uint8_t cause) +{ + switch (cause) { + case CauseResponse::RSRC_NOT_AVAIL_NETWORK: + return std::string("RSRC_NOT_AVAIL_NETWORK (Resource Not Available - Network)"); + case CauseResponse::RSRC_NOT_AVAIL_TEMP: + return std::string("RSRC_NOT_AVAIL_TEMP (Resource Not Available - Temporary)"); + case CauseResponse::RSRC_NOT_AVAIL_QUEUED: + return std::string("RSRC_NOT_AVAIL_QUEUED (Resource Not Available - Queued)"); + case CauseResponse::SVC_UNAVAILABLE: + return std::string("SVC_UNAVAILABLE (Service Unavailable)"); + case CauseResponse::PROC_ERROR: + return std::string("PROC_ERROR (Procedure Error - Lack of packet data)"); + case CauseResponse::PROC_ERROR_UNDEF: + return std::string("PROC_ERROR_UNDEF (Procedure Error - Invalid packet data)"); + + case CauseResponse::VD_GRP_NOT_PERM: + return std::string("VD_GRP_NOT_PERM (Voice Group Not Permitted)"); + case CauseResponse::VD_REQ_UNIT_NOT_PERM: + return std::string("VD_REQ_UNIT_NOT_PERM (Voice Requesting Unit Not Permitted)"); + case CauseResponse::VD_TGT_UNIT_NOT_PERM: + return std::string("VD_TGT_UNIT_NOT_PERM (Voice Target Unit Not Permitted)"); + case CauseResponse::VD_REQ_UNIT_NOT_REG: + return std::string("VD_REQ_UNIT_NOT_REG (Voice Requesting Unit Not Registered)"); + case CauseResponse::VD_QUE_CHN_RESOURCE_NOT_AVAIL: + return std::string("VD_QUE_CHN_RESOURCE_NOT_AVAIL (Voice Channel Resources Unavailable)"); + case CauseResponse::VD_QUE_TGT_UNIT_BUSY: + return std::string("VD_QUE_TGT_UNIT_BUSY (Voice Target Unit Busy)"); + case CauseResponse::VD_QUE_GRP_BUSY: + return std::string("VD_QUE_GRP_BUSY (Voice Group Busy)"); + + case CauseResponse::DISC_USER: + return std::string("DISC_USER (Disconnect by User)"); + case CauseResponse::DISC_OTHER: + return std::string("DISC_OTHER (Other Disconnect)"); + + default: + return std::string(); + } +} diff --git a/src/common/nxdn/NXDNUtils.h b/src/common/nxdn/NXDNUtils.h index 6ceafb136..264931b2f 100644 --- a/src/common/nxdn/NXDNUtils.h +++ b/src/common/nxdn/NXDNUtils.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -41,6 +41,13 @@ namespace nxdn * @param data Buffer to apply post field bits to. */ static void addPostBits(uint8_t* data); + + /** + * @brief Helper to convert a cause code to a string. + * @param cause Cause code. + * @returns std::string Cause code string. + */ + static std::string causeToString(uint8_t cause); }; } // namespace nxdn diff --git a/src/common/nxdn/channel/CAC.cpp b/src/common/nxdn/channel/CAC.cpp index 87422b740..ab86a3757 100644 --- a/src/common/nxdn/channel/CAC.cpp +++ b/src/common/nxdn/channel/CAC.cpp @@ -170,7 +170,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) } #if DEBUG_NXDN_CAC - Utils::dump(2U, "CAC::decode(), CAC Raw", buffer, NXDN_CAC_IN_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::decode(), CAC Raw", buffer, NXDN_CAC_IN_FEC_LENGTH_BYTES); #endif if (longInbound) { @@ -213,7 +213,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) conv.chainback(m_data, NXDN_CAC_LONG_CRC_LENGTH_BITS); #if DEBUG_NXDN_CAC - Utils::dump(2U, "Decoded Long CAC", m_data, (NXDN_CAC_LONG_CRC_LENGTH_BITS / 8U) + 1U); + Utils::dump(2U, "NXDN, CAC::decode(), Decoded Long CAC", m_data, (NXDN_CAC_LONG_CRC_LENGTH_BITS / 8U) + 1U); #endif // check CRC-16 @@ -271,7 +271,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) conv.chainback(m_data, NXDN_CAC_SHORT_CRC_LENGTH_BITS); #if DEBUG_NXDN_CAC - Utils::dump(2U, "Decoded CAC", m_data, (NXDN_CAC_SHORT_CRC_LENGTH_BITS / 8U) + 1U); + Utils::dump(2U, "NXDN, CAC::decode(), Decoded CAC", m_data, (NXDN_CAC_SHORT_CRC_LENGTH_BITS / 8U) + 1U); #endif // check CRC-16 @@ -298,7 +298,7 @@ bool CAC::decode(const uint8_t* data, bool longInbound) } #if DEBUG_NXDN_CAC - Utils::dump(2U, "Raw CAC Buffer", m_data, NXDN_CAC_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::decode(), Raw CAC Buffer", m_data, NXDN_CAC_FEC_LENGTH_BYTES); #endif return true; @@ -324,7 +324,7 @@ void CAC::encode(uint8_t* data) const uint16_t crc = edac::CRC::addCRC16(buffer, NXDN_CAC_LENGTH_BITS); #if DEBUG_NXDN_CAC - Utils::dump(2U, "Encoded CAC", buffer, NXDN_CAC_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::encode(), Encoded CAC", buffer, NXDN_CAC_FEC_LENGTH_BYTES); #endif // encode convolution @@ -357,7 +357,7 @@ void CAC::encode(uint8_t* data) const } #if DEBUG_NXDN_CAC - Utils::dump(2U, "CAC::encode(), CAC Puncture and Interleave", data, NXDN_FRAME_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::encode(), CAC Puncture and Interleave", data, NXDN_FRAME_LENGTH_BYTES); #endif // apply control field @@ -380,7 +380,7 @@ void CAC::encode(uint8_t* data) const } #if DEBUG_NXDN_CAC - Utils::dump(2U, "CAC::encode(), CAC + Control", data, NXDN_FRAME_LENGTH_BYTES); + Utils::dump(2U, "NXDN, CAC::encode(), CAC + Control", data, NXDN_FRAME_LENGTH_BYTES); #endif } diff --git a/src/common/nxdn/channel/FACCH1.cpp b/src/common/nxdn/channel/FACCH1.cpp index 94bb717d9..d590d0505 100644 --- a/src/common/nxdn/channel/FACCH1.cpp +++ b/src/common/nxdn/channel/FACCH1.cpp @@ -99,7 +99,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset) } #if DEBUG_NXDN_FACCH1 - Utils::dump(2U, "FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_FEC_LENGTH_BYTES); #endif // depuncture @@ -137,7 +137,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset) conv.chainback(m_data, NXDN_FACCH1_CRC_LENGTH_BITS); #if DEBUG_NXDN_FACCH1 - Utils::dump(2U, "Decoded FACCH1", m_data, NXDN_FACCH1_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::decode(), Decoded FACCH1", m_data, NXDN_FACCH1_CRC_LENGTH_BYTES); #endif // check CRC-12 @@ -163,7 +163,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const edac::CRC::addCRC12(buffer, NXDN_FACCH1_LENGTH_BITS); #if DEBUG_NXDN_FACCH1 - Utils::dump(2U, "Encoded FACCH1", buffer, NXDN_FACCH1_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::encode(), Encoded FACCH1", buffer, NXDN_FACCH1_CRC_LENGTH_BYTES); #endif // encode convolution @@ -196,7 +196,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const } #if DEBUG_NXDN_SACCH - Utils::dump(2U, "FACCH1::encode(), FACCH1 Puncture and Interleave", data, NXDN_FACCH1_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, FACCH1::encode(), FACCH1 Puncture and Interleave", data, NXDN_FACCH1_FEC_LENGTH_BYTES); #endif } diff --git a/src/common/nxdn/channel/SACCH.cpp b/src/common/nxdn/channel/SACCH.cpp index 91f2221d7..6aeb3a768 100644 --- a/src/common/nxdn/channel/SACCH.cpp +++ b/src/common/nxdn/channel/SACCH.cpp @@ -139,7 +139,7 @@ bool SACCH::decode(const uint8_t* data) conv.chainback(m_data, NXDN_SACCH_CRC_LENGTH_BITS); #if DEBUG_NXDN_SACCH - Utils::dump(2U, "Decoded SACCH", m_data, NXDN_SACCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "SACCH::decode(), Decoded SACCH", m_data, NXDN_SACCH_CRC_LENGTH_BYTES); #endif // check CRC-6 @@ -178,7 +178,7 @@ void SACCH::encode(uint8_t* data) const edac::CRC::addCRC6(buffer, NXDN_SACCH_LENGTH_BITS); #if DEBUG_NXDN_SACCH - Utils::dump(2U, "Encoded SACCH", buffer, NXDN_SACCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "SACCH::encode(), Encoded SACCH", buffer, NXDN_SACCH_CRC_LENGTH_BYTES); #endif // encode convolution diff --git a/src/common/nxdn/channel/UDCH.cpp b/src/common/nxdn/channel/UDCH.cpp index c42fc6a69..92aa821ff 100644 --- a/src/common/nxdn/channel/UDCH.cpp +++ b/src/common/nxdn/channel/UDCH.cpp @@ -125,7 +125,7 @@ bool UDCH::decode(const uint8_t* data) } #if DEBUG_NXDN_UDCH - Utils::dump(2U, "UDCH::decode(), UDCH Raw", buffer, NXDN_UDCH_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::decode(), UDCH Raw", buffer, NXDN_UDCH_FEC_LENGTH_BYTES); #endif // depuncture @@ -163,7 +163,7 @@ bool UDCH::decode(const uint8_t* data) conv.chainback(m_data, NXDN_UDCH_CRC_LENGTH_BITS); #if DEBUG_NXDN_UDCH - Utils::dump(2U, "Decoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::decode(), Decoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); #endif // check CRC-15 @@ -193,7 +193,7 @@ void UDCH::encode(uint8_t* data) const edac::CRC::addCRC15(buffer, NXDN_UDCH_LENGTH_BITS); #if DEBUG_NXDN_UDCH - Utils::dump(2U, "Encoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::encode(), Encoded UDCH", m_data, NXDN_UDCH_CRC_LENGTH_BYTES); #endif // encode convolution @@ -226,7 +226,7 @@ void UDCH::encode(uint8_t* data) const } #if DEBUG_NXDN_UDCH - Utils::dump(2U, "UDCH::encode(), UDCH Puncture and Interleave", data, NXDN_UDCH_FEC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, UDCH::encode(), UDCH Puncture and Interleave", data, NXDN_UDCH_FEC_LENGTH_BYTES); #endif } diff --git a/src/common/nxdn/lc/RCCH.cpp b/src/common/nxdn/lc/RCCH.cpp index f409f42d6..b5a696711 100644 --- a/src/common/nxdn/lc/RCCH.cpp +++ b/src/common/nxdn/lc/RCCH.cpp @@ -115,7 +115,7 @@ void RCCH::decode(const uint8_t* data, uint8_t* rcch, uint32_t length, uint32_t } if (m_verbose) { - Utils::dump(2U, "Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, RCCH::decode(), Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES); } m_messageType = data[0U] & 0x3FU; // Message Type @@ -138,7 +138,7 @@ void RCCH::encode(uint8_t* data, const uint8_t* rcch, uint32_t length, uint32_t } if (m_verbose) { - Utils::dump(2U, "Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, RCCH::encode(), Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES); } } diff --git a/src/common/nxdn/lc/RTCH.cpp b/src/common/nxdn/lc/RTCH.cpp index 01fbd9547..a476dc62a 100644 --- a/src/common/nxdn/lc/RTCH.cpp +++ b/src/common/nxdn/lc/RTCH.cpp @@ -114,7 +114,7 @@ void RTCH::decode(const uint8_t* data, uint32_t length, uint32_t offset) } if (m_verbose) { - Utils::dump(2U, "Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES); + Utils::dump(2U, "NXDN, RTCH::decode(), Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES); } decodeLC(rtch); @@ -137,7 +137,7 @@ void RTCH::encode(uint8_t* data, uint32_t length, uint32_t offset) } if (m_verbose) { - Utils::dump(2U, "Encoded RTCH Data", data, length); + Utils::dump(2U, "NXDN, RTCH::encode(), Encoded RTCH Data", data, length); } } @@ -181,6 +181,10 @@ bool RTCH::decodeLC(const uint8_t* data) { assert(data != nullptr); +#if DEBUG_NXDN_RTCH + Utils::dump(2U, "NXDN, RTCH::decodeLC(), RTCH", data, NXDN_RTCH_LC_LENGTH_BYTES); +#endif + m_messageType = data[0U] & 0x3FU; // Message Type // message type opcodes @@ -286,6 +290,12 @@ bool RTCH::decodeLC(const uint8_t* data) return false; } + // is this a private call? + if (m_callType == CallType::INDIVIDUAL) + m_group = false; + else + m_group = true; + return true; } @@ -405,6 +415,10 @@ void RTCH::encodeLC(uint8_t* data) LogError(LOG_NXDN, "RTCH::encodeRTCH(), unknown RTCH value, messageType = $%02X", m_messageType); return; } + +#if DEBUG_NXDN_RTCH + Utils::dump(2U, "NXDN, RTCH::encodeLC(), RTCH", data, NXDN_RTCH_LC_LENGTH_BYTES); +#endif } /* Internal helper to copy the the class. */ diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp index b6f496ed6..3b3002f90 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp @@ -22,12 +22,14 @@ using namespace nxdn::lc::rcch; // --------------------------------------------------------------------------- /* Initializes a new instance of the MESSAGE_TYPE_REG class. */ + MESSAGE_TYPE_REG::MESSAGE_TYPE_REG() : RCCH() { m_messageType = MessageType::RCCH_REG; } /* Decode RCCH data. */ + void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t offset) { assert(data != nullptr); @@ -49,6 +51,7 @@ void MESSAGE_TYPE_REG::decode(const uint8_t* data, uint32_t length, uint32_t off } /* Encode RCCH data. */ + void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) { assert(data != nullptr); @@ -73,6 +76,7 @@ void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) } /* Returns a string that represents the current RCCH. */ + std::string MESSAGE_TYPE_REG::toString(bool isp) { return (isp) ? std::string("RCCH_REG (Registration Request)") : diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index b88e56a11..1b7ff9cb1 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -112,6 +112,7 @@ namespace p25 const uint8_t ENCRYPTED_NULL_IMBE[] = { 0xFCU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; const uint8_t MOT_CALLSIGN_LENGTH_BYTES = 8U; + const uint8_t HARRIS_USER_ALIAS_LENGTH_BYTES = 14U; const uint8_t AUTH_RES_LENGTH_BYTES = 4U; const uint8_t AUTH_RAND_SEED_LENGTH_BYTES = 10U; @@ -138,6 +139,8 @@ namespace p25 /** @brief Motorola MFId */ const uint8_t MFG_MOT = 0x90U; + /** @brief L3Harris MFId */ + const uint8_t MFG_HARRIS = 0xA4U; /** @brief DVM; Omaha Communication Systems, LLC ($9C) */ const uint8_t MFG_DVM_OCS = 0x9CU; /** @} */ @@ -683,6 +686,7 @@ namespace p25 TEL_INT_VCH_USER = 0x06U, //! TEL INT VCH USER - Telephone Interconnect Voice Channel User / MOT GPS DATA - Motorola In-Band GPS Data TEL_INT_ANS_RQST = 0x07U, //! TEL INT ANS RQST - Telephone Interconnect Answer Request EXPLICIT_SOURCE_ID = 0x09U, //! EXPLICIT SOURCE ID - Explicit Source ID + PRIVATE_EXT = 0x0AU, //! UU VCH USER EXT - Unit-to-Unit Voice Channel User Extended CALL_TERM = 0x0FU, //! CALL TERM - Call Termination or Cancellation IDEN_UP = 0x18U, //! IDEN UP - Channel Identifier Update SYS_SRV_BCAST = 0x20U, //! SYS SRV BCAST - System Service Broadcast @@ -692,7 +696,21 @@ namespace p25 CONV_FALLBACK = 0x2AU, //! CONV FALLBACK - Conventional Fallback // LDUx/TDULC Motorola Link Control Opcode(s) - FAILSOFT = 0x02U //! FAILSOFT - Failsoft + FAILSOFT = 0x02U, //! FAILSOFT - Failsoft + + MOT_PTT_LOC_HEADER = 0x29U, //! MOT PTT LOC HEADER - Motorola PTT Location Header + MOT_PTT_LOC_PAYLOAD = 0x2AU, //! MOT PTT LOC PAYLOAD - Motorola PTT Location Payload + + // LDUx/TDULC Harris Link Control Opcode(s) + HARRIS_PTT_PA_ODD = 0x2AU, //! HARRIS PTT PA ODD - Harris PTT Position and Altitude Odd + HARRIS_PTT_PB_ODD = 0x2BU, //! HARRIS PTT PB ODD - Harris PTT Position and Bearing Odd + HARRIS_PTT_PA_EVEN = 0x2CU, //! HARRIS PTT PA EVEN - Harris PTT Position and Altitude Even + HARRIS_PTT_PB_EVEN = 0x2DU, //! HARRIS PTT PB EVEN - Harris PTT Position and Bearing Even + + HARRIS_USER_ALIAS_PA_ODD = 0x32U, //! HARRIS USER ALIAS PA ODD - Harris User Alias Position and Altitude Odd + HARRIS_USER_ALIAS_PB_ODD = 0x33U, //! HARRIS USER ALIAS PB ODD - Harris User Alias Position and Bearing Odd + HARRIS_USER_ALIAS_PA_EVEN = 0x34U, //! HARRIS USER ALIAS PA EVEN - Harris User Alias Position and Altitude Even + HARRIS_USER_ALIAS_PB_EVEN = 0x35U, //! HARRIS USER ALIAS PB EVEN - Harris User Alias Position and Bearing Even }; } diff --git a/src/common/p25/P25Utils.cpp b/src/common/p25/P25Utils.cpp index e7b7c1ff2..6ac0adf90 100644 --- a/src/common/p25/P25Utils.cpp +++ b/src/common/p25/P25Utils.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -242,3 +242,71 @@ uint32_t P25Utils::compare(const uint8_t* data1, const uint8_t* data2, uint32_t return errs; } + +/* Helper to convert a denial reason code to a string. */ + +std::string P25Utils::denyRsnToString(uint8_t reason) +{ + switch (reason) { + case ReasonCode::DENY_REQ_UNIT_NOT_VALID: + return std::string("DENY_REQ_UNIT_NOT_VALID (Requesting Unit Not Valid)"); + case ReasonCode::DENY_REQ_UNIT_NOT_AUTH: + return std::string("DENY_REQ_UNIT_NOT_AUTH (Requesting Unit Not Authenticated)"); + + case ReasonCode::DENY_TGT_UNIT_NOT_VALID: + return std::string("DENY_TGT_UNIT_NOT_VALID (Target Unit Not Valid)"); + case ReasonCode::DENY_TGT_UNIT_NOT_AUTH: + return std::string("DENY_TGT_UNIT_NOT_AUTH (Target Unit Not Authenticated)"); + case ReasonCode::DENY_SU_FAILED_AUTH: + return std::string("DENY_SU_FAILED_AUTH (Target Unit Failed Authentication)"); + case ReasonCode::DENY_TGT_UNIT_REFUSED: + return std::string("DENY_TGT_UNIT_REFUSED (Target Unit Refused)"); + + case ReasonCode::DENY_TGT_GROUP_NOT_VALID: + return std::string("DENY_TGT_GROUP_NOT_VALID (Target Group Not Valid)"); + case ReasonCode::DENY_TGT_GROUP_NOT_AUTH: + return std::string("DENY_TGT_GROUP_NOT_AUTH (Target Group Not Authenticated)"); + + case ReasonCode::DENY_NO_NET_RSRC_AVAIL: + return std::string("DENY_NO_NET_RSRC_AVAIL (Requested Network Resources Not Available)"); + case ReasonCode::DENY_NO_RF_RSRC_AVAIL: + return std::string("DENY_NO_RF_RSRC_AVAIL (Requested RF Resources Not Available)"); + case ReasonCode::DENY_SVC_IN_USE: + return std::string("DENY_SVC_IN_USE (Service In Use)"); + + case ReasonCode::DENY_SITE_ACCESS_DENIAL: + return std::string("DENY_SITE_ACCESS_DENIAL (Site Access Denial)"); + + case ReasonCode::DENY_PTT_COLLIDE: + return std::string("DENY_PTT_COLLIDE (Push-to-Talk Collision)"); + case ReasonCode::DENY_PTT_BONK: + return std::string("DENY_PTT_BONK (Push-to-Talk Denial/Bonk)"); + + case ReasonCode::DENY_SYS_UNSUPPORTED_SVC: + return std::string("DENY_SYS_UNSUPPORTED_SVC (Service Unsupported)"); + + default: + return std::string(); + } +} + +/* Helper to convert a queue reason code to a string. */ + +std::string P25Utils::queueRsnToString(uint8_t reason) +{ + switch (reason) { + case ReasonCode::QUE_REQ_ACTIVE_SERVICE: + return std::string("QUE_REQ_ACTIVE_SERVICE (Requested Service Active)"); + case ReasonCode::QUE_TGT_ACTIVE_SERVICE: + return std::string("QUE_TGT_ACTIVE_SERVICE (Target Service Active)"); + + case ReasonCode::QUE_TGT_UNIT_QUEUED: + return std::string("QUE_TGT_UNIT_QUEUED (Target Unit Queued)"); + + case ReasonCode::QUE_CHN_RESOURCE_NOT_AVAIL: + return std::string("QUE_CHN_RESOURCE_NOT_AVAIL (Channel Resource Not Available)"); + + default: + return std::string(); + } +} diff --git a/src/common/p25/P25Utils.h b/src/common/p25/P25Utils.h index bc030a43a..66b4cb27c 100644 --- a/src/common/p25/P25Utils.h +++ b/src/common/p25/P25Utils.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2021 Bryan Biedenkapp, N2PLL + * Copyright (C) 2021-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -198,6 +198,19 @@ namespace p25 * @returns uint32_t */ static uint32_t compare(const uint8_t* data1, const uint8_t* data2, uint32_t length); + + /** + * @brief Helper to convert a denial reason code to a string. + * @param reason Reason code. + * @returns std::string Reason code string. + */ + static std::string denyRsnToString(uint8_t reason); + /** + * @brief Helper to convert a queue reason code to a string. + * @param reason Reason code. + * @returns std::string Reason code string. + */ + static std::string queueRsnToString(uint8_t reason); }; } // namespace p25 diff --git a/src/common/p25/acl/AccessControl.cpp b/src/common/p25/acl/AccessControl.cpp index 881902bc8..2c0fd7915 100644 --- a/src/common/p25/acl/AccessControl.cpp +++ b/src/common/p25/acl/AccessControl.cpp @@ -53,12 +53,16 @@ bool AccessControl::validateSrcId(uint32_t id) /* Helper to validate a talkgroup ID. */ -bool AccessControl::validateTGId(uint32_t id) +bool AccessControl::validateTGId(uint32_t id, bool allowZero) { // TG0 is never valid - if (id == 0U) + if (id == 0U && !allowZero) return false; + // TG0 is always valid if allow zero is set + if (id == 0U && allowZero) + return true; + // check if TID ACLs are enabled if (!m_tidLookup->getACL()) { return true; diff --git a/src/common/p25/acl/AccessControl.h b/src/common/p25/acl/AccessControl.h index b14593b15..7f1baf76d 100644 --- a/src/common/p25/acl/AccessControl.h +++ b/src/common/p25/acl/AccessControl.h @@ -54,9 +54,10 @@ namespace p25 /** * @brief Helper to validate a talkgroup ID. * @param id Talkgroup ID (TGID). + * @param allowZero Flag indicating whether TGID 0 is allowed or not. * @returns bool True, if talkgroup ID is valid, otherwise false. */ - static bool validateTGId(uint32_t id); + static bool validateTGId(uint32_t id, bool allowZero = false); /** * @brief Helper to determine if a talkgroup ID is non-preferred. diff --git a/src/common/p25/data/DataBlock.cpp b/src/common/p25/data/DataBlock.cpp index cf6b44bc5..e750ef32b 100644 --- a/src/common/p25/data/DataBlock.cpp +++ b/src/common/p25/data/DataBlock.cpp @@ -128,7 +128,7 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) ::memcpy(m_data, buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data } catch (...) { - Utils::dump(2U, "P25, decoding excepted with input data", data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + Utils::dump(2U, "P25, DataBlock::decode(), decoding excepted with input data", data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); return false; } } diff --git a/src/common/p25/data/DataHeader.cpp b/src/common/p25/data/DataHeader.cpp index 923ded10e..211953dac 100644 --- a/src/common/p25/data/DataHeader.cpp +++ b/src/common/p25/data/DataHeader.cpp @@ -225,6 +225,9 @@ void DataHeader::encode(uint8_t* data, bool noTrellis) header[1U] = ((m_rspClass & 0x03U) << 6) + // Response Class ((m_rspType & 0x07U) << 3) + // Response Type ((m_rspStatus & 0x07U)); // Response Status + + // the "full message" flag in a response PDU indicates whether or not the response is to an + // extended addressing PDU if (!m_F) { header[7U] = (m_srcLlId >> 16) & 0xFFU; // Source Logical Link ID header[8U] = (m_srcLlId >> 8) & 0xFFU; diff --git a/src/common/p25/data/DataHeader.h b/src/common/p25/data/DataHeader.h index 97164d6b2..d59c8c5c4 100644 --- a/src/common/p25/data/DataHeader.h +++ b/src/common/p25/data/DataHeader.h @@ -160,6 +160,8 @@ namespace p25 DECLARE_PROPERTY(uint8_t, padLength, PadLength); /** * @brief Flag indicating whether or not this data packet is a full message. + * @note This is used on extended addressing response packets to indicate whether or not + * the response is for a extended addressing request. */ DECLARE_PROPERTY(bool, F, FullMessage); /** @@ -183,7 +185,7 @@ namespace p25 */ DECLARE_PROPERTY(uint8_t, headerOffset, HeaderOffset); - // Extended Addressing Data + /** @name Symmetric Addressing Data */ /** * @brief Service access point. */ @@ -192,8 +194,9 @@ namespace p25 * @brief Source Logical link ID. */ DECLARE_PROPERTY(uint32_t, srcLlId, SrcLLId); + /** @} */ - // Response Data + /** @name Response Packet Data */ /** * @brief Response class. */ @@ -206,8 +209,9 @@ namespace p25 * @brief Response status. */ DECLARE_PROPERTY(uint8_t, rspStatus, ResponseStatus); + /** @} */ - // AMBT Data + /** @name AMBT Packet Data */ /** * @brief Alternate Trunking Block Opcode */ @@ -220,6 +224,7 @@ namespace p25 * @brief Alternate Trunking Block Field 9 */ DECLARE_PROPERTY(uint8_t, ambtField9, AMBTField9); + /** @} */ private: edac::Trellis m_trellis; diff --git a/src/common/p25/data/LowSpeedData.cpp b/src/common/p25/data/LowSpeedData.cpp index 83891b499..1cd6c111a 100644 --- a/src/common/p25/data/LowSpeedData.cpp +++ b/src/common/p25/data/LowSpeedData.cpp @@ -106,7 +106,7 @@ void LowSpeedData::process(uint8_t* data) } } - // Utils::dump(1U, "P25 Low Speed Data", lsd, 4U); + // Utils::dump(1U, "P25, LowSpeedData::process(), Low Speed Data", lsd, 4U); m_lsd1 = lsd[0U]; m_lsd2 = lsd[2U]; diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index bf78aff63..9630cd993 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.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) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -43,6 +43,16 @@ namespace p25 const uint32_t DFSI_VHDR_RAW_LEN = 36U; const uint32_t DFSI_VHDR_LEN = 27U; + const uint32_t DFSI_MOT_START_LEN = 9U; + const uint32_t DFSI_MOT_VHDR_1_LEN = 30U; + const uint32_t DFSI_MOT_VHDR_2_LEN = 22U; + const uint32_t DFSI_MOT_TSBK_LEN = 24U; + const uint32_t DFSI_MOT_TDULC_LEN = 21U; + + const uint32_t DFSI_TIA_VHDR_LEN = 22U; + + const uint32_t DFSI_MOT_ICW_LENGTH = 6U; + const uint32_t DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES = 22U; const uint32_t DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES = 14U; const uint32_t DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES = 17U; @@ -68,31 +78,35 @@ namespace p25 * @{ */ - const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! - - const uint8_t DFSI_STATUS_NO_ERROR = 0x00U; //! - const uint8_t DFSI_STATUS_ERASE = 0x02U; //! + const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! + const uint8_t DFSI_RTP_MOT_PAYLOAD_TYPE = 0x5DU; //! - const uint8_t DFSI_RT_ENABLED = 0x02U; //! - const uint8_t DFSI_RT_DISABLED = 0x04U; //! + const uint8_t DFSI_RTP_SEQ_HANDSHAKE = 0x00U; //! + const uint8_t DFSI_RTP_SEQ_STARTSTOP = 0x01U; //! - const uint8_t DFSI_START_FLAG = 0x0CU; //! - const uint8_t DFSI_STOP_FLAG = 0x25U; //! + const uint8_t DFSI_MOT_ICW_FMT_TYPE3 = 0x02U; //! - const uint8_t DFSI_TYPE_DATA_PAYLOAD = 0x06U; //! - const uint8_t DFSI_TYPE_VOICE = 0x0BU; //! + const uint8_t DFSI_MOT_ICW_PARM_NOP = 0x00U; //! No Operation + const uint8_t DSFI_MOT_ICW_PARM_PAYLOAD = 0x0CU; //! Stream Payload + const uint8_t DFSI_MOT_ICW_PARM_RSSI1 = 0x1AU; //! RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_RSSI2 = 0x1BU; //! RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //! Stop Stream + const uint8_t DFSI_MOT_ICW_TX_ADDRESS = 0x2CU; //! Tx Device Address + const uint8_t DFSI_MOT_ICW_RX_ADDRESS = 0x35U; //! Rx Device Address - const uint8_t DFSI_DEF_ICW_SOURCE = 0x00U; //! Infrastructure Source - Default Source - const uint8_t DFSI_DEF_SOURCE = 0x00U; //! + const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //! Talkaround + const uint8_t DFSI_BUSY_BITS_BUSY = 0x01U; //! Busy + const uint8_t DFSI_BUSY_BITS_INBOUND = 0x02U; //! Inbound + const uint8_t DFSI_BUSY_BITS_IDLE = 0x03U; //! Idle /** @brief DFSI Frame Type */ namespace DFSIFrameType { /** @brief DFSI Frame Type */ enum E : uint8_t { - MOT_START_STOP = 0x00U, // Motorola Start/Stop + MOT_START_STOP = 0x00U, // Motorola/V.24 Start/Stop Stream - MOT_VHDR_1 = 0x60U, // Motorola Voice Header 1 - MOT_VHDR_2 = 0x61U, // Motorola Voice Header 2 + MOT_VHDR_1 = 0x60U, // Motorola/V.24 Voice Header 1 + MOT_VHDR_2 = 0x61U, // Motorola/V.24 Voice Header 2 LDU1_VOICE1 = 0x62U, // IMBE LDU1 - Voice 1 LDU1_VOICE2 = 0x63U, // IMBE LDU1 - Voice 2 @@ -114,8 +128,9 @@ namespace p25 LDU2_VOICE17 = 0x72U, // IMBE LDU2 - Voice 17 + Encryption Sync LDU2_VOICE18 = 0x73U, // IMBE LDU2 - Voice 18 + Low Speed Data - PDU = 0x87U, // PDU - TSBK = 0xA1U // TSBK + MOT_TDULC = 0x74U, // Motorola/V.24 TDULC + MOT_PDU_SINGLE = 0x87U, // Motorola/V.24 PDU (Single Block) + MOT_TSBK = 0xA1U // Motorola/V.24 TSBK (Single Block) }; } diff --git a/src/common/p25/dfsi/LC.cpp b/src/common/p25/dfsi/LC.cpp index bfd9cfe28..eaa3ee3a5 100644 --- a/src/common/p25/dfsi/LC.cpp +++ b/src/common/p25/dfsi/LC.cpp @@ -404,8 +404,8 @@ void LC::encodeLDU1(uint8_t* data, const uint8_t* imbe) } #if DEBUG_P25_DFSI - LogDebugEx(LOG_P25, "LC::encodeLDU1()", "frameType = $%02X", m_frameType); - Utils::dump(2U, "[LC::encodeLDU1()] DFSI LDU1 Frame", dfsiFrame, frameLength); + LogDebugEx(LOG_P25, "dfsi::LC::encodeLDU1()", "frameType = $%02X", m_frameType); + Utils::dump(2U, "P25, dfsi::LC::encodeLDU1(), DFSI LDU1 Frame", dfsiFrame, frameLength); #endif ::memcpy(data, dfsiFrame, frameLength); @@ -644,8 +644,8 @@ void LC::encodeLDU2(uint8_t* data, const uint8_t* imbe) } #if DEBUG_P25_DFSI - LogDebugEx(LOG_P25, "LC::encodeLDU2()", "frameType = $%02X", m_frameType); - Utils::dump(2U, "[LC::encodeLDU2()] DFSI LDU2 Frame", dfsiFrame, frameLength); + LogDebugEx(LOG_P25, "dfsi::LC::encodeLDU2()", "frameType = $%02X", m_frameType); + Utils::dump(2U, "P25, dfsi::LC::encodeLDU2(), DFSI LDU2 Frame", dfsiFrame, frameLength); #endif ::memcpy(data, dfsiFrame, frameLength); diff --git a/src/common/p25/dfsi/frames/FrameDefines.h b/src/common/p25/dfsi/frames/FrameDefines.h index a2db0b9ef..eb8c01bf3 100644 --- a/src/common/p25/dfsi/frames/FrameDefines.h +++ b/src/common/p25/dfsi/frames/FrameDefines.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -91,57 +91,23 @@ namespace p25 }; } - /** @brief RT/RT Flag */ - namespace RTFlag { - /** @brief RT/RT Flag */ + /** @brief Motorola Start of Stream Operation */ + namespace MotStartStreamOpcode { + /** @brief Motorola Start of Stream Operation */ enum E : uint8_t { - ENABLED = 0x02U, //! RT/RT Enabled - DISABLED = 0x04U //! RT/RT Disabled + TRANSMIT = 0x02U, //! Transmit + RECEIVE = 0x04U, //! Receive }; } - /** @brief Start/Stop Flag */ - namespace StartStopFlag { - /** @brief Start/Stop Flag */ + /** @brief Motorola Stream Payload */ + namespace MotStreamPayload { + /** @brief Motorola Stream Payload */ enum E : uint8_t { - START = 0x0CU, //! Start - STOP = 0x25U //! Stop - }; - } - - /** @brief V.24 Data Stream Type */ - namespace StreamTypeFlag { - /** @brief V.24 Data Stream Type */ - enum E : uint8_t { - VOICE = 0x0BU, //! Voice - TSBK = 0x0FU //! TSBK - }; - } - - /** @brief RSSI Data Validity */ - namespace RssiValidityFlag { - /** @brief RSSI Data Validity */ - enum E : uint8_t { - INVALID = 0x00U, //! Invalid - VALID = 0x1A //! Valid - }; - } - - /** @brief V.24 Data Source */ - namespace SourceFlag { - /** @brief V.24 Data Source */ - enum E : uint8_t { - DIU = 0x00U, //! DIU - QUANTAR = 0x02U //! Quantar - }; - } - - /** @brief */ - namespace ICWFlag { - /** @brief */ - enum E : uint8_t { - DIU = 0x00U, //! DIU - QUANTAR = 0x1B //! Quantar + VOICE = 0x0BU, //! P25 Voice + DATA = 0x0CU, //! P25 Data + TERM_LC = 0x0EU, //! P25 Termination Link Control + TSBK = 0x0FU //! P25 TSBK }; } /** @} */ diff --git a/src/common/p25/dfsi/frames/Frames.h b/src/common/p25/dfsi/frames/Frames.h index 7779b8d14..498546e4d 100644 --- a/src/common/p25/dfsi/frames/Frames.h +++ b/src/common/p25/dfsi/frames/Frames.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #if !defined(__DFSI_FRAMES_H__) @@ -20,13 +20,11 @@ #include "common/p25/dfsi/frames/FullRateVoice.h" // "The" Manufacturer -#include "common/p25/dfsi/frames/MotFullRateVoice.h" #include "common/p25/dfsi/frames/MotStartOfStream.h" #include "common/p25/dfsi/frames/MotStartVoiceFrame.h" -#include "common/p25/dfsi/frames/MotVoiceHeader1.h" -#include "common/p25/dfsi/frames/MotVoiceHeader2.h" +#include "common/p25/dfsi/frames/MotFullRateVoice.h" +#include "common/p25/dfsi/frames/MotTDULCFrame.h" #include "common/p25/dfsi/frames/MotTSBKFrame.h" -#include "common/p25/dfsi/frames/MotPDUFrame.h" // FSC #include "common/p25/dfsi/frames/fsc/FSCMessage.h" diff --git a/src/common/p25/dfsi/frames/FullRateVoice.cpp b/src/common/p25/dfsi/frames/FullRateVoice.cpp index d334e84f4..b9c677073 100644 --- a/src/common/p25/dfsi/frames/FullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/FullRateVoice.cpp @@ -34,7 +34,7 @@ FullRateVoice::FullRateVoice() : m_muteFrame(false), m_lostFrame(false), m_superframeCnt(0U), - m_busy(0U) + m_busy(DFSI_BUSY_BITS_TALKAROUND) { imbeData = new uint8_t[IMBE_BUF_LEN]; ::memset(imbeData, 0x00U, IMBE_BUF_LEN); @@ -52,7 +52,7 @@ FullRateVoice::FullRateVoice(uint8_t* data) : m_muteFrame(false), m_lostFrame(false), m_superframeCnt(0U), - m_busy(0U) + m_busy(DFSI_BUSY_BITS_TALKAROUND) { decode(data); } diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.cpp b/src/common/p25/dfsi/frames/MotFullRateVoice.cpp index 542ff3273..552795586 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.cpp +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/MotFullRateVoice.h" @@ -33,7 +33,8 @@ MotFullRateVoice::MotFullRateVoice() : imbeData(nullptr), additionalData(nullptr), m_frameType(DFSIFrameType::LDU1_VOICE1), - m_source(SourceFlag::QUANTAR) + m_totalErrors(0U), + m_busy(DFSI_BUSY_BITS_TALKAROUND) { imbeData = new uint8_t[RAW_IMBE_LENGTH_BYTES]; ::memset(imbeData, 0x00U, RAW_IMBE_LENGTH_BYTES); @@ -41,11 +42,17 @@ MotFullRateVoice::MotFullRateVoice() : /* Initializes a instance of the MotFullRateVoice class. */ -MotFullRateVoice::MotFullRateVoice(uint8_t* data) +MotFullRateVoice::MotFullRateVoice(uint8_t* data) : + imbeData(nullptr), + additionalData(nullptr), + m_frameType(DFSIFrameType::LDU1_VOICE1), + m_totalErrors(0U), + m_busy(DFSI_BUSY_BITS_TALKAROUND) { // set our pointers to null since it doesn't get initialized otherwise imbeData = nullptr; additionalData = nullptr; + // decode decode(data); } @@ -100,11 +107,14 @@ bool MotFullRateVoice::decode(const uint8_t* data, bool shortened) if (shortened) { ::memcpy(imbeData, data + 1U, RAW_IMBE_LENGTH_BYTES); - m_source = (SourceFlag::E)data[12U]; - // Forgot to set this originally and left additionalData uninitialized, whoops! + + m_totalErrors = (uint8_t)((data[12U] >> 3) & 0x0FU); // Total Errors + m_busy = (uint8_t)(data[13U] & 0x03U); // Busy Status + + // forgot to set this originally and left additionalData uninitialized, whoops! additionalData = nullptr; } else { - // Frames 0x6A and 0x73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier + // frames $6A and $73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier uint8_t imbeStart = 5U; if (isVoice9or18()) { imbeStart = 4U; @@ -119,7 +129,13 @@ bool MotFullRateVoice::decode(const uint8_t* data, bool shortened) // copy IMBE data based on our imbe start position ::memcpy(imbeData, data + imbeStart, RAW_IMBE_LENGTH_BYTES); - m_source = (SourceFlag::E)data[RAW_IMBE_LENGTH_BYTES + imbeStart]; + if (isVoice9or18()) { + m_totalErrors = 0U; // these frames don't have total errors + m_busy = (uint8_t)(data[3U] & 0x03U); // Busy Status + } else { + m_totalErrors = (uint8_t)((data[imbeStart + RAW_IMBE_LENGTH_BYTES] >> 2) & 0x0FU); // Total Errors + m_busy = (uint8_t)(data[imbeStart + RAW_IMBE_LENGTH_BYTES] & 0x03U); // Busy Status + } } return true; @@ -141,26 +157,28 @@ void MotFullRateVoice::encode(uint8_t* data, bool shortened) // copy based on shortened frame or not if (shortened) { ::memcpy(data + 1U, imbeData, RAW_IMBE_LENGTH_BYTES); - data[12U] = (uint8_t)m_source; - } + data[13U] = (uint8_t)(m_busy & 0x03U); // Busy Status + } // if not shortened, our IMBE data start position depends on frame type else { - // Starting index for the IMBE data + // starting index for the IMBE data uint8_t imbeStart = 5U; if (isVoice9or18()) { imbeStart = 4U; } - // Check if we have additional data + // check if we have additional data if (additionalData != nullptr) { ::memcpy(data + 1U, additionalData, ADDITIONAL_LENGTH); } - // Copy rest of data ::memcpy(data + imbeStart, imbeData, RAW_IMBE_LENGTH_BYTES); - // Source byte at the end - data[11U + imbeStart] = (uint8_t)m_source; + if (isVoice9or18()) { + data[3U] = (uint8_t)(m_busy & 0x03U); // Busy Status + } else { + data[imbeStart + RAW_IMBE_LENGTH_BYTES] = (uint8_t)(m_busy & 0x03U); // Busy Status + } } } diff --git a/src/common/p25/dfsi/frames/MotFullRateVoice.h b/src/common/p25/dfsi/frames/MotFullRateVoice.h index 51da71522..06c43616e 100644 --- a/src/common/p25/dfsi/frames/MotFullRateVoice.h +++ b/src/common/p25/dfsi/frames/MotFullRateVoice.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -35,7 +35,7 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola full rate voice packet. + * @brief Implements a P25 Motorola/V.24 full rate voice packet. * \code{.unparsed} * 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 @@ -48,7 +48,7 @@ namespace p25 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Src Flag | + * | Busy Bits | * +=+=+=+=+=+=+=+=+ * \endcode * @ingroup dfsi_frames @@ -99,9 +99,13 @@ namespace p25 */ DECLARE_PROPERTY(defines::DFSIFrameType::E, frameType, FrameType); /** - * @brief V.24 Data Source. + * @brief Total errors detected in the frame. */ - DECLARE_PROPERTY(SourceFlag::E, source, Source); + DECLARE_PROPERTY(uint8_t, totalErrors, TotalErrors); + /** + * @brief Busy Status. + */ + DECLARE_PROPERTY(uint8_t, busy, Busy); private: /** diff --git a/src/common/p25/dfsi/frames/MotPDUFrame.cpp b/src/common/p25/dfsi/frames/MotPDUFrame.cpp deleted file mode 100644 index eb79ee9fa..000000000 --- a/src/common/p25/dfsi/frames/MotPDUFrame.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/P25Defines.h" -#include "common/p25/dfsi/frames/MotPDUFrame.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25; -using namespace p25::defines; -using namespace p25::dfsi; -using namespace p25::dfsi::defines; -using namespace p25::dfsi::frames; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the MotPDUFrame class. */ - -MotPDUFrame::MotPDUFrame() : - startOfStream(nullptr), - pduHeaderData(nullptr) -{ - pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES]; - ::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); - - startOfStream = new MotStartOfStream(); -} - -/* Initializes a instance of the MotPDUFrame class. */ - -MotPDUFrame::MotPDUFrame(uint8_t* data) : - startOfStream(nullptr), - pduHeaderData(nullptr) -{ - pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES]; - ::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); - - decode(data); -} - -/* Finalizes a instance of the MotPDUFrame class. */ - -MotPDUFrame::~MotPDUFrame() -{ - if (startOfStream != nullptr) - delete startOfStream; - if (pduHeaderData != nullptr) - delete[] pduHeaderData; -} - -/* Decode a PDU frame. (only the PDU data header...) */ - -bool MotPDUFrame::decode(const uint8_t* data) -{ - assert(data != nullptr); - - // create a new start of stream - if (startOfStream != nullptr) - delete startOfStream; - startOfStream = new MotStartOfStream(); - - // create a buffer to decode the start record skipping the 10th byte (adjMM) - uint8_t startBuffer[MotStartOfStream::LENGTH]; - ::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); - ::memcpy(startBuffer + 1U, data, 4U); - - // decode start of stream - startOfStream->decode(startBuffer); - - ::memcpy(pduHeaderData, data + 9U, P25_PDU_HEADER_LENGTH_BYTES); - - return true; -} - -/* Encode a PDU frame. (only the PDU data header...) */ - -void MotPDUFrame::encode(uint8_t* data) -{ - assert(data != nullptr); - assert(startOfStream != nullptr); - - // encode start of stream - scope is intentional - { - uint8_t buffer[MotStartOfStream::LENGTH]; - startOfStream->encode(buffer); - - // copy to data array (skipping first and last bytes) - ::memcpy(data + 1U, buffer + 1U, 4U); - } - - // encode PDU - scope is intentional - { - data[0U] = DFSIFrameType::PDU; - ::memcpy(data + 9U, pduHeaderData, P25_PDU_HEADER_LENGTH_BYTES); - } -} diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.cpp b/src/common/p25/dfsi/frames/MotStartOfStream.cpp index 0ce7e6cd5..edabe4be4 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.cpp +++ b/src/common/p25/dfsi/frames/MotStartOfStream.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/MotStartOfStream.h" @@ -28,34 +28,44 @@ using namespace p25::dfsi::frames; /* Initializes a instance of the MotStartOfStream class. */ MotStartOfStream::MotStartOfStream() : - m_marker(FIXED_MARKER), - m_rt(RTFlag::DISABLED), - m_startStop(StartStopFlag::START), - m_streamType(StreamTypeFlag::VOICE) + m_format(DFSI_MOT_ICW_FMT_TYPE3), + m_opcode(MotStartStreamOpcode::TRANSMIT), + icw(nullptr) { - /* stub */ + icw = new uint8_t[DFSI_MOT_ICW_LENGTH]; + ::memset(icw, 0x00U, DFSI_MOT_ICW_LENGTH); } /* Initializes a instance of the MotStartOfStream class. */ MotStartOfStream::MotStartOfStream(uint8_t* data) : - m_marker(FIXED_MARKER), - m_rt(RTFlag::DISABLED), - m_startStop(StartStopFlag::START), - m_streamType(StreamTypeFlag::VOICE) + m_format(DFSI_MOT_ICW_FMT_TYPE3), + m_opcode(MotStartStreamOpcode::TRANSMIT), + icw(nullptr) { + icw = new uint8_t[DFSI_MOT_ICW_LENGTH]; + ::memset(icw, 0x00U, DFSI_MOT_ICW_LENGTH); + decode(data); } +/* Finalizes a instance of the MotStartOfStream class. */ + +MotStartOfStream::~MotStartOfStream() +{ + if (icw != nullptr) + delete icw; +} + /* Decode a start of stream frame. */ bool MotStartOfStream::decode(const uint8_t* data) { assert(data != nullptr); - m_rt = (RTFlag::E)data[2U]; - m_startStop = (StartStopFlag::E)data[3U]; - m_streamType = (StreamTypeFlag::E)data[4U]; + m_format = data[1U] & 0x3FU; + m_opcode = (MotStartStreamOpcode::E)data[2U]; + ::memcpy(icw, data + 3U, DFSI_MOT_ICW_LENGTH); return true; } @@ -67,8 +77,7 @@ void MotStartOfStream::encode(uint8_t* data) assert(data != nullptr); data[0U] = DFSIFrameType::MOT_START_STOP; - data[1U] = FIXED_MARKER; - data[2U] = m_rt; - data[3U] = m_startStop; - data[4U] = m_streamType; + data[1U] = m_format & 0x3FU; + data[2U] = m_opcode; + ::memcpy(data + 3U, icw, DFSI_MOT_ICW_LENGTH); } diff --git a/src/common/p25/dfsi/frames/MotStartOfStream.h b/src/common/p25/dfsi/frames/MotStartOfStream.h index 14f4897b0..2d23868ca 100644 --- a/src/common/p25/dfsi/frames/MotStartOfStream.h +++ b/src/common/p25/dfsi/frames/MotStartOfStream.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -34,25 +34,22 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola start of stream packet. + * @brief Implements a P25 Motorola/V.24 start of stream packet. * \code{.unparsed} * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Fixed Mark | RT Mode Flag | Start/Stop | Type Flag | + * | FT | ICW Format | Opcode | Param 1 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | | + * | Argment 1 | Param 2 | Argument 2 | Param 3 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Argment 3 | * +-+-+-+-+-+-+-+-+ * \endcode * @ingroup dfsi_frames */ class HOST_SW_API MotStartOfStream { public: - static const uint8_t LENGTH = 10U; - static const uint8_t FIXED_MARKER = 0x02U; - /** * @brief Initializes a copy instance of the MotStartOfStream class. */ @@ -62,6 +59,10 @@ namespace p25 * @param data Buffer to containing MotStartOfStream to decode. */ MotStartOfStream(uint8_t* data); + /** + * @brief Finalizes a instance of the MotStartOfStream class. + */ + ~MotStartOfStream(); /** * @brief Decode a start of stream frame. @@ -73,24 +74,92 @@ namespace p25 * @param[out] data Buffer to encode a MotStartOfStream. */ void encode(uint8_t* data); - - public: + + /** @name Start of Stream Type 3 control word parameters. */ + /** + * @brief Helper to get parameter 1 of the control word. + * @return uint8_t Parameter 1 of the control word. + */ + uint8_t getParam1() const { return icw[0U]; } /** - * @brief + * @brief Helper to get the argument for parameter 1 of the control word. + * @return uint8_t Argument 1 for parameter 1 of the control word. */ - DECLARE_PROPERTY(uint8_t, marker, Marker); + uint8_t getArgument1() const { return icw[1U]; } /** - * @brief RT/RT Flag. + * @brief Helper to set parameter 1 of the control word. + * @param value Parameter 1 of the control word. */ - DECLARE_PROPERTY(RTFlag::E, rt, RT); + void setParam1(uint8_t value) { icw[0U] = value; } /** - * @brief Start/Stop. + * @brief Helper to set the argument for parameter 1 of the control word. + * @param value Argument 1 for parameter 1 of the control word. */ - DECLARE_PROPERTY(StartStopFlag::E, startStop, StartStop); + void setArgument1(uint8_t value) { icw[1U] = value; } + /** - * @brief Stream Type. + * @brief Helper to get parameter 2 of the control word. + * @return uint8_t Parameter 2 of the control word. */ - DECLARE_PROPERTY(StreamTypeFlag::E, streamType, StreamType); + uint8_t getParam2() const { return icw[2U]; } + /** + * @brief Helper to get the argument for parameter 2 of the control word. + * @return uint8_t Argument 2 for parameter 2 of the control word. + */ + uint8_t getArgument2() const { return icw[3U]; } + /** + * @brief Helper to set parameter 1 of the control word. + * @param value Parameter 1 of the control word. + */ + void setParam2(uint8_t value) { icw[2U] = value; } + /** + * @brief Helper to set the argument for parameter 2 of the control word. + * @param value Argument for parameter 2 of the control word. + */ + void setArgument2(uint8_t value) { icw[3U] = value; } + + /** + * @brief Helper to get parameter 3 of the control word. + * @return uint8_t Parameter 3 of the control word. + */ + uint8_t getParam3() const { return icw[4U]; } + /** + * @brief Helper to get argument 3 for parameter 3 of the control word. + * @return uint8_t Argument for parameter 3 of the control word. + */ + uint8_t getArgument3() const { return icw[5U]; } + /** + * @brief Helper to set parameter 3 of the control word. + * @param value Parameter 3 of the control word. + */ + void setParam3(uint8_t value) { icw[4U] = value; } + /** + * @brief Helper to set the argument for parameter 3 of the control word. + * @param value Argument for parameter 3 of the control word. + */ + void setArgument3(uint8_t value) { icw[5U] = value; } + /** @} */ + + /** + * @brief Get the raw ICW parameter/argument buffer. + * @return uint8_t* Raw ICW buffer. + * @note The buffer is 6 bytes long and contains the parameters and arguments for the + * start of stream control word. + */ + uint8_t* getICW() const { return icw; } + + public: + /** + * @brief Format. + */ + DECLARE_PROPERTY(uint8_t, format, Format); + /** + * @brief Opcode. + */ + DECLARE_PROPERTY(MotStartStreamOpcode::E, opcode, Opcode); + + private: + uint8_t* icw; }; } // namespace frames } // namespace dfsi diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp b/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp index 91070f4fd..5a899ec5d 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "common/p25/dfsi/frames/MotStartVoiceFrame.h" @@ -30,11 +30,7 @@ using namespace p25::dfsi::frames; MotStartVoiceFrame::MotStartVoiceFrame() : startOfStream(nullptr), fullRateVoice(nullptr), - m_icw(ICWFlag::DIU), - m_rssi(0U), - m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U), - m_adjMM(0U) + m_totalErrors(0U) { startOfStream = new MotStartOfStream(); fullRateVoice = new MotFullRateVoice(); @@ -45,11 +41,7 @@ MotStartVoiceFrame::MotStartVoiceFrame() : MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data) : startOfStream(nullptr), fullRateVoice(nullptr), - m_icw(ICWFlag::DIU), - m_rssi(0U), - m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U), - m_adjMM(0U) + m_totalErrors(0U) { decode(data); } @@ -76,9 +68,9 @@ bool MotStartVoiceFrame::decode(const uint8_t* data) startOfStream = new MotStartOfStream(); // create a buffer to decode the start record skipping the 10th byte (adjMM) - uint8_t startBuffer[MotStartOfStream::LENGTH]; - ::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); - ::memcpy(startBuffer, data, 9U); + uint8_t startBuffer[DFSI_MOT_START_LEN]; + ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN); + ::memcpy(startBuffer, data, DFSI_MOT_START_LEN); // decode start of stream startOfStream->decode(startBuffer); @@ -94,13 +86,6 @@ bool MotStartVoiceFrame::decode(const uint8_t* data) ::memcpy(voiceBuffer + 1U, data + 10U, MotFullRateVoice::SHORTENED_LENGTH - 1); fullRateVoice->decode(voiceBuffer, true); - // get rest of data - m_icw = (ICWFlag::E)data[5U]; - m_rssi = data[6U]; - m_rssiValidity = (RssiValidityFlag::E)data[7U]; - m_nRssi = data[8U]; - m_adjMM = data[9U]; - return true; } @@ -114,11 +99,11 @@ void MotStartVoiceFrame::encode(uint8_t* data) // encode start of stream - scope is intentional { - uint8_t buffer[MotStartOfStream::LENGTH]; + uint8_t buffer[DFSI_MOT_START_LEN]; startOfStream->encode(buffer); - // copy to data array (skipping first and last bytes) - ::memcpy(data + 1U, buffer + 1U, MotStartOfStream::LENGTH - 2); + // copy to data array (skipping first byte which is frame type) + ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U); } // encode full rate voice - scope is intentional @@ -129,11 +114,4 @@ void MotStartVoiceFrame::encode(uint8_t* data) data[0U] = fullRateVoice->getFrameType(); ::memcpy(data + 10U, buffer + 1U, MotFullRateVoice::SHORTENED_LENGTH - 1); } - - // Copy the rest - data[5U] = m_icw; - data[6U] = m_rssi; - data[7U] = m_rssiValidity; - data[8U] = m_nRssi; - data[9U] = m_adjMM; } diff --git a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h index 0f2877240..66df6bf98 100644 --- a/src/common/p25/dfsi/frames/MotStartVoiceFrame.h +++ b/src/common/p25/dfsi/frames/MotStartVoiceFrame.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -36,21 +36,21 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola voice frame 1/10 start. + * @brief Implements a P25 Motorola/V.24 voice frame 1/10 start. * \code{.unparsed} * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | + * | FT | Encoded V.24 Start of Stream | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+ + * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | ICW Flag ? | RSSI | RSSI Valid | RSSI | + * | | Full Rate Voice Frame | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Adj MM ? | Full Rate Voice Frame | - * +-+-+-+-+-+-+-+-+ + * | | - * + + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +=+=+=+=+=+=+=+=+ * \endcode @@ -90,25 +90,9 @@ namespace p25 MotFullRateVoice* fullRateVoice; // ?? - this should probably be private with getters/setters /** - * @brief - */ - DECLARE_PROPERTY(ICWFlag::E, icw, ICW); - /** - * @brief RSSI Value. - */ - DECLARE_PROPERTY(uint8_t, rssi, RSSI); - /** - * @brief Flag indicating whether or not the RSSI field is valid. - */ - DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity); - /** - * @brief - */ - DECLARE_PROPERTY(uint8_t, nRssi, NRSSI); - /** - * @brief + * @brief Total errors detected in the frame. */ - DECLARE_PROPERTY(uint8_t, adjMM, AdjMM); + DECLARE_PROPERTY(uint8_t, totalErrors, TotalErrors); }; } // namespace frames } // namespace dfsi diff --git a/src/common/p25/dfsi/frames/MotTDULCFrame.cpp b/src/common/p25/dfsi/frames/MotTDULCFrame.cpp new file mode 100644 index 000000000..a022bbeff --- /dev/null +++ b/src/common/p25/dfsi/frames/MotTDULCFrame.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "common/p25/P25Defines.h" +#include "common/p25/dfsi/frames/MotTDULCFrame.h" +#include "common/p25/dfsi/DFSIDefines.h" +#include "common/Utils.h" +#include "common/Log.h" + +#include +#include + +using namespace p25; +using namespace p25::defines; +using namespace p25::dfsi; +using namespace p25::dfsi::defines; +using namespace p25::dfsi::frames; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a instance of the MotTDULCFrame class. */ + +MotTDULCFrame::MotTDULCFrame() : + startOfStream(nullptr), + tdulcData(nullptr) +{ + tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES]; + ::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + + startOfStream = new MotStartOfStream(); +} + +/* Initializes a instance of the MotTDULCFrame class. */ + +MotTDULCFrame::MotTDULCFrame(uint8_t* data) : + startOfStream(nullptr), + tdulcData(nullptr) +{ + tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES]; + ::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + + decode(data); +} + +/* Finalizes a instance of the MotTDULCFrame class. */ + +MotTDULCFrame::~MotTDULCFrame() +{ + if (startOfStream != nullptr) + delete startOfStream; + if (tdulcData != nullptr) + delete[] tdulcData; +} + +/* Decode a TDULC frame. */ + +bool MotTDULCFrame::decode(const uint8_t* data) +{ + assert(data != nullptr); + + // create a new start of stream + if (startOfStream != nullptr) + delete startOfStream; + startOfStream = new MotStartOfStream(); + + // create a buffer to decode the start record + uint8_t startBuffer[DFSI_MOT_START_LEN]; + ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN); + ::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U); + + // decode start of stream + startOfStream->decode(startBuffer); + + uint8_t tdulcBuffer[9U]; + ::memcpy(tdulcBuffer, data + DFSI_MOT_START_LEN, 9U); + + ::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + tdulcData[0U] = (uint8_t)((tdulcBuffer[0U] >> 3) & 0x3FU); + tdulcData[1U] = (uint8_t)(((tdulcBuffer[0U] & 0x07U) << 3) | ((tdulcBuffer[1U] >> 4) & 0x07U)); + tdulcData[2U] = (uint8_t)(((tdulcBuffer[1U] & 0x0FU) << 2) | ((tdulcBuffer[2U] >> 5) & 0x03U)); + tdulcData[3U] = (uint8_t)(tdulcBuffer[2U] & 0x1FU); + tdulcData[4U] = (uint8_t)((tdulcBuffer[3U] >> 3) & 0x3FU); + tdulcData[5U] = (uint8_t)(((tdulcBuffer[3U] & 0x07U) << 3) | ((tdulcBuffer[4U] >> 4) & 0x07U)); + tdulcData[6U] = (uint8_t)(((tdulcBuffer[4U] & 0x0FU) << 2) | ((tdulcBuffer[5U] >> 5) & 0x03U)); + tdulcData[7U] = (uint8_t)(tdulcBuffer[5U] & 0x1FU); + tdulcData[8U] = (uint8_t)((tdulcBuffer[6U] >> 3) & 0x3FU); + tdulcData[9U] = (uint8_t)(((tdulcBuffer[6U] & 0x07U) << 3) | ((tdulcBuffer[7U] >> 4) & 0x07U)); + tdulcData[10U] = (uint8_t)(((tdulcBuffer[7U] & 0x0FU) << 2) | ((tdulcBuffer[8U] >> 5) & 0x03U)); + tdulcData[11U] = (uint8_t)(tdulcBuffer[8U] & 0x1FU); + + return true; +} + +/* Encode a TDULC frame. */ + +void MotTDULCFrame::encode(uint8_t* data) +{ + assert(data != nullptr); + assert(startOfStream != nullptr); + + // encode start of stream - scope is intentional + { + uint8_t buffer[DFSI_MOT_START_LEN]; + startOfStream->encode(buffer); + + // copy to data array + ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U); + } + + // encode TDULC - scope is intentional + { + data[0U] = DFSIFrameType::MOT_TDULC; + + data[DFSI_MOT_START_LEN + 1U] = (uint8_t)((tdulcData[0U] & 0x3FU) << 3) | ((tdulcData[1U] >> 3) & 0x07U); + data[DFSI_MOT_START_LEN + 2U] = (uint8_t)((tdulcData[1U] & 0x0FU) << 4) | ((tdulcData[2U] >> 2) & 0x0FU); + data[DFSI_MOT_START_LEN + 3U] = (uint8_t)((tdulcData[2U] & 0x03U)) | (tdulcData[3U] & 0x3FU); + + data[DFSI_MOT_START_LEN + 4U] = (uint8_t)((tdulcData[4U] & 0x3FU) << 3) | ((tdulcData[5U] >> 3) & 0x07U); + data[DFSI_MOT_START_LEN + 5U] = (uint8_t)((tdulcData[5U] & 0x0FU) << 4) | ((tdulcData[6U] >> 2) & 0x0FU); + data[DFSI_MOT_START_LEN + 6U] = (uint8_t)((tdulcData[6U] & 0x03U)) | (tdulcData[7U] & 0x3FU); + + data[DFSI_MOT_START_LEN + 7U] = (uint8_t)((tdulcData[8U] & 0x3FU) << 3) | ((tdulcData[9U] >> 3) & 0x07U); + data[DFSI_MOT_START_LEN + 8U] = (uint8_t)((tdulcData[9U] & 0x0FU) << 4) | ((tdulcData[10U] >> 2) & 0x0FU); + data[DFSI_MOT_START_LEN + 9U] = (uint8_t)((tdulcData[10U] & 0x03U)) | (tdulcData[11U] & 0x3FU); + + data[DFSI_MOT_START_LEN + 11U] = DFSI_BUSY_BITS_IDLE; + } +} diff --git a/src/common/p25/dfsi/frames/MotPDUFrame.h b/src/common/p25/dfsi/frames/MotTDULCFrame.h similarity index 62% rename from src/common/p25/dfsi/frames/MotPDUFrame.h rename to src/common/p25/dfsi/frames/MotTDULCFrame.h index 792143a61..66a308383 100644 --- a/src/common/p25/dfsi/frames/MotPDUFrame.h +++ b/src/common/p25/dfsi/frames/MotTDULCFrame.h @@ -4,17 +4,17 @@ * 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 MotPDUFrame.h + * @file MotTDULCFrame.h * @ingroup dfsi_frames - * @file MotPDUFrame.cpp + * @file MotTDULCFrame.cpp * @ingroup dfsi_frames */ -#if !defined(__MOT_PDU_FRAME_H__) -#define __MOT_PDU_FRAME_H__ +#if !defined(__MOT_TDULC_FRAME_H__) +#define __MOT_TDULC_FRAME_H__ #include "Defines.h" #include "common/Defines.h" @@ -35,59 +35,57 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola PDU frame. + * @brief Implements a P25 Motorola/V.24 TDULC frame. * \code{.unparsed} * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | + * | FT | Encoded V.24 Start of Stream | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved ? | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | PDU Header | - * + + * | | - * + + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | TDULC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \endcode * @ingroup dfsi_frames */ - class HOST_SW_API MotPDUFrame { + class HOST_SW_API MotTDULCFrame { public: - static const uint8_t LENGTH = 20U; - /** - * @brief Initializes a copy instance of the MotPDUFrame class. + * @brief Initializes a copy instance of the MotTDULCFrame class. */ - MotPDUFrame(); + MotTDULCFrame(); /** - * @brief Initializes a copy instance of the MotPDUFrame class. - * @param data Buffer to containing MotPDUFrame to decode. + * @brief Initializes a copy instance of the MotTDULCFrame class. + * @param data Buffer to containing MotTDULCFrame to decode. */ - MotPDUFrame(uint8_t* data); + MotTDULCFrame(uint8_t* data); /** - * @brief Finalizes a instance of the MotPDUFrame class. + * @brief Finalizes a instance of the MotTDULCFrame class. */ - ~MotPDUFrame(); + ~MotTDULCFrame(); /** - * @brief Decode a PDU frame. (only the PDU data header...) - * @param[in] data Buffer to containing MotPDUFrame to decode. + * @brief Decode a TDULC frame. + * @param[in] data Buffer to containing MotTDULCFrame to decode. */ bool decode(const uint8_t* data); /** - * @brief Encode a PDU frame. (only the PDU data header...) - * @param[out] data Buffer to encode a MotPDUFrame. + * @brief Encode a TDULC frame. + * @param[out] data Buffer to encode a MotTDULCFrame. */ void encode(uint8_t* data); public: MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters - uint8_t* pduHeaderData; // ?? - this should probably be private with getters/setters + uint8_t* tdulcData; // ?? - this should probably be private with getters/setters }; } // namespace frames } // namespace dfsi } // namespace p25 -#endif // __MOT_PDU_FRAME_H__ \ No newline at end of file +#endif // __MOT_TDULC_FRAME_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.cpp b/src/common/p25/dfsi/frames/MotTSBKFrame.cpp index 61fe0ed20..1ac6875ba 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.cpp +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.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/P25Defines.h" @@ -71,15 +71,15 @@ bool MotTSBKFrame::decode(const uint8_t* data) delete startOfStream; startOfStream = new MotStartOfStream(); - // create a buffer to decode the start record skipping the 10th byte (adjMM) - uint8_t startBuffer[MotStartOfStream::LENGTH]; - ::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH); - ::memcpy(startBuffer + 1U, data, 4U); + // create a buffer to decode the start record + uint8_t startBuffer[DFSI_MOT_START_LEN]; + ::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN); + ::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U); // decode start of stream startOfStream->decode(startBuffer); - ::memcpy(tsbkData, data + 9U, P25_TSBK_LENGTH_BYTES); + ::memcpy(tsbkData, data + DFSI_MOT_START_LEN, P25_TSBK_LENGTH_BYTES); return true; } @@ -93,16 +93,16 @@ void MotTSBKFrame::encode(uint8_t* data) // encode start of stream - scope is intentional { - uint8_t buffer[MotStartOfStream::LENGTH]; + uint8_t buffer[DFSI_MOT_START_LEN]; startOfStream->encode(buffer); - // copy to data array (skipping first and last bytes) - ::memcpy(data + 1U, buffer + 1U, 4U); + // copy to data array + ::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U); } // encode TSBK - scope is intentional { - data[0U] = DFSIFrameType::TSBK; - ::memcpy(data + 9U, tsbkData, P25_TSBK_LENGTH_BYTES); + data[0U] = DFSIFrameType::MOT_TSBK; + ::memcpy(data + DFSI_MOT_START_LEN, tsbkData, P25_TSBK_LENGTH_BYTES); } } diff --git a/src/common/p25/dfsi/frames/MotTSBKFrame.h b/src/common/p25/dfsi/frames/MotTSBKFrame.h index 654f26409..2ffee559c 100644 --- a/src/common/p25/dfsi/frames/MotTSBKFrame.h +++ b/src/common/p25/dfsi/frames/MotTSBKFrame.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 * */ /** @@ -35,30 +35,28 @@ namespace p25 // --------------------------------------------------------------------------- /** - * @brief Implements a P25 Motorola TSBK frame. + * @brief Implements a P25 Motorola/V.24 TSBK frame. * \code{.unparsed} * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | + * | FT | Encoded V.24 Start of Stream | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved ? | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | TSBK | - * + + * | | - * + + + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | TSBK | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Unknown ? | + * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \endcode * @ingroup dfsi_frames */ class HOST_SW_API MotTSBKFrame { public: - static const uint8_t LENGTH = 24U; - /** * @brief Initializes a copy instance of the MotTSBKFrame class. */ diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp b/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp deleted file mode 100644 index 908cd831f..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/dfsi/frames/MotVoiceHeader1.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25; -using namespace p25::dfsi; -using namespace p25::dfsi::defines; -using namespace p25::dfsi::frames; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the MotVoiceHeader1 class. */ - -MotVoiceHeader1::MotVoiceHeader1() : - header(nullptr), - startOfStream(nullptr), - m_icw(ICWFlag::DIU), - m_rssi(0U), - m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U) -{ - startOfStream = new MotStartOfStream(); - - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); -} - -/* Initializes a instance of the MotVoiceHeader1 class. */ - -MotVoiceHeader1::MotVoiceHeader1(uint8_t* data) : - header(nullptr), - startOfStream(nullptr), - m_icw(ICWFlag::DIU), - m_rssi(0U), - m_rssiValidity(RssiValidityFlag::INVALID), - m_nRssi(0U) -{ - decode(data); -} - -/* Finalizes a instance of the MotVoiceHeader1 class. */ - -MotVoiceHeader1::~MotVoiceHeader1() -{ - if (startOfStream != nullptr) - delete startOfStream; - if (header != nullptr) - delete[] header; -} - -/* Decode a voice header 1 frame. */ - -bool MotVoiceHeader1::decode(const uint8_t* data) -{ - assert(data != nullptr); - - // create a start of stream - if (startOfStream != nullptr) - delete startOfStream; - startOfStream = new MotStartOfStream(); - - uint8_t buffer[MotStartOfStream::LENGTH]; - ::memset(buffer, 0x00U, MotStartOfStream::LENGTH); - - // we copy the bytes from [1:4] - ::memcpy(buffer + 1U, data + 1U, 4); - startOfStream->decode(buffer); - - // decode the other stuff - m_icw = (ICWFlag::E)data[5U]; - m_rssi = data[6U]; - m_rssiValidity = (RssiValidityFlag::E)data[7U]; - m_nRssi = data[8U]; - - // our header includes the trailing source and check bytes - if (header != nullptr) - delete[] header; - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); - ::memcpy(header, data + 9U, HCW_LENGTH); - - return true; -} - -/* Encode a voice header 1 frame. */ - -void MotVoiceHeader1::encode(uint8_t* data) -{ - assert(data != nullptr); - assert(startOfStream != nullptr); - - data[0U] = DFSIFrameType::MOT_VHDR_1; - - // scope is intentional - { - uint8_t buffer[MotStartOfStream::LENGTH]; - ::memset(buffer, 0x00U, MotStartOfStream::LENGTH); - startOfStream->encode(buffer); - - // copy the 4 start record bytes from the start of stream frame - ::memcpy(data + 1U, buffer + 1U, 4U); - } - - data[5U] = m_icw; - data[6U] = m_rssi; - data[7U] = m_rssiValidity; - data[8U] = m_nRssi; - - // our header includes the trailing source and check bytes - if (header != nullptr) { - ::memcpy(data + 9U, header, HCW_LENGTH); - } -} diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader1.h b/src/common/p25/dfsi/frames/MotVoiceHeader1.h deleted file mode 100644 index 05951a0a8..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader1.h +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file MotVoiceHeader1.h - * @ingroup dfsi_frames - * @file MotVoiceHeader1.cpp - * @ingroup dfsi_frames - */ -#if !defined(__MOT_VOICE_HEADER_1_H__) -#define __MOT_VOICE_HEADER_1_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/MotStartOfStream.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Implements a P25 Motorola voice header frame 1. - * \code{.unparsed} - * 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 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Encoded Motorola Start of Stream | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | ICW Flag ? | RSSI | RSSI Valid | RSSI | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Header Control Word | - * + + - * | | - * + + - * | | - * + + - * | | - * + + - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Src Flag | - * +-+-+-+-+-+-+-+-+ - * \endcode - * @ingroup dfsi_frames - */ - class HOST_SW_API MotVoiceHeader1 { - public: - static const uint8_t LENGTH = 30U; - static const uint8_t HCW_LENGTH = 21U; - - /** - * @brief Initializes a copy instance of the MotVoiceHeader1 class. - */ - MotVoiceHeader1(); - /** - * @brief Initializes a copy instance of the MotVoiceHeader1 class. - * @param data Buffer to containing MotVoiceHeader1 to decode. - */ - MotVoiceHeader1(uint8_t* data); - /** - * @brief Finalizes a instance of the MotVoiceHeader1 class. - */ - ~MotVoiceHeader1(); - - /** - * @brief Decode a voice header 1 frame. - * @param[in] data Buffer to containing MotVoiceHeader1 to decode. - */ - bool decode(const uint8_t* data); - /** - * @brief Encode a voice header 1 frame. - * @param[out] data Buffer to encode a MotVoiceHeader1. - */ - void encode(uint8_t* data); - - public: - uint8_t* header; // ?? - this should probably be private with getters/setters - MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters - - /** - * @brief - */ - DECLARE_PROPERTY(ICWFlag::E, icw, ICW); - /** - * @brief RSSI Value. - */ - DECLARE_PROPERTY(uint8_t, rssi, RSSI); - /** - * @brief Flag indicating whether or not the RSSI field is valid. - */ - DECLARE_PROPERTY(RssiValidityFlag::E, rssiValidity, RSSIValidity); - /** - * @brief - */ - DECLARE_PROPERTY(uint8_t, nRssi, NRSSI); - }; - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __MOT_VOICE_HEADER_1_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader2.cpp b/src/common/p25/dfsi/frames/MotVoiceHeader2.cpp deleted file mode 100644 index 0bfd227be..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader2.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -#include "common/p25/dfsi/frames/MotVoiceHeader2.h" -#include "common/p25/dfsi/DFSIDefines.h" -#include "common/Utils.h" -#include "common/Log.h" - -#include -#include - -using namespace p25; -using namespace p25::dfsi; -using namespace p25::dfsi::defines; -using namespace p25::dfsi::frames; - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a instance of the MotVoiceHeader2 class. */ - -MotVoiceHeader2::MotVoiceHeader2() : - header(nullptr), - m_source(SourceFlag::QUANTAR) -{ - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); -} - -/* Initializes a instance of the MotVoiceHeader2 class. */ - -MotVoiceHeader2::MotVoiceHeader2(uint8_t* data) : - header(nullptr), - m_source(SourceFlag::QUANTAR) -{ - decode(data); -} - -/* Finalizes a instance of the MotVoiceHeader2 class. */ - -MotVoiceHeader2::~MotVoiceHeader2() -{ - if (header != nullptr) - delete[] header; -} - -/* Decode a voice header 2 frame. */ - -bool MotVoiceHeader2::decode(const uint8_t* data) -{ - assert(data != nullptr); - - m_source = (SourceFlag::E)data[21]; - - if (header != nullptr) { - delete[] header; - } - - header = new uint8_t[HCW_LENGTH]; - ::memset(header, 0x00U, HCW_LENGTH); - ::memcpy(header, data + 1U, HCW_LENGTH); - - return true; -} - -/* Encode a voice header 2 frame. */ - -void MotVoiceHeader2::encode(uint8_t* data) -{ - assert(data != nullptr); - - data[0U] = DFSIFrameType::MOT_VHDR_2; - - if (header != nullptr) { - ::memcpy(data + 1U, header, HCW_LENGTH); - } - - data[LENGTH - 1U] = (uint8_t)m_source; -} diff --git a/src/common/p25/dfsi/frames/MotVoiceHeader2.h b/src/common/p25/dfsi/frames/MotVoiceHeader2.h deleted file mode 100644 index a1918a77c..000000000 --- a/src/common/p25/dfsi/frames/MotVoiceHeader2.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Digital Voice Modem - Common Library - * GPLv2 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (C) 2024 Patrick McDonnell, W3AXL - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file MotVoiceHeader2.h - * @ingroup dfsi_frames - * @file MotVoiceHeader2.cpp - * @ingroup dfsi_frames - */ -#if !defined(__MOT_VOICE_HEADER_2_H__) -#define __MOT_VOICE_HEADER_2_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/MotStartOfStream.h" - -namespace p25 -{ - namespace dfsi - { - namespace frames - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Implements a P25 Motorola voice header frame 2. - * \code{.unparsed} - * 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 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Header Control Word | - * + + - * | | - * + + - * | | - * + + - * | | - * + + - * | | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | | Reserved | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * \endcode - * @ingroup dfsi_frames - */ - class HOST_SW_API MotVoiceHeader2 { - public: - static const uint8_t LENGTH = 22U; - static const uint8_t HCW_LENGTH = 20U; - - /** - * @brief Initializes a copy instance of the MotVoiceHeader2 class. - */ - MotVoiceHeader2(); - /** - * @brief Initializes a copy instance of the MotVoiceHeader2 class. - * @param data Buffer to containing MotVoiceHeader2 to decode. - */ - MotVoiceHeader2(uint8_t* data); - /** - * @brief Finalizes a instance of the MotVoiceHeader2 class. - */ - ~MotVoiceHeader2(); - - /** - * @brief Decode a voice header 2 frame. - * @param[in] data Buffer to containing MotVoiceHeader2 to decode. - */ - bool decode(const uint8_t* data); - /** - * @brief Encode a voice header 2 frame. - * @param[out] data Buffer to encode a MotVoiceHeader2. - */ - void encode(uint8_t* data); - - public: - uint8_t* header; // ?? - this should probably be a private with getters/setters - - /** - * @brief V.24 Data Source. - */ - DECLARE_PROPERTY(SourceFlag::E, source, Source); - }; - } // namespace frames - } // namespace dfsi -} // namespace p25 - -#endif // __MOT_VOICE_HEADER_2_H__ \ No newline at end of file diff --git a/src/common/p25/dfsi/frames/StartOfStream.h b/src/common/p25/dfsi/frames/StartOfStream.h index 426803750..014b08c22 100644 --- a/src/common/p25/dfsi/frames/StartOfStream.h +++ b/src/common/p25/dfsi/frames/StartOfStream.h @@ -45,7 +45,7 @@ namespace p25 */ class HOST_SW_API StartOfStream { public: - static const uint8_t LENGTH = 4U; + static const uint8_t LENGTH = 3U; /** * @brief Initializes a copy instance of the StartOfStream class. diff --git a/src/common/p25/lc/AMBT.cpp b/src/common/p25/lc/AMBT.cpp index 36fb0f919..54cd15734 100644 --- a/src/common/p25/lc/AMBT.cpp +++ b/src/common/p25/lc/AMBT.cpp @@ -115,7 +115,7 @@ bool AMBT::decode(const data::DataHeader& dataHeader, const data::DataBlock* blo if (m_verbose) { LogDebugEx(LOG_P25, "AMBT::decode()", "mfId = $%02X, lco = $%02X, ambt8 = $%02X, ambt9 = $%02X", m_mfId, m_lco, dataHeader.getAMBTField8(), dataHeader.getAMBTField9()); - Utils::dump(2U, "[AMBT::decode()] pduUserData", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * dataHeader.getBlocksToFollow()); + Utils::dump(2U, "P25, AMBT::decode(), pduUserData", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * dataHeader.getBlocksToFollow()); } return true; diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index 9d67e65cb..a0003e05a 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -67,10 +67,16 @@ LC::LC() : m_encryptOverride(false), m_tsbkVendorSkip(false), m_callTimer(0U), - m_mi(nullptr) + m_mi(nullptr), + m_userAlias(nullptr), + m_gotUserAliasPartA(false), + m_gotUserAlias(false) { m_mi = new uint8_t[MI_LENGTH_BYTES]; ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); } /* Finalizes a instance of LC class. */ @@ -81,6 +87,11 @@ LC::~LC() delete[] m_mi; m_mi = nullptr; } + + if (m_userAlias != nullptr) { + delete[] m_userAlias; + m_userAlias = nullptr; + } } /* Equals operator. */ @@ -109,14 +120,14 @@ bool LC::decodeHDU(const uint8_t* data, bool rawOnly) P25Utils::decode(data, raw, 114U, 780U); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::decodeHDU(), HDU Raw", raw, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), HDU Raw", raw, P25_HDU_LENGTH_BYTES); #endif // decode Golay (18,6,8) FEC decodeHDUGolay(raw, rs); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::decodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); #endif // decode RS (36,20,17) FEC @@ -128,12 +139,12 @@ bool LC::decodeHDU(const uint8_t* data, bool rawOnly) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), RS excepted with input data", rs, P25_HDU_LENGTH_BYTES); return false; } #if DEBUG_P25_HDU - Utils::dump(2U, "LC::decodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); #endif m_mfId = rs[9U]; // Mfg Id. @@ -193,14 +204,14 @@ void LC::encodeHDU(uint8_t* data, bool rawOnly) rs[14U] = (m_dstId >> 0) & 0xFFU; // ... #if DEBUG_P25_HDU - Utils::dump(2U, "LC::encodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES); #endif // encode RS (36,20,17) FEC m_rs.encode362017(rs); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::encodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES); #endif uint8_t raw[P25_HDU_LENGTH_BYTES + 1U]; @@ -218,7 +229,7 @@ void LC::encodeHDU(uint8_t* data, bool rawOnly) P25Utils::encode(raw, data, 114U, 780U); #if DEBUG_P25_HDU - Utils::dump(2U, "LC::encodeHDU(), HDU Interleave", data, P25_HDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeHDU(), HDU Interleave", data, P25_HDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } @@ -251,7 +262,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_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // decode RS (24,12,13) FEC @@ -263,12 +274,12 @@ bool LC::decodeLDU1(const uint8_t* data, bool rawOnly) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU1(), RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); return false; } #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif return decodeLC(rs, rawOnly); @@ -286,14 +297,14 @@ void LC::encodeLDU1(uint8_t* data) encodeLC(rs); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, 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_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode Hamming (10,6,3) FEC and interleave for LC data @@ -317,7 +328,7 @@ void LC::encodeLDU1(uint8_t* data) P25Utils::encode(raw, data, 1356U, 1398U); #if DEBUG_P25_LDU1 - Utils::dump(2U, "LC::encodeLDU1(), LDU1 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU1(), LDU1 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } @@ -350,7 +361,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_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // decode RS (24,16,9) FEC @@ -362,12 +373,12 @@ bool LC::decodeLDU2(const uint8_t* data) } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU2(), RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES); return false; } #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::decodeLDU2(), LDU2 LC", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif m_algId = rs[9U]; // Algorithm ID @@ -418,14 +429,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_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, 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_FEC_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); #endif // encode Hamming (10,6,3) FEC and interleave for LC data @@ -449,7 +460,7 @@ void LC::encodeLDU2(uint8_t* data) P25Utils::encode(raw, data, 1356U, 1398U); #if DEBUG_P25_LDU2 - Utils::dump(2U, "LC::encodeLDU2(), LDU2 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, LC::encodeLDU2(), LDU2 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } @@ -462,97 +473,12 @@ bool LC::isStandardMFId() const return false; } -/* -** Encryption data -*/ - -/* Sets the encryption message indicator. */ - -void LC::setMI(const uint8_t* mi) -{ - assert(mi != nullptr); - - ::memcpy(m_mi, mi, MI_LENGTH_BYTES); -} - -/* Gets the encryption message indicator. */ - -void LC::getMI(uint8_t* mi) const -{ - assert(mi != nullptr); - - ::memcpy(mi, m_mi, MI_LENGTH_BYTES); -} - -// --------------------------------------------------------------------------- -// Private Class Members -// --------------------------------------------------------------------------- - -/* Internal helper to copy the the class. */ - -void LC::copy(const LC& data) -{ - m_lco = data.m_lco; - - m_protect = data.m_protect; - m_mfId = data.m_mfId; - - m_srcId = data.m_srcId; - m_dstId = data.m_dstId; - - m_grpVchNo = data.m_grpVchNo; - - m_grpVchNoB = data.m_grpVchNoB; - m_dstIdB = data.m_dstIdB; - - m_explicitId = data.m_explicitId; - - m_netId = data.m_netId; - m_sysId = data.m_sysId; - - m_emergency = data.m_emergency; - m_encrypted = data.m_encrypted; - m_priority = data.m_priority; - - m_group = data.m_group; - - m_callTimer = data.m_callTimer; - - m_rsValue = data.m_rsValue; - - m_algId = data.m_algId; - if (m_algId != ALGO_UNENCRYPT) { - delete[] m_mi; - - m_mi = new uint8_t[MI_LENGTH_BYTES]; - ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); - - m_kId = data.m_kId; - if (!m_encrypted) { - m_encryptOverride = true; - m_encrypted = true; - } - } - else { - delete[] m_mi; - - m_mi = new uint8_t[MI_LENGTH_BYTES]; - ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); - - m_kId = 0x0000U; - if (m_encrypted) { - m_encryptOverride = true; - m_encrypted = false; - } - } - - m_siteData = data.m_siteData; -} - /* Decode link control. */ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) { + assert(rs != nullptr); + ulong64_t rsValue = 0U; // combine bytes into ulong64_t (8 byte) value @@ -569,7 +495,11 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) m_protect = (rs[0U] & 0x80U) == 0x80U; // Protect Flag m_lco = rs[0U] & 0x3FU; // LCO - m_mfId = rs[1U]; // Mfg Id. + bool sf = (rs[0U] & 0x40U) == 0x40U; // Implicit/Explicit Operation + if (sf) + m_mfId = MFG_STANDARD; + else + m_mfId = rs[1U]; // Mfg Id. if (rawOnly) return true; @@ -577,7 +507,36 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) // non-standard P25 vendor opcodes (these are just detected for passthru, and stored // as the packed RS value) if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { - //Utils::dump(1U, "Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + //Utils::dump(1U, "P25, LC::decodeLC(), Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + // Harris + if (m_mfId == MFG_HARRIS) { + // Harris P25 opcodes + switch (m_lco) { + case LCO::HARRIS_USER_ALIAS_PA_ODD: + case LCO::HARRIS_USER_ALIAS_PA_EVEN: + m_gotUserAliasPartA = true; + m_gotUserAlias = false; + + if (m_userAlias != nullptr) { + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + ::memcpy(m_userAlias, rs + 2U, 7U); + m_gotUserAlias = true; + } + break; + + case LCO::HARRIS_USER_ALIAS_PB_ODD: + case LCO::HARRIS_USER_ALIAS_PB_EVEN: + if (m_gotUserAliasPartA && (m_userAlias != nullptr)) { + ::memcpy(m_userAlias + 7U, rs + 2U, 7U); + m_gotUserAlias = true; + } + break; + + default: + break; + } + } + return true; } @@ -622,6 +581,18 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) m_sysId = (uint32_t)((rsValue >> 24) & 0xFFFU); // System ID m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address break; + case LCO::PRIVATE_EXT: + m_explicitId = (rs[1U] & 0x01U) == 0x01U; // Explicit Source ID Flag + m_group = false; + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + if (!m_encryptOverride) { + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + } + m_priority = (rs[2U] & 0x07U); // Priority + m_explicitId = (rs[3U] & 0x01U) == 0x01U; // Explicit Source ID Flag + m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + break; default: LogError(LOG_P25, "LC::decodeLC(), unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); return false; @@ -639,19 +610,65 @@ bool LC::decodeLC(const uint8_t* rs, bool rawOnly) void LC::encodeLC(uint8_t* rs) { + assert(rs != nullptr); + ulong64_t rsValue = 0U; rs[0U] = m_lco; // LCO + // non-standard P25 vendor opcodes (these are just detected for passthru, and stored + // as the packed RS value) + if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { + //Utils::dump(1U, "P25, LC::decodeLC(), Decoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + if (m_mfId == MFG_HARRIS) { + // Harris P25 opcodes + switch (m_lco) { + case LCO::HARRIS_USER_ALIAS_PA_ODD: + case LCO::HARRIS_USER_ALIAS_PA_EVEN: + if (m_userAlias != nullptr) { + // split ulong64_t (8 byte) value into bytes + rs[1U] = m_mfId; // Manufacturer ID + rs[2U] = m_userAlias[0U]; + rs[3U] = m_userAlias[1U]; + rs[4U] = m_userAlias[2U]; + rs[5U] = m_userAlias[3U]; + rs[6U] = m_userAlias[4U]; + rs[7U] = m_userAlias[5U]; + rs[8U] = m_userAlias[6U]; + } + return; + + case LCO::HARRIS_USER_ALIAS_PB_ODD: + case LCO::HARRIS_USER_ALIAS_PB_EVEN: + if (m_userAlias != nullptr) { + // split ulong64_t (8 byte) value into bytes + rs[1U] = m_mfId; // Manufacturer ID + rs[2U] = m_userAlias[7U]; + rs[3U] = m_userAlias[8U]; + rs[4U] = m_userAlias[9U]; + rs[5U] = m_userAlias[10U]; + rs[6U] = m_userAlias[11U]; + rs[7U] = m_userAlias[12U]; + rs[8U] = m_userAlias[13U]; + } + return; + + default: + break; + } + } + } + if ((m_mfId == MFG_STANDARD) || (m_mfId == MFG_STANDARD_ALT)) { // standard P25 reference opcodes switch (m_lco) { case LCO::GROUP: - rsValue = m_mfId; + rsValue = m_mfId; // Manufacturer ID rsValue = (rsValue << 8) + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag (m_priority & 0x07U); // Priority - rsValue = (rsValue << 24) + m_dstId; // Talkgroup Address + rsValue = (rsValue << 8) + (m_explicitId ? 0x01U : 0x00U); // Explicit Source ID Flag + rsValue = (rsValue << 16) + m_dstId; // Talkgroup Address rsValue = (rsValue << 24) + m_srcId; // Source Radio Address break; case LCO::GROUP_UPDT: @@ -664,7 +681,7 @@ void LC::encodeLC(uint8_t* rs) rsValue = (rsValue << 16) + m_dstIdB; // Group B - Talkgroup Address break; case LCO::PRIVATE: - rsValue = m_mfId; + rsValue = m_mfId; // Manufacturer ID rsValue = (rsValue << 8) + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag @@ -682,10 +699,21 @@ void LC::encodeLC(uint8_t* rs) rsValue = (rsValue << 24) + m_srcId; // Source/Target Radio Address break; case LCO::EXPLICIT_SOURCE_ID: - rsValue = m_netId; // Network ID + rs[0U] |= 0x40U; // Implicit Operation + rsValue = (rsValue << 8) + m_netId; // Network ID rsValue = (rsValue << 12) + (m_sysId & 0xFFFU); // System ID rsValue = (rsValue << 24) + m_srcId; // Source Radio Address break; + case LCO::PRIVATE_EXT: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = (m_explicitId ? 0x01U : 0x00U); // Explicit Source ID Flag + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 24) + m_dstId; // Target Radio Address + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + break; case LCO::RFSS_STS_BCAST: rs[0U] |= 0x40U; // Implicit Operation rsValue = m_siteData.lra(); // Location Registration Area @@ -720,11 +748,142 @@ void LC::encodeLC(uint8_t* rs) rs[8U] = (uint8_t)((rsValue >> 0) & 0xFFU); /* if ((m_mfId != MFG_STANDARD) && (m_mfId != MFG_STANDARD_ALT)) { - Utils::dump(1U, "Encoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, LC::encodeLC(), Encoded P25 Non-Standard RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES); } */ } +/* +** Encryption data +*/ + +/* Sets the encryption message indicator. */ + +void LC::setMI(const uint8_t* mi) +{ + assert(mi != nullptr); + + ::memcpy(m_mi, mi, MI_LENGTH_BYTES); +} + +/* Gets the encryption message indicator. */ + +void LC::getMI(uint8_t* mi) const +{ + assert(mi != nullptr); + + ::memcpy(mi, m_mi, MI_LENGTH_BYTES); +} + +/* +** User Alias data +*/ + +/* Gets the user alias. */ + +std::string LC::getUserAlias() const +{ + std::string alias; + if (m_gotUserAlias) { + for (uint32_t i = 0; i < HARRIS_USER_ALIAS_LENGTH_BYTES; i++) + alias[i] = m_userAlias[i]; + } + + return alias; +} + +/* Sets the user alias. */ + +void LC::setUserAlias(std::string alias) +{ + if (m_userAlias == nullptr) + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + for (uint32_t i = 0; i < HARRIS_USER_ALIAS_LENGTH_BYTES; i++) + m_userAlias[i] = alias[i]; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void LC::copy(const LC& data) +{ + m_lco = data.m_lco; + + m_protect = data.m_protect; + m_mfId = data.m_mfId; + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + + m_grpVchNo = data.m_grpVchNo; + + m_grpVchNoB = data.m_grpVchNoB; + m_dstIdB = data.m_dstIdB; + + m_explicitId = data.m_explicitId; + + m_netId = data.m_netId; + m_sysId = data.m_sysId; + + m_emergency = data.m_emergency; + m_encrypted = data.m_encrypted; + m_priority = data.m_priority; + + m_group = data.m_group; + + m_callTimer = data.m_callTimer; + + m_rsValue = data.m_rsValue; + + m_algId = data.m_algId; + if (m_algId != ALGO_UNENCRYPT) { + delete[] m_mi; + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); + + m_kId = data.m_kId; + if (!m_encrypted) { + m_encryptOverride = true; + m_encrypted = true; + } + } + else { + delete[] m_mi; + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + + m_kId = 0x0000U; + if (m_encrypted) { + m_encryptOverride = true; + m_encrypted = false; + } + } + + // do we have user alias data to copy? + if (data.m_gotUserAlias && data.m_userAlias != nullptr) { + delete[] m_userAlias; + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memcpy(m_userAlias, data.m_userAlias, HARRIS_USER_ALIAS_LENGTH_BYTES); + m_gotUserAlias = data.m_gotUserAlias; + } else { + delete[] m_userAlias; + + m_userAlias = new uint8_t[HARRIS_USER_ALIAS_LENGTH_BYTES]; + ::memset(m_userAlias, 0x00U, HARRIS_USER_ALIAS_LENGTH_BYTES); + m_gotUserAlias = false; + } + + m_siteData = data.m_siteData; +} + /* Decode LDU hamming FEC. */ void LC::decodeLDUHamming(const uint8_t* data, uint8_t* raw) diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index 0a54f9338..1af45054d 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -115,6 +115,19 @@ namespace p25 */ bool isStandardMFId() const; + /** + * @brief Decode link control. + * @param[in] rs Buffer containing the decoded Reed-Solomon LC data. + * @param rawOnly Flag indicating only the raw bytes of the LC should be decoded. + * @returns bool True, if LC is decoded, otherwise false. + */ + bool decodeLC(const uint8_t* rs, bool rawOnly = false); + /** + * @brief Encode link control. + * @param[out] rs Buffer to encode LC data. + */ + void encodeLC(uint8_t* rs); + /** @name Encryption data */ /** * @brief Sets the encryption message indicator. @@ -128,6 +141,18 @@ namespace p25 void getMI(uint8_t* mi) const; /** @} */ + /** @name User Alias data */ + /** + * @brief Gets the user alias. + * @returns std::string User Alias. + */ + std::string getUserAlias() const; + /** + * @brief Sets the user alias. + * @param alias User alias. + */ + void setUserAlias(std::string alias); + /** @name Local Site data */ /** * @brief Gets the local site data. @@ -243,6 +268,11 @@ namespace p25 // Encryption data uint8_t* m_mi; + // User Alias data + uint8_t* m_userAlias; + bool m_gotUserAliasPartA; + bool m_gotUserAlias; + // Local Site data static SiteData m_siteData; @@ -251,19 +281,6 @@ namespace p25 */ void copy(const LC& data); - /** - * @brief Decode link control. - * @param[in] rs Buffer containing the decoded Reed-Solomon LC data. - * @param rawOnly Flag indicating only the raw bytes of the LC should be decoded. - * @returns bool True, if LC is decoded, otherwise false. - */ - bool decodeLC(const uint8_t* rs, bool rawOnly = false); - /** - * @brief Encode link control. - * @param[out] rs Buffer to encode LC data. - */ - void encodeLC(uint8_t* rs); - /** * @brief Decode LDU hamming FEC. * @param[in] raw diff --git a/src/common/p25/lc/TDULC.cpp b/src/common/p25/lc/TDULC.cpp index 9fec40f42..94025eb06 100644 --- a/src/common/p25/lc/TDULC.cpp +++ b/src/common/p25/lc/TDULC.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) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -72,6 +72,9 @@ TDULC::TDULC() : m_srcId(0U), m_dstId(0U), m_grpVchNo(0U), + m_explicitId(false), + m_netId(WACN_STD_DEFAULT), + m_sysId(SID_STD_DEFAULT), m_emergency(false), m_encrypted(false), m_priority(4U), @@ -79,7 +82,8 @@ TDULC::TDULC() : m_siteIdenEntry(lookups::IdenTable()), m_rs(), m_implicit(false), - m_callTimer(0U) + m_callTimer(0U), + m_raw(nullptr) { m_grpVchNo = m_siteData.channelNo(); } @@ -88,7 +92,15 @@ TDULC::TDULC() : TDULC::~TDULC() { - /* stub */ + if (m_raw != nullptr) + delete[] m_raw; +} + +/* Returns a copy of the raw decoded TDULC bytes. */ + +uint8_t* TDULC::getDecodedRaw() const +{ + return m_raw; } // --------------------------------------------------------------------------- @@ -138,84 +150,99 @@ UInt8Array TDULC::fromValue(const ulong64_t value) /* Internal helper to decode a terminator data unit w/ link control. */ -bool TDULC::decode(const uint8_t* data, uint8_t* payload) +bool TDULC::decode(const uint8_t* data, uint8_t* payload, bool rawTDULC) { assert(data != nullptr); assert(payload != nullptr); - uint8_t rs[P25_TDULC_LENGTH_BYTES]; - ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + if (rawTDULC) { + ::memcpy(payload, data, P25_TDULC_PAYLOAD_LENGTH_BYTES); + return true; + } else { + uint8_t rs[P25_TDULC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); - // deinterleave - uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; - P25Utils::decode(data, raw, 114U, 410U); + // deinterleave + uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; + P25Utils::decode(data, raw, 114U, 410U); - // decode Golay (24,12,8) FEC - edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); + // decode Golay (24,12,8) FEC + edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif - // decode RS (24,12,13) FEC - try { - bool ret = m_rs.decode241213(rs); - if (!ret) { - LogError(LOG_P25, "TDULC::decode(), failed to decode RS (24,12,13) FEC"); + // decode RS (24,12,13) FEC + try { + bool ret = m_rs.decode241213(rs); + if (!ret) { + LogError(LOG_P25, "TDULC::decode(), failed to decode RS (24,12,13) FEC"); + return false; + } + } + catch (...) { + Utils::dump(2U, "P25, TDULC::decode(), RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); return false; } - } - catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); - return false; - } - if (m_verbose) { - Utils::dump(2U, "TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); - } + if (m_verbose) { + Utils::dump(2U, "P25, TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); + } + + if (m_raw != nullptr) + delete[] m_raw; + m_raw = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; + ::memcpy(m_raw, rs + 1U, P25_TDULC_PAYLOAD_LENGTH_BYTES); - ::memcpy(payload, rs + 1U, P25_TDULC_PAYLOAD_LENGTH_BYTES); - return true; + ::memcpy(payload, rs + 1U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + return true; + } } /* Internal helper to encode a terminator data unit w/ link control. */ -void TDULC::encode(uint8_t* data, const uint8_t* payload) +void TDULC::encode(uint8_t* data, const uint8_t* payload, bool rawTDULC) { assert(data != nullptr); assert(payload != nullptr); - uint8_t rs[P25_TDULC_LENGTH_BYTES]; - ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); - ::memcpy(rs + 1U, payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); + if (rawTDULC) { + ::memcpy(data, payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); + return; + } else { + uint8_t rs[P25_TDULC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + ::memcpy(rs + 1U, payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); - rs[0U] = m_lco; // LCO - if (m_implicit) - rs[0U] |= 0x40U; // Implicit Operation + rs[0U] = m_lco; // LCO + if (m_implicit) + rs[0U] |= 0x40U; // Implicit Operation - if (m_verbose) { - Utils::dump(2U, "TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); - } + if (m_verbose) { + Utils::dump(2U, "P25, TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); + } - // encode RS (24,12,13) FEC - m_rs.encode241213(rs); + // encode RS (24,12,13) FEC + m_rs.encode241213(rs); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::encode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::encode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif - uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; - ::memset(raw, 0x00U, P25_TDULC_FEC_LENGTH_BYTES + 1U); + uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; + ::memset(raw, 0x00U, P25_TDULC_FEC_LENGTH_BYTES + 1U); - // encode Golay (24,12,8) FEC - edac::Golay24128::encode24128(raw, rs, P25_TDULC_LENGTH_BYTES); + // encode Golay (24,12,8) FEC + edac::Golay24128::encode24128(raw, rs, P25_TDULC_LENGTH_BYTES); - // interleave - P25Utils::encode(raw, data, 114U, 410U); + // interleave + P25Utils::encode(raw, data, 114U, 410U); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULC::encode(), TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULC::encode(), TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif + } } /* Internal helper to copy the the class. */ @@ -232,6 +259,11 @@ void TDULC::copy(const TDULC& data) m_grpVchNo = data.m_grpVchNo; + m_explicitId = data.m_explicitId; + + m_netId = data.m_netId; + m_sysId = data.m_sysId; + m_emergency = data.m_emergency; m_encrypted = data.m_encrypted; m_priority = data.m_priority; diff --git a/src/common/p25/lc/TDULC.h b/src/common/p25/lc/TDULC.h index 163afa89d..9e1ccf4d8 100644 --- a/src/common/p25/lc/TDULC.h +++ b/src/common/p25/lc/TDULC.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) 2017-2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -79,6 +79,13 @@ namespace p25 */ virtual void encode(uint8_t* data) = 0; + /** + * @brief Returns a copy of the raw decoded TDULC bytes. + * This will only return data for a *decoded* TDULC, not a created or copied TDULC. + * @returns uint8_t* Raw decoded TDULC bytes. + */ + uint8_t* getDecodedRaw() const; + /** * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. @@ -126,6 +133,20 @@ namespace p25 * @brief Voice channel number. */ DECLARE_PROTECTED_PROPERTY(uint32_t, grpVchNo, GrpVchNo); + + /** + * @brief Flag indicating explicit addressing. + */ + DECLARE_PROTECTED_PROPERTY(bool, explicitId, ExplicitId); + + /** + * @brief Network ID. + */ + DECLARE_PROTECTED_PROPERTY(uint32_t, netId, NetId); + /** + * @brief System ID. + */ + DECLARE_PROTECTED_PROPERTY(uint32_t, sysId, SysId); /** @} */ /** @name Service Options */ @@ -184,16 +205,21 @@ namespace p25 * @brief Internal helper to decode terminator data unit w/ link control. * @param[in] data Raw data. * @param[out] payload TDULC payload buffer. + * @param rawTDULC Flag indicating whether or not the passed buffer is raw. */ - bool decode(const uint8_t* data, uint8_t* payload); + bool decode(const uint8_t* data, uint8_t* payload, bool rawTDULC = false); /** * @brief Internal helper to encode terminator data unit w/ link control. * @param[out] data Raw data. * @param[in] payload TDULC payload buffer. + * @param rawTDULC Flag indicating whether or not the passed buffer is raw. */ - void encode(uint8_t* data, const uint8_t* payload); + void encode(uint8_t* data, const uint8_t* payload, bool rawTDULC = false); DECLARE_PROTECTED_COPY(TDULC); + + private: + uint8_t* m_raw; }; } // namespace lc } // namespace p25 diff --git a/src/common/p25/lc/TSBK.cpp b/src/common/p25/lc/TSBK.cpp index 0e5c0aa14..da39bc4fa 100644 --- a/src/common/p25/lc/TSBK.cpp +++ b/src/common/p25/lc/TSBK.cpp @@ -196,8 +196,7 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) uint8_t tsbk[P25_TSBK_LENGTH_BYTES]; ::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES); - if (rawTSBK) - { + if (rawTSBK) { ::memcpy(tsbk, data, P25_TSBK_LENGTH_BYTES); bool ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); @@ -243,15 +242,17 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) return false; } catch (...) { - Utils::dump(2U, "P25, decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::decode(), decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); return false; } } if (m_verbose) { - Utils::dump(2U, "TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } + if (m_raw != nullptr) + delete[] m_raw; m_raw = new uint8_t[P25_TSBK_LENGTH_BYTES]; ::memcpy(m_raw, tsbk, P25_TSBK_LENGTH_BYTES); @@ -282,7 +283,7 @@ void TSBK::encode(uint8_t* data, const uint8_t* payload, bool rawTSBK, bool noTr edac::CRC::addCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); if (m_verbose) { - Utils::dump(2U, "TSBK::encode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::encode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } uint8_t raw[P25_TSBK_FEC_LENGTH_BYTES]; @@ -305,7 +306,7 @@ void TSBK::encode(uint8_t* data, const uint8_t* payload, bool rawTSBK, bool noTr P25Utils::encode(raw, data, 114U, 318U); #if DEBUG_P25_TSBK - Utils::dump(2U, "TSBK::encode(), TSBK Interleave", data, P25_TSBK_FEC_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBK::encode(), TSBK Interleave", data, P25_TSBK_FEC_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); #endif } } diff --git a/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h b/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h index c0b52204e..3c9179776 100644 --- a/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h @@ -13,8 +13,8 @@ * @file LC_ADJ_STS_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_ADJ_STS_BCAST_H__) -#define __P25_LC_TSBK__LC_ADJ_STS_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_ADJ_STS_BCAST_H__) +#define __P25_LC_TDULC__LC_ADJ_STS_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -88,4 +88,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_ADJ_STS_BCAST_H__ +#endif // __P25_LC_TDULC__LC_ADJ_STS_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_CALL_TERM.h b/src/common/p25/lc/tdulc/LC_CALL_TERM.h index d409cb068..fb0dc5ea6 100644 --- a/src/common/p25/lc/tdulc/LC_CALL_TERM.h +++ b/src/common/p25/lc/tdulc/LC_CALL_TERM.h @@ -13,8 +13,8 @@ * @file LC_CALL_TERM.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_CALL_TERM_H__) -#define __P25_LC_TSBK__LC_CALL_TERM_H__ +#if !defined(__P25_LC_TDULC__LC_CALL_TERM_H__) +#define __P25_LC_TDULC__LC_CALL_TERM_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_CALL_TERM_H__ +#endif // __P25_LC_TDULC__LC_CALL_TERM_H__ diff --git a/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h b/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h index 9c60aaddd..858e2e083 100644 --- a/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h +++ b/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.h @@ -13,8 +13,8 @@ * @file LC_CONV_FALLBACK.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_CONV_FALLBACK_H__) -#define __P25_LC_TSBK__LC_CONV_FALLBACK_H__ +#if !defined(__P25_LC_TDULC__LC_CONV_FALLBACK_H__) +#define __P25_LC_TDULC__LC_CONV_FALLBACK_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_CONV_FALLBACK_H__ +#endif // __P25_LC_TDULC__LC_CONV_FALLBACK_H__ diff --git a/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp new file mode 100644 index 000000000..5ce90d512 --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::lc; +using namespace p25::lc::tdulc; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the LC_EXPLICIT_SOURCE_ID class. */ + +LC_EXPLICIT_SOURCE_ID::LC_EXPLICIT_SOURCE_ID() : TDULC() +{ + m_lco = LCO::EXPLICIT_SOURCE_ID; +} + +/* Decode a terminator data unit w/ link control. */ + +bool LC_EXPLICIT_SOURCE_ID::decode(const uint8_t* data) +{ + assert(data != nullptr); + + 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_netId = (uint32_t)((rsValue >> 36) & 0xFFFFFU); // Network ID + m_sysId = (uint32_t)((rsValue >> 24) & 0xFFFU); // System ID + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + + return true; +} + +/* Encode a terminator data unit w/ link control. */ + +void LC_EXPLICIT_SOURCE_ID::encode(uint8_t* data) +{ + assert(data != nullptr); + + ulong64_t rsValue = 0U; + + m_implicit = true; + + rsValue = (rsValue << 8) + m_netId; // Network ID + rsValue = (rsValue << 12) + (m_sysId & 0xFFFU); // System ID + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + + std::unique_ptr rs = TDULC::fromValue(rsValue); + TDULC::encode(data, rs.get()); +} diff --git a/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h new file mode 100644 index 000000000..0f95255ab --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file LC_EXPLICIT_SOURCE_ID.h + * @ingroup p25_lc + * @file LC_EXPLICIT_SOURCE_ID.cpp + * @ingroup p25_lc + */ +#if !defined(__P25_LC_TDULC__LC_EXPLICIT_SOURCE_ID_H__) +#define __P25_LC_TDULC__LC_EXPLICIT_SOURCE_ID_H__ + +#include "common/Defines.h" +#include "common/p25/lc/TDULC.h" + +namespace p25 +{ + namespace lc + { + namespace tdulc + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements EXPLICIT SOURCE ID - Explicit Source ID + * @ingroup p25_lc + */ + class HOST_SW_API LC_EXPLICIT_SOURCE_ID : public TDULC { + public: + /** + * @brief Initializes a new instance of the LC_EXPLICIT_SOURCE_ID class. + */ + LC_EXPLICIT_SOURCE_ID(); + + /** + * @brief Decode a terminator data unit w/ link control. + * @param[in] data Buffer containing a TDULC to decode. + * @returns bool True, if TDULC decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a terminator data unit w/ link control. + * @param[out] data Buffer to encode a TDULC. + */ + void encode(uint8_t* data) override; + }; + } // namespace tdulc + } // namespace lc +} // namespace p25 + +#endif // __P25_LC_TDULC__LC_EXPLICIT_SOURCE_ID_H__ diff --git a/src/common/p25/lc/tdulc/LC_FAILSOFT.h b/src/common/p25/lc/tdulc/LC_FAILSOFT.h index 7918a7550..8cb418df5 100644 --- a/src/common/p25/lc/tdulc/LC_FAILSOFT.h +++ b/src/common/p25/lc/tdulc/LC_FAILSOFT.h @@ -13,8 +13,8 @@ * @file LC_FAILSOFT.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_FAILSOFT_H__) -#define __P25_LC_TSBK__LC_FAILSOFT_H__ +#if !defined(__P25_LC_TDULC__LC_FAILSOFT_H__) +#define __P25_LC_TDULC__LC_FAILSOFT_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_FAILSOFT_H__ +#endif // __P25_LC_TDULC__LC_FAILSOFT_H__ diff --git a/src/common/p25/lc/tdulc/LC_GROUP.cpp b/src/common/p25/lc/tdulc/LC_GROUP.cpp index 6395fcb77..2da97f320 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP.cpp +++ b/src/common/p25/lc/tdulc/LC_GROUP.cpp @@ -48,7 +48,8 @@ bool LC_GROUP::decode(const uint8_t* data) m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag m_priority = (rs[2U] & 0x07U); // Priority - m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFU); // Talkgroup Address + m_explicitId = (rs[3U] & 0x01U) == 0x01U; // Explicit Source ID Flag + m_dstId = (uint32_t)((rsValue >> 16) & 0xFFFFU); // Talkgroup Address m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address return true; @@ -67,7 +68,8 @@ void LC_GROUP::encode(uint8_t* data) (m_emergency ? 0x80U : 0x00U) + // Emergency Flag (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag (m_priority & 0x07U); // Priority - rsValue = (rsValue << 24) + m_dstId; // Talkgroup Address + rsValue = (rsValue << 8) + (m_explicitId ? 0x01U : 0x00U); // Explicit Source ID Flag + rsValue = (rsValue << 16) + m_dstId; // Talkgroup Address rsValue = (rsValue << 24) + m_srcId; // Source Radio Address std::unique_ptr rs = TDULC::fromValue(rsValue); diff --git a/src/common/p25/lc/tdulc/LC_GROUP.h b/src/common/p25/lc/tdulc/LC_GROUP.h index e80d7855b..cb2e4c04f 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP.h +++ b/src/common/p25/lc/tdulc/LC_GROUP.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) 2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -13,8 +13,8 @@ * @file LC_GROUP.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_GROUP_H__) -#define __P25_LC_TSBK__LC_GROUP_H__ +#if !defined(__P25_LC_TDULC__LC_GROUP_H__) +#define __P25_LC_TDULC__LC_GROUP_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_GROUP_H__ +#endif // __P25_LC_TDULC__LC_GROUP_H__ diff --git a/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h b/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h index de066ac47..5f9a2b335 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h +++ b/src/common/p25/lc/tdulc/LC_GROUP_UPDT.h @@ -13,8 +13,8 @@ * @file LC_GROUP_UPDT.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_GROUP_UPDT_H__) -#define __P25_LC_TSBK__LC_GROUP_UPDT_H__ +#if !defined(__P25_LC_TDULC__LC_GROUP_UPDT_H__) +#define __P25_LC_TDULC__LC_GROUP_UPDT_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_GROUP_UPDT_H__ +#endif // __P25_LC_TDULC__LC_GROUP_UPDT_H__ diff --git a/src/common/p25/lc/tdulc/LC_IDEN_UP.h b/src/common/p25/lc/tdulc/LC_IDEN_UP.h index 97c295fae..fcc3048b2 100644 --- a/src/common/p25/lc/tdulc/LC_IDEN_UP.h +++ b/src/common/p25/lc/tdulc/LC_IDEN_UP.h @@ -13,8 +13,8 @@ * @file LC_IDEN_UP.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_IDEN_UP_H__) -#define __P25_LC_TSBK__LC_IDEN_UP_H__ +#if !defined(__P25_LC_TDULC__LC_IDEN_UP_H__) +#define __P25_LC_TDULC__LC_IDEN_UP_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_IDEN_UP_H__ +#endif // __P25_LC_TDULC__LC_IDEN_UP_H__ diff --git a/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h b/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h index 9cfb00c0e..ca41156c9 100644 --- a/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.h @@ -13,8 +13,8 @@ * @file LC_NET_STS_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_NET_STS_BCAST_H__) -#define __P25_LC_TSBK__LC_NET_STS_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_NET_STS_BCAST_H__) +#define __P25_LC_TDULC__LC_NET_STS_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_NET_STS_BCAST_H__ +#endif // __P25_LC_TDULC__LC_NET_STS_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_PRIVATE.h b/src/common/p25/lc/tdulc/LC_PRIVATE.h index 869a96e2f..8b24a655c 100644 --- a/src/common/p25/lc/tdulc/LC_PRIVATE.h +++ b/src/common/p25/lc/tdulc/LC_PRIVATE.h @@ -13,8 +13,8 @@ * @file LC_PRIVATE.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_PRIVATE_H__) -#define __P25_LC_TSBK__LC_PRIVATE_H__ +#if !defined(__P25_LC_TDULC__LC_PRIVATE_H__) +#define __P25_LC_TDULC__LC_PRIVATE_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_PRIVATE_H__ +#endif // __P25_LC_TDULC__LC_PRIVATE_H__ diff --git a/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h b/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h index 80b5afdda..ba8567aa7 100644 --- a/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h @@ -13,8 +13,8 @@ * @file LC_RFSS_STS_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_RFSS_STS_BCAST_H__) -#define __P25_LC_TSBK__LC_RFSS_STS_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_RFSS_STS_BCAST_H__) +#define __P25_LC_TDULC__LC_RFSS_STS_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_RFSS_STS_BCAST_H__ +#endif // __P25_LC_TDULC__LC_RFSS_STS_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h index 064062ccb..e8f81678b 100644 --- a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h +++ b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h @@ -13,8 +13,8 @@ * @file LC_SYS_SRV_BCAST.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_SYS_SRV_BCAST_H__) -#define __P25_LC_TSBK__LC_SYS_SRV_BCAST_H__ +#if !defined(__P25_LC_TDULC__LC_SYS_SRV_BCAST_H__) +#define __P25_LC_TDULC__LC_SYS_SRV_BCAST_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_SYS_SRV_BCAST_H__ +#endif // __P25_LC_TDULC__LC_SYS_SRV_BCAST_H__ diff --git a/src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp b/src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp new file mode 100644 index 000000000..e27fae283 --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_TDULC_RAW.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/lc/tdulc/LC_TDULC_RAW.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::lc; +using namespace p25::lc::tdulc; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the LC_TDULC_RAW class. */ + +LC_TDULC_RAW::LC_TDULC_RAW() : TDULC(), + m_tdulc(nullptr) +{ + m_lco = LCO::GROUP; +} + +/* Finalizes a new instance of the LC_TDULC_RAW class. */ + +LC_TDULC_RAW::~LC_TDULC_RAW() +{ + if (m_tdulc != nullptr) { + delete m_tdulc; + } +} + +/* Decode a terminator data unit w/ link control. */ + +bool LC_TDULC_RAW::decode(const uint8_t* data) +{ + assert(data != nullptr); + + return decode(data, false); +} + +/* Encode a terminator data unit w/ link control. */ + +void LC_TDULC_RAW::encode(uint8_t* data) +{ + assert(data != nullptr); + assert(m_tdulc != nullptr); + + encode(data, false); +} + +/* Decode a terminator data unit w/ link control. */ + +bool LC_TDULC_RAW::decode(const uint8_t* data, bool rawTDULC) +{ + assert(data != nullptr); + + if (m_tdulc != nullptr) + delete[] m_tdulc; + + m_tdulc = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES + 1U]; + ::memset(m_tdulc, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + + bool ret = TDULC::decode(data, m_tdulc, rawTDULC); + if (!ret) + return false; + + return true; +} + +/* Encode a terminator data unit w/ link control. */ + +void LC_TDULC_RAW::encode(uint8_t* data, bool rawTDULC) +{ + assert(data != nullptr); + assert(m_tdulc != nullptr); + + /* stub */ + + TDULC::encode(data, m_tdulc, rawTDULC); +} + +/* Sets the TDULC to encode. */ + +void LC_TDULC_RAW::setTDULC(const uint8_t* tdulc) +{ + assert(tdulc != nullptr); + + m_tdulc = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; + ::memset(m_tdulc, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + + ::memcpy(m_tdulc, tdulc, P25_TDULC_PAYLOAD_LENGTH_BYTES); +} diff --git a/src/common/p25/lc/tdulc/LC_TDULC_RAW.h b/src/common/p25/lc/tdulc/LC_TDULC_RAW.h new file mode 100644 index 000000000..e7cf5ce2d --- /dev/null +++ b/src/common/p25/lc/tdulc/LC_TDULC_RAW.h @@ -0,0 +1,86 @@ +// 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 LC_TDULC_RAW.h + * @ingroup p25_lc + * @file LC_TDULC_RAW.cpp + * @ingroup p25_lc + */ +#if !defined(__P25_LC_TDULC__RAW_H__) +#define __P25_LC_TDULC__RAW_H__ + +#include "common/Defines.h" +#include "common/p25/lc/TDULC.h" + +namespace p25 +{ + namespace lc + { + namespace tdulc + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a mechanism to generate raw TDULC data from bytes. + * @ingroup p25_lc + */ + class HOST_SW_API LC_TDULC_RAW : public TDULC { + public: + /** + * @brief Initializes a new instance of the LC_TDULC_RAW class. + */ + LC_TDULC_RAW(); + /** + * @brief Finalizes a instance of the LC_TDULC_RAW class. + */ + ~LC_TDULC_RAW(); + + /** + * @brief Decode a terminator data unit w/ link control. + * @param[in] data Buffer containing a TDULC to decode. + * @returns bool True, if TDULC decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a terminator data unit w/ link control. + * @param[out] data Buffer to encode a TDULC. + */ + void encode(uint8_t* data) override; + + /** + * @brief Decode a terminator data unit w/ link control. + * @param[in] data Buffer containing a TDULC to decode. + * @param rawTDULC Flag indicating whether or not the passed buffer is raw. + * @returns bool True, if TDULC decoded, otherwise false. + */ + bool decode(const uint8_t* data, bool rawTDULC); + /** + * @brief Encode a terminator data unit w/ link control. + * @param rawTDULC Flag indicating whether or not the output buffer is raw. + * @param[out] data Buffer to encode a TDULC. + */ + void encode(uint8_t* data, bool rawTDULC); + + /** + * @brief Sets the TDULC to encode. + * @param[in] tdulc Buffer containing TDULC to encode. + */ + void setTDULC(const uint8_t* tdulc); + + private: + uint8_t* m_tdulc; + }; + } // namespace tdulc + } // namespace lc +} // namespace p25 + +#endif // __P25_LC_TDULC__RAW_H__ diff --git a/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h b/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h index 8b672d4b5..e8c66a56a 100644 --- a/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h +++ b/src/common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h @@ -13,8 +13,8 @@ * @file LC_TEL_INT_VCH_USER.cpp * @ingroup p25_lc */ -#if !defined(__P25_LC_TSBK__LC_TEL_INT_VCH_USER_H__) -#define __P25_LC_TSBK__LC_TEL_INT_VCH_USER_H__ +#if !defined(__P25_LC_TDULC__LC_TEL_INT_VCH_USER_H__) +#define __P25_LC_TDULC__LC_TEL_INT_VCH_USER_H__ #include "common/Defines.h" #include "common/p25/lc/TDULC.h" @@ -56,4 +56,4 @@ namespace p25 } // namespace lc } // namespace p25 -#endif // __P25_LC_TSBK__LC_TEL_INT_VCH_USER_H__ +#endif // __P25_LC_TDULC__LC_TEL_INT_VCH_USER_H__ diff --git a/src/common/p25/lc/tdulc/TDULCFactory.cpp b/src/common/p25/lc/tdulc/TDULCFactory.cpp index 4d5a98902..e8a050881 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.cpp +++ b/src/common/p25/lc/tdulc/TDULCFactory.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" @@ -53,19 +53,19 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); #if DEBUG_P25_TDULC - Utils::dump(2U, "TDULCFactory::decode(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULCFactory::createTDULC(), TDULC RS", rs, P25_TDULC_LENGTH_BYTES); #endif // decode RS (24,12,13) FEC try { bool ret = m_rs.decode241213(rs); if (!ret) { - LogError(LOG_P25, "TDULCFactory::decode(), failed to decode RS (24,12,13) FEC"); + LogError(LOG_P25, "TDULCFactory::createTDULC(), failed to decode RS (24,12,13) FEC"); return nullptr; } } catch (...) { - Utils::dump(2U, "P25, RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); + Utils::dump(2U, "P25, TDULCFactory::createTDULC(), RS excepted with input data", rs, P25_TDULC_LENGTH_BYTES); return nullptr; } @@ -81,8 +81,10 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) return decode(new LC_TEL_INT_VCH_USER(), data); case LCO::CALL_TERM: return decode(new LC_CALL_TERM(), data); + case LCO::EXPLICIT_SOURCE_ID: + return decode(new LC_EXPLICIT_SOURCE_ID(), data); default: - LogError(LOG_P25, "TDULCFactory::create(), unknown TDULC LCO value, lco = $%02X", lco); + LogError(LOG_P25, "TDULCFactory::createTDULC(), unknown TDULC LCO value, lco = $%02X", lco); break; } diff --git a/src/common/p25/lc/tdulc/TDULCFactory.h b/src/common/p25/lc/tdulc/TDULCFactory.h index d8704c9da..632ecd175 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.h +++ b/src/common/p25/lc/tdulc/TDULCFactory.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 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -23,6 +23,7 @@ #include "common/p25/lc/tdulc/LC_ADJ_STS_BCAST.h" #include "common/p25/lc/tdulc/LC_CALL_TERM.h" #include "common/p25/lc/tdulc/LC_CONV_FALLBACK.h" +#include "common/p25/lc/tdulc/LC_EXPLICIT_SOURCE_ID.h" #include "common/p25/lc/tdulc/LC_GROUP_UPDT.h" #include "common/p25/lc/tdulc/LC_GROUP.h" #include "common/p25/lc/tdulc/LC_IDEN_UP.h" @@ -31,6 +32,7 @@ #include "common/p25/lc/tdulc/LC_RFSS_STS_BCAST.h" #include "common/p25/lc/tdulc/LC_SYS_SRV_BCAST.h" #include "common/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h" +#include "common/p25/lc/tdulc/LC_TDULC_RAW.h" #include "common/p25/lc/tdulc/LC_FAILSOFT.h" diff --git a/src/common/p25/lc/tsbk/TSBKFactory.cpp b/src/common/p25/lc/tsbk/TSBKFactory.cpp index 3f6a596ba..742d7d491 100644 --- a/src/common/p25/lc/tsbk/TSBKFactory.cpp +++ b/src/common/p25/lc/tsbk/TSBKFactory.cpp @@ -101,7 +101,7 @@ std::unique_ptr TSBKFactory::createTSBK(const uint8_t* data, bool rawTSBK) return nullptr; } catch (...) { - Utils::dump(2U, "P25, decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump(2U, "P25, TSBKFactory::createTSBK(), decoding excepted with input data", tsbk, P25_TSBK_LENGTH_BYTES); return nullptr; } } diff --git a/src/common/zlib/Compression.cpp b/src/common/zlib/Compression.cpp index 0d8dd5d28..d3cf80d37 100644 --- a/src/common/zlib/Compression.cpp +++ b/src/common/zlib/Compression.cpp @@ -24,7 +24,7 @@ using namespace compress; /* Compress the given input buffer. */ -uint8_t* Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen) +UInt8Array Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen) { assert(buffer != nullptr); assert(len > 0U); @@ -33,8 +33,8 @@ uint8_t* Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* co *compressedLen = 0U; } - uint8_t* data = new uint8_t[len]; - ::memset(data, 0x00U, len); + uint8_t* data = new uint8_t[len + 1U]; + ::memset(data, 0x00U, len + 1U); ::memcpy(data, buffer, len); // compression structures @@ -87,18 +87,25 @@ uint8_t* Compression::compress(const uint8_t* buffer, uint32_t len, uint32_t* co Utils::dump(2U, "Compression::compress(), Compressed Data", compressed, strm.total_out); #endif - // reuse data buffer to return compressed data delete[] data; - data = new uint8_t[strm.total_out]; - ::memset(data, 0x00U, strm.total_out); - ::memcpy(data, compressed, strm.total_out); - return data; + if (strm.total_out == 0U) { + LogError(LOG_HOST, "ZLIB compression resulted in zero bytes of output"); + return nullptr; // return nullptr if no data was compressed + } + + // return compressed data + UInt8Array out = std::make_unique(strm.total_out + 1U); + ::memset(out.get(), 0x00U, strm.total_out + 1U); + ::memcpy(out.get(), compressed, strm.total_out); + + compressedData.clear(); // clear the vector to release memory + return out; } /* Decompress the given input buffer. */ -uint8_t* Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen) +UInt8Array Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen) { assert(buffer != nullptr); assert(len > 0U); @@ -107,8 +114,8 @@ uint8_t* Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* *decompressedLen = 0U; } - uint8_t* data = new uint8_t[len]; - ::memset(data, 0x00U, len); + uint8_t* data = new uint8_t[len + 1U]; + ::memset(data, 0x00U, len + 1U); ::memcpy(data, buffer, len); // compression structures @@ -159,11 +166,18 @@ uint8_t* Compression::decompress(const uint8_t* buffer, uint32_t len, uint32_t* Utils::dump(2U, "Compression::decompress(), Decompressed Data", decompressed, strm.total_out); #endif - // reuse data buffer to return decompressed data delete[] data; - data = new uint8_t[strm.total_out]; - ::memset(data, 0x00U, strm.total_out); - ::memcpy(data, decompressed, strm.total_out); - return data; + if (strm.total_out == 0U) { + LogError(LOG_HOST, "ZLIB decompression resulted in zero bytes of output"); + return nullptr; // return nullptr if no data was decompressed + } + + // return decompressed data + UInt8Array out = std::make_unique(strm.total_out + 1U); + ::memset(out.get(), 0x00U, strm.total_out + 1U); + ::memcpy(out.get(), decompressed, strm.total_out); + + decompressedData.clear(); // clear the vector to release memory + return out; } diff --git a/src/common/zlib/Compression.h b/src/common/zlib/Compression.h index f740aea12..74519b7db 100644 --- a/src/common/zlib/Compression.h +++ b/src/common/zlib/Compression.h @@ -39,18 +39,18 @@ namespace compress * @param[in] buffer Buffer containing data to zlib compress. * @param[in] len Length of data to compress. * @param[out] compressedLen Length of compressed data. - * @returns uint8_t* Buffer containing compressed data. + * @returns UInt8Array Buffer containing compressed data. */ - static uint8_t* compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen); + static UInt8Array compress(const uint8_t* buffer, uint32_t len, uint32_t* compressedLen); /** * @brief Decompress the given input buffer using zlib compression. * @param[in] buffer Buffer containing zlib compressed data. * @param[in] len Length of compressed data. * @param[out] decompressedLen Length of decompressed data. - * @returns uint8_t* Buffer containing decompressed data. + * @returns UInt8Array Buffer containing decompressed data. */ - static uint8_t* decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen); + static UInt8Array decompress(const uint8_t* buffer, uint32_t len, uint32_t* decompressedLen); }; } // namespace compress diff --git a/src/fne/CMakeLists.txt b/src/fne/CMakeLists.txt index ae3ef35ed..2425e6c0c 100644 --- a/src/fne/CMakeLists.txt +++ b/src/fne/CMakeLists.txt @@ -4,10 +4,12 @@ # * 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 # * # */ file(GLOB dvmfne_SRC + "src/fne/lookups/*.h" + "src/fne/lookups/*.cpp" "src/fne/network/callhandler/*.h" "src/fne/network/callhandler/*.cpp" "src/fne/network/callhandler/packetdata/*.h" @@ -17,6 +19,14 @@ file(GLOB dvmfne_SRC "src/fne/network/*.h" "src/fne/network/*.cpp" "src/fne/xml/*.h" + "src/fne/win32/*.h" "src/fne/*.h" "src/fne/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB dvmfne_RC + "src/fne/win32/*.rc" +) diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index f42e9d945..247690fd2 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -15,6 +15,7 @@ #include "network/callhandler/TagDMRData.h" #include "network/callhandler/TagP25Data.h" #include "network/callhandler/TagNXDNData.h" +#include "network/callhandler/TagAnalogData.h" #include "ActivityLog.h" #include "HostFNE.h" #include "FNEMain.h" @@ -42,6 +43,8 @@ using namespace lookups; #define MIN_WORKER_CNT 4U #define MAX_WORKER_CNT 128U +#define MAX_RECOMMENDED_PEER_NETWORKS 8U + #define THREAD_CYCLE_THRESHOLD 2U #define IDLE_WARMUP_MS 5U @@ -66,6 +69,7 @@ HostFNE::HostFNE(const std::string& confFile) : m_dmrEnabled(false), m_p25Enabled(false), m_nxdnEnabled(false), + m_analogEnabled(false), m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), @@ -213,8 +217,6 @@ int HostFNE::run() #if !defined(_WIN32) if (!Thread::runAsThread(this, threadVirtualNetworking)) return EXIT_FAILURE; - if (!Thread::runAsThread(this, threadVirtualNetworkingClock)) - return EXIT_FAILURE; #endif // !defined(_WIN32) /* ** Main execution loop @@ -249,16 +251,22 @@ int HostFNE::run() network::PeerNetwork* peerNetwork = network.second; if (peerNetwork != nullptr) { peerNetwork->clock(ms); + } + } - // skip peer if it isn't enabled - if (!peerNetwork->isEnabled()) { - continue; - } - - // process peer network traffic - processPeer(peerNetwork); +#if !defined(_WIN32) + if (m_vtunEnabled) { + switch (m_packetDataMode) { + case PacketDataMode::DMR: + // TODO: not supported yet + break; + + case PacketDataMode::PROJECT25: + m_network->p25TrafficHandler()->packetData()->clock(ms); + break; } } +#endif // !defined(_WIN32) if (ms < 2U) Thread::sleep(1U); @@ -388,6 +396,10 @@ bool HostFNE::readParams() std::string talkgroupConfig = talkgroupRules["file"].as(); uint32_t talkgroupConfigReload = talkgroupRules["time"].as(30U); + yaml::Node adjSiteMapRules = masterConf["adj_site_map"]; + std::string adjSiteMapConfig = adjSiteMapRules["file"].as(); + uint32_t adjSiteMapReload = adjSiteMapRules["time"].as(30U); + yaml::Node cryptoContainer = masterConf["crypto_container"]; bool cryptoContainerEnabled = cryptoContainer["enable"].as(false); #if !defined(ENABLE_SSL) @@ -420,6 +432,14 @@ bool HostFNE::readParams() m_peerListLookup = new PeerListLookup(peerListLookupFile, peerListConfigReload, peerListLookupEnable); m_peerListLookup->read(); + LogInfo("Adjacent Site Map Lookups"); + LogInfo(" File: %s", adjSiteMapConfig.length() > 0U ? adjSiteMapConfig.c_str() : "None"); + if (adjSiteMapReload > 0U) + LogInfo(" Reload: %u mins", adjSiteMapReload); + + m_adjSiteMapLookup = new AdjSiteMapLookup(adjSiteMapConfig, adjSiteMapReload); + m_adjSiteMapLookup->read(); + // try to load peer whitelist/blacklist LogInfo("Crypto Container Lookups"); LogInfo(" Enabled: %s", cryptoContainerEnabled ? "yes" : "no"); @@ -493,7 +513,7 @@ bool HostFNE::initializeRESTAPI() // initialize network remote command if (restApiEnable) { m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, restApiSSLKey, restApiSSLCert, restApiEnableSSL, this, restApiDebug); - m_RESTAPI->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup); + m_RESTAPI->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_adjSiteMapLookup); bool ret = m_RESTAPI->open(); if (!ret) { delete m_RESTAPI; @@ -572,6 +592,7 @@ bool HostFNE::createMasterNetwork() m_dmrEnabled = masterConf["allowDMRTraffic"].as(true); m_p25Enabled = masterConf["allowP25Traffic"].as(true); m_nxdnEnabled = masterConf["allowNXDNTraffic"].as(true); + m_analogEnabled = masterConf["allowAnalogTraffic"].as(false); uint32_t parrotDelay = masterConf["parrotDelay"].as(2500U); if (m_pingTime * 1000U < parrotDelay) { @@ -587,6 +608,7 @@ bool HostFNE::createMasterNetwork() LogInfo(" Allow DMR Traffic: %s", m_dmrEnabled ? "yes" : "no"); LogInfo(" Allow P25 Traffic: %s", m_p25Enabled ? "yes" : "no"); LogInfo(" Allow NXDN Traffic: %s", m_nxdnEnabled ? "yes" : "no"); + LogInfo(" Allow Analog Traffic: %s", m_analogEnabled ? "yes" : "no"); LogInfo(" Parrot Repeat Delay: %u ms", parrotDelay); LogInfo(" Parrot Grant Demand: %s", parrotGrantDemand ? "yes" : "no"); @@ -605,11 +627,11 @@ bool HostFNE::createMasterNetwork() } // initialize networking - m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, + m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime, workerCnt); m_network->setOptions(masterConf, true); - m_network->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup); + m_network->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup, m_adjSiteMapLookup); if (m_RESTAPI != nullptr) { m_RESTAPI->setNetwork(m_network); @@ -761,6 +783,10 @@ bool HostFNE::createPeerNetworks() { yaml::Node& peerList = m_conf["peers"]; if (peerList.size() > 0U) { + if (peerList.size() > MAX_RECOMMENDED_PEER_NETWORKS) { + LogWarning(LOG_HOST, "Peer network count (%zu) exceeds the recommended maximum of %u. This may result in poor performance.", peerList.size(), MAX_RECOMMENDED_PEER_NETWORKS); + } + for (size_t i = 0; i < peerList.size(); i++) { yaml::Node& peerConf = peerList[i]; @@ -818,7 +844,8 @@ bool HostFNE::createPeerNetworks() } // initialize networking - network::PeerNetwork* network = new PeerNetwork(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, true, true, m_allowActivityTransfer, m_allowDiagnosticTransfer, false, false); + network::PeerNetwork* network = new PeerNetwork(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, true, true, + m_allowActivityTransfer, m_allowDiagnosticTransfer, false, false); network->setMetadata(identity, rxFrequency, txFrequency, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); network->setLookups(m_ridLookup, m_tidLookup); network->setPeerLookups(m_peerListLookup); @@ -827,6 +854,11 @@ bool HostFNE::createPeerNetworks() network->setPresharedKey(presharedKey); } + network->setDMRCallback(std::bind(&HostFNE::processPeerDMR, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setP25Callback(std::bind(&HostFNE::processPeerP25, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setNXDNCallback(std::bind(&HostFNE::processPeerNXDN, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setAnalogCallback(std::bind(&HostFNE::processPeerAnalog, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + /* ** Block Traffic To Peers */ @@ -969,115 +1001,92 @@ void* HostFNE::threadVirtualNetworking(void* arg) return nullptr; } +#endif // !defined(_WIN32) -/* Entry point to virtual networking clocking thread. */ +/* Processes DMR peer network traffic. */ -void* HostFNE::threadVirtualNetworkingClock(void* arg) +void HostFNE::processPeerDMR(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) { - thread_t* th = (thread_t*)arg; - if (th != nullptr) { - ::pthread_detach(th->thread); - - std::string threadName("fne:vt-clock"); - HostFNE* fne = static_cast(th->obj); - if (fne == nullptr) { - g_killed = true; - LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); - } - - if (g_killed) { - delete th; - return nullptr; - } + if (peerNetwork == nullptr) + return; // this shouldn't happen... - if (!fne->m_vtunEnabled) { - delete th; - return nullptr; - } + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); -#ifdef _GNU_SOURCE - ::pthread_setname_np(th->thread, threadName.c_str()); -#endif // _GNU_SOURCE + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; - if (fne->m_tun != nullptr) { - StopWatch stopWatch; - stopWatch.start(); + // process DMR data + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->dmrTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); + } +} - while (!g_killed) { - uint32_t ms = stopWatch.elapsed(); - stopWatch.start(); +/* Processes P25 peer network traffic. */ - // clock traffic handler - switch (fne->m_packetDataMode) { - case PacketDataMode::DMR: - // TODO: not supported yet - break; +void HostFNE::processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) +{ + if (peerNetwork == nullptr) + return; // this shouldn't happen... - case PacketDataMode::PROJECT25: - fne->m_network->p25TrafficHandler()->packetData()->clock(ms); - break; - } + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; - if (ms < THREAD_CYCLE_THRESHOLD) - Thread::sleep(THREAD_CYCLE_THRESHOLD); - } - } + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); - delete th; + // process P25 data + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->p25TrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } - - return nullptr; } -#endif // !defined(_WIN32) -/* Processes any peer network traffic. */ +/* Processes NXDN peer network traffic. */ -void HostFNE::processPeer(network::PeerNetwork* peerNetwork) +void HostFNE::processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) { if (peerNetwork == nullptr) return; // this shouldn't happen... + + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; + if (peerNetwork->getStatus() != NET_STAT_RUNNING) return; - // process DMR data - if (peerNetwork->hasDMRData()) { - uint32_t length = 100U; - bool ret = false; - UInt8Array data = peerNetwork->readDMR(ret, length); - if (ret) { - uint32_t peerId = peerNetwork->getPeerId(); - uint32_t slotNo = (data[15U] & 0x80U) == 0x80U ? 2U : 1U; - uint32_t streamId = peerNetwork->getRxDMRStreamId(slotNo); - - m_network->dmrTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); - } + // process NXDN data + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->nxdnTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } +} - // process P25 data - if (peerNetwork->hasP25Data()) { - uint32_t length = 100U; - bool ret = false; - UInt8Array data = peerNetwork->readP25(ret, length); - if (ret) { - uint32_t peerId = peerNetwork->getPeerId(); - uint32_t streamId = peerNetwork->getRxP25StreamId(); - - m_network->p25TrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); - } - } +/* Processes analog peer network traffic. */ - // process NXDN data - if (peerNetwork->hasNXDNData()) { - uint32_t length = 100U; - bool ret = false; - UInt8Array data = peerNetwork->readNXDN(ret, length); - if (ret) { - uint32_t peerId = peerNetwork->getPeerId(); - uint32_t streamId = peerNetwork->getRxNXDNStreamId(); - - m_network->nxdnTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId, true); - } +void HostFNE::processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader) +{ + if (peerNetwork == nullptr) + return; // this shouldn't happen... + + // skip peer if it isn't enabled + if (!peerNetwork->isEnabled()) + return; + + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; + + // process analog data + if (length > 0U) { + uint32_t peerId = peerNetwork->getPeerId(); + m_network->analogTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), peerNetwork->pktLastSeq(), streamId, true); } } diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index d3e0da486..4c76c47e6 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.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 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -20,6 +20,7 @@ #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" +#include "common/lookups/AdjSiteMapLookup.h" #include "common/network/viface/VIFace.h" #include "common/yaml/Yaml.h" #include "common/Timer.h" @@ -42,6 +43,7 @@ namespace network { namespace callhandler { namespace packetdata { class HOST_SW namespace network { namespace callhandler { class HOST_SW_API TagP25Data; } } namespace network { namespace callhandler { namespace packetdata { class HOST_SW_API P25PacketData; } } } namespace network { namespace callhandler { class HOST_SW_API TagNXDNData; } } +namespace network { namespace callhandler { class HOST_SW_API TagAnalogData; } } // --------------------------------------------------------------------------- // Class Declaration @@ -88,6 +90,7 @@ class HOST_SW_API HostFNE { friend class network::callhandler::TagP25Data; friend class network::callhandler::packetdata::P25PacketData; friend class network::callhandler::TagNXDNData; + friend class network::callhandler::TagAnalogData; network::FNENetwork* m_network; network::DiagNetwork* m_diagNetwork; @@ -100,10 +103,12 @@ class HOST_SW_API HostFNE { bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_analogEnabled; lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; + lookups::AdjSiteMapLookup* m_adjSiteMapLookup; CryptoContainer* m_cryptoLookup; @@ -168,18 +173,47 @@ class HOST_SW_API HostFNE { * @returns void* (Ignore) */ static void* threadVirtualNetworking(void* arg); +#endif // !defined(_WIN32) /** - * @brief Entry point to virtual networking clocking thread. - * @param arg Instance of the thread_t structure. - * @returns void* (Ignore) + * @brief Processes DMR peer network traffic. + * @param data Buffer containing DMR data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. */ - static void* threadVirtualNetworkingClock(void* arg); -#endif // !defined(_WIN32) + void processPeerDMR(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Processes P25 peer network traffic. + * @param data Buffer containing P25 data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. + */ + void processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Processes NXDN peer network traffic. + * @param data Buffer containing NXDN data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. + */ + void processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); /** - * @brief Processes any peer network traffic. - * @param peerNetwork Instance of PeerNetwork to process traffic for. + * @brief Processes analog peer network traffic. + * @param data Buffer containing analog data. + * @param length Length of the buffer. + * @param streamId Stream ID. + * @param fneHeader FNE header for the packet. + * @param rtpHeader RTP header for the packet. */ - void processPeer(network::PeerNetwork* peerNetwork); + void processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, + const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); }; #endif // __HOST_FNE_H__ diff --git a/src/fne/lookups/AffiliationLookup.cpp b/src/fne/lookups/AffiliationLookup.cpp new file mode 100644 index 000000000..b84944327 --- /dev/null +++ b/src/fne/lookups/AffiliationLookup.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * 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/Log.h" +#include "lookups/AffiliationLookup.h" + +using namespace fne_lookups; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the AffiliationLookup class. */ + +AffiliationLookup::AffiliationLookup(const std::string name, ::lookups::ChannelLookup* chLookup, bool verbose) : + ::lookups::AffiliationLookup(name, chLookup, verbose), + m_unitRegPeerTable() +{ + m_unitRegPeerTable.clear(); +} + +/* Finalizes a instance of the AffiliationLookup class. */ + +AffiliationLookup::~AffiliationLookup() = default; + +/* Helper to group affiliate a source ID. */ + +void AffiliationLookup::unitReg(uint32_t srcId, uint32_t ssrc) +{ + if (isUnitReg(srcId)) { + return; + } + + ::lookups::AffiliationLookup::unitReg(srcId); + + m_unitRegPeerTable[srcId] = ssrc; +} + +/* Helper to group unaffiliate a source ID. */ + +bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) +{ + bool ret = false; + + if (!isUnitReg(srcId)) { + return false; + } + + ret = ::lookups::AffiliationLookup::unitDereg(srcId, automatic); + + if (ret) { + // lookup dynamic table entry + if (m_unitRegPeerTable.find(srcId) == m_unitRegPeerTable.end()) { + return false; + } + + // remove dynamic table entry + try { + uint32_t entry = m_unitRegPeerTable.at(srcId); // this value will get discarded + (void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard + m_unitRegPeerTable.erase(srcId); + return true; + } + catch (...) { + return false; + } + } + + return false; +} + +/* Helper to get the originating network peer ID by the registered source ID. */ + +uint32_t AffiliationLookup::getSSRCByUnitReg(uint32_t srcId) +{ + // lookup dynamic channel grant table entry + m_unitRegPeerTable.lock(false); + for (auto entry : m_grantChTable) { + if (entry.first == srcId) { + m_unitRegPeerTable.unlock(); + return entry.second; + } + } + m_unitRegPeerTable.unlock(); + + return 0U; +} + +/* Helper to release unit registrations. */ + +void AffiliationLookup::clearUnitReg() +{ + ::lookups::AffiliationLookup::clearUnitReg(); + m_unitRegPeerTable.clear(); +} diff --git a/src/fne/lookups/AffiliationLookup.h b/src/fne/lookups/AffiliationLookup.h new file mode 100644 index 000000000..d6faac605 --- /dev/null +++ b/src/fne/lookups/AffiliationLookup.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup fne_lookups Lookup Tables + * @brief Implementation for various data lookup tables. + * @ingroup fne + * + * @file P25AffiliationLookup.h + * @ingroup fne_lookups + * @file P25AffiliationLookup.cpp + * @ingroup fne_lookups + */ +#if !defined(__FNE_AFFILIATION_LOOKUP_H__) +#define __FNE_AFFILIATION_LOOKUP_H__ + +#include "Defines.h" +#include "common/lookups/AffiliationLookup.h" +#include "common/lookups/ChannelLookup.h" + +namespace fne_lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a lookup table class that contains subscriber registration + * and group affiliation information. + * @ingroup fne_lookups + */ + class HOST_SW_API AffiliationLookup : public ::lookups::AffiliationLookup { + public: + /** + * @brief Initializes a new instance of the AffiliationLookup class. + * @param name Name of lookup table. + * @param channelLookup Instance of the channel lookup class. + * @param verbose Flag indicating whether verbose logging is enabled. + */ + AffiliationLookup(const std::string name, ::lookups::ChannelLookup* chLookup, bool verbose); + /** + * @brief Finalizes a instance of the AffiliationLookup class. + */ + ~AffiliationLookup() override; + + /** @name Unit Registrations */ + /** + * @brief Helper to register a source ID. + * @param srcId Source Radio ID. + * @param ssrc Originating Peer ID. + */ + void unitReg(uint32_t srcId, uint32_t ssrc); + /** + * @brief Helper to deregister a source ID. + * @param srcId Source Radio ID. + * @param automatic Flag indicating the deregistration is a result of an automated timer. + * @returns bool True, if source ID is deregistered, otherwise false. + */ + bool unitDereg(uint32_t srcId, bool automatic = false) override; + /** + * @brief Helper to get the originating network peer ID by the registered source ID. + * @param srcId Source Radio ID. + * @returns uint32_t Originating Peer ID. + */ + uint32_t getSSRCByUnitReg(uint32_t srcId); + /** + * @brief Helper to release unit registrations. + */ + void clearUnitReg() override; + /** @} */ + + protected: + concurrent::unordered_map m_unitRegPeerTable; + }; +} // namespace fne_lookups + +#endif // __FNE_AFFILIATION_LOOKUP_H__ diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 42834eed9..fb5e260ee 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -70,7 +70,7 @@ void DiagNetwork::processNetwork() UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Message", buffer.get(), length); + Utils::dump(1U, "DiagNetwork::processNetwork(), Network Message", buffer.get(), length); uint32_t peerId = fneHeader.getPeerId(); @@ -360,7 +360,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) default: network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length); + Utils::dump("Unknown transfer opcode from the peer", req->buffer, req->length); break; } } @@ -379,7 +379,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) DECLARE_UINT8_ARRAY(rawPayload, req->length); ::memcpy(rawPayload, req->buffer, req->length); - // Utils::dump(1U, "Raw Payload", rawPayload, req->length); + // Utils::dump(1U, "DiagNetwork::taskNetworkRx(), PEER_LINK, Raw Payload", rawPayload, req->length); if (network->m_peerLinkActPkt.find(peerId) == network->m_peerLinkActPkt.end()) { network->m_peerLinkActPkt.insert(peerId, FNENetwork::PacketBufferEntry()); @@ -435,6 +435,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) if (!v.is()) { LogError(LOG_NET, "PEER %u error parsing active peer list, data was not valid", peerId); pkt.buffer->clear(); + delete pkt.buffer; pkt.streamId = 0U; if (decompressed != nullptr) { delete[] decompressed; diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 3e75a1a7a..7f04225c2 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -18,6 +18,7 @@ #include "network/callhandler/TagDMRData.h" #include "network/callhandler/TagP25Data.h" #include "network/callhandler/TagNXDNData.h" +#include "network/callhandler/TagAnalogData.h" #include "fne/ActivityLog.h" #include "HostFNE.h" @@ -56,12 +57,13 @@ std::timed_mutex FNENetwork::m_keyQueueMutex; /* Initializes a new instance of the FNENetwork class. */ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, - bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool parrotGrantDemand, + bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, bool analog, uint32_t parrotDelay, bool parrotGrantDemand, bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt) : BaseNetwork(peerId, true, debug, true, true, allowActivityTransfer, allowDiagnosticTransfer), m_tagDMR(nullptr), m_tagP25(nullptr), m_tagNXDN(nullptr), + m_tagAnalog(nullptr), m_host(host), m_address(address), m_port(port), @@ -69,6 +71,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_dmrEnabled(dmr), m_p25Enabled(p25), m_nxdnEnabled(nxdn), + m_analogEnabled(analog), m_parrotDelay(parrotDelay), m_parrotDelayTimer(1000U, 0U, parrotDelay), m_parrotGrantDemand(parrotGrantDemand), @@ -76,6 +79,8 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), + m_adjSiteMapLookup(nullptr), + m_cryptoLookup(nullptr), m_status(NET_STAT_INVALID), m_peers(), m_peerLinkPeers(), @@ -92,10 +97,13 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_allowConvSiteAffOverride(false), m_disallowCallTerm(false), m_restrictGrantToAffOnly(false), + m_restrictPVCallToRegOnly(false), m_enableInCallCtrl(true), m_rejectUnknownRID(false), - m_filterHeaders(true), + m_maskOutboundPeerID(false), + m_maskOutboundPeerIDForNonPL(false), m_filterTerminators(true), + m_forceListUpdate(false), m_disallowU2U(false), m_dropU2UPeerTable(), m_enableInfluxDB(false), @@ -109,6 +117,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_disablePacketData(false), m_dumpPacketData(false), m_verbosePacketData(false), + m_logDenials(false), m_reportPeerPing(reportPeerPing), m_verbose(verbose) { @@ -120,6 +129,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_tagDMR = new TagDMRData(this, debug); m_tagP25 = new TagP25Data(this, debug); m_tagNXDN = new TagNXDNData(this, debug); + m_tagAnalog = new TagAnalogData(this, debug); } /* Finalizes a instance of the FNENetwork class. */ @@ -129,6 +139,7 @@ FNENetwork::~FNENetwork() delete m_tagDMR; delete m_tagP25; delete m_tagNXDN; + delete m_tagAnalog; } /* Helper to set configuration options. */ @@ -140,6 +151,8 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_allowConvSiteAffOverride = conf["allowConvSiteAffOverride"].as(true); m_enableInCallCtrl = conf["enableInCallCtrl"].as(false); m_rejectUnknownRID = conf["rejectUnknownRID"].as(false); + m_maskOutboundPeerID = conf["maskOutboundPeerID"].as(false); + m_maskOutboundPeerIDForNonPL = conf["maskOutboundPeerIDForNonPeerLink"].as(false); m_disallowCallTerm = conf["disallowCallTerm"].as(false); m_softConnLimit = conf["connectionLimit"].as(MAX_HARD_CONN_CAP); @@ -166,13 +179,15 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_parrotOnlyOriginating = conf["parrotOnlyToOrginiatingPeer"].as(false); m_restrictGrantToAffOnly = conf["restrictGrantToAffiliatedOnly"].as(false); - m_filterHeaders = conf["filterHeaders"].as(true); + m_restrictPVCallToRegOnly = conf["restrictPrivateCallToRegOnly"].as(false); m_filterTerminators = conf["filterTerminators"].as(true); m_disablePacketData = conf["disablePacketData"].as(false); m_dumpPacketData = conf["dumpPacketData"].as(false); m_verbosePacketData = conf["verbosePacketData"].as(false); + m_logDenials = conf["logDenials"].as(false); + /* ** Drop Unit to Unit Peers */ @@ -202,8 +217,13 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) 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(" Log Traffic Denials: %s", m_logDenials ? "yes" : "no"); + LogInfo(" Mask Outbound Traffic Peer ID: %s", m_maskOutboundPeerID ? "yes" : "no"); + if (m_maskOutboundPeerIDForNonPL) { + LogInfo(" Mask Outbound Traffic Peer ID for Non-Peer Link: yes"); + } LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); - LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); + LogInfo(" Restrict private call to registered units: %s", m_restrictPVCallToRegOnly ? "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"); @@ -221,12 +241,13 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) /* Sets the instances of the Radio ID, Talkgroup ID Peer List, and Crypto lookup tables. */ void FNENetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::PeerListLookup* peerListLookup, - CryptoContainer* cryptoLookup) + CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; m_peerListLookup = peerListLookup; m_cryptoLookup = cryptoLookup; + m_adjSiteMapLookup = adjSiteMapLookup; } /* Sets endpoint preshared encryption key. */ @@ -254,7 +275,7 @@ void FNENetwork::processNetwork() UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); if (length > 0) { if (m_debug) - Utils::dump(1U, "Network Message", buffer.get(), length); + Utils::dump(1U, "FNENetwork::processNetwork(), Network Message", buffer.get(), length); uint32_t peerId = fneHeader.getPeerId(); @@ -441,9 +462,14 @@ void FNENetwork::clock(uint32_t ms) if (m_tagNXDN->hasParrotFrames()) { m_tagNXDN->playbackParrot(); } + + // if the analog handler has parrot frames to playback, playback a frame + if (m_tagAnalog->hasParrotFrames()) { + m_tagAnalog->playbackParrot(); + } } - if (!m_tagDMR->hasParrotFrames() && !m_tagP25->hasParrotFrames() && !m_tagNXDN->hasParrotFrames() && + if (!m_tagDMR->hasParrotFrames() && !m_tagP25->hasParrotFrames() && !m_tagNXDN->hasParrotFrames() && !m_tagAnalog->hasParrotFrames() && m_parrotDelayTimer.isRunning() && m_parrotDelayTimer.hasExpired()) { m_parrotDelayTimer.stop(); } @@ -497,7 +523,8 @@ void FNENetwork::close() uint32_t streamId = createStreamId(); for (auto peer : m_peers) { - writePeer(peer.first, { NET_FUNC::MST_DISC, NET_SUBFUNC::NOP }, buffer, 1U, RTP_END_OF_CALL_SEQ, streamId, false); + writePeer(peer.first, m_peerId, { NET_FUNC::MST_DISC, NET_SUBFUNC::NOP }, buffer, 1U, RTP_END_OF_CALL_SEQ, + streamId, false); } } @@ -546,6 +573,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (req->length > 0) { uint32_t peerId = req->fneHeader.getPeerId(); + uint32_t ssrc = req->rtpHeader.getSSRC(); uint32_t streamId = req->fneHeader.getStreamId(); // determine if this packet is late (i.e. are we processing this packet more than 200ms after it was received?) @@ -614,7 +642,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { if (network->m_dmrEnabled) { if (network->m_tagDMR != nullptr) { - network->m_tagDMR->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagDMR->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); } } else { network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); @@ -640,7 +668,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { if (network->m_p25Enabled) { if (network->m_tagP25 != nullptr) { - network->m_tagP25->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagP25->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); } } else { network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); @@ -666,7 +694,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { if (network->m_nxdnEnabled) { if (network->m_tagNXDN != nullptr) { - network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); } } else { network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); @@ -680,8 +708,34 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_analogEnabled) { + if (network->m_tagAnalog != nullptr) { + network->m_tagAnalog->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); + } + } else { + network->writePeerNAK(peerId, streamId, TAG_ANALOG_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + } + } + else { + network->writePeerNAK(peerId, TAG_ANALOG_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } + } + break; + default: - Utils::dump("unknown protocol opcode from peer", req->buffer, req->length); + Utils::dump("Unknown protocol opcode from peer", req->buffer, req->length); break; } } @@ -1112,7 +1166,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) break; default: network->writePeerNAK(peerId, streamId, TAG_REPEATER_GRANT, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("unknown state for grant request from the peer", req->buffer, req->length); + Utils::dump("Unknown state for grant request from the peer", req->buffer, req->length); break; } } @@ -1177,7 +1231,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (network->m_debug) { LogDebugEx(LOG_HOST, "FNENetwork::threadedNetworkRx()", "keyLength = %u", keyLength); - Utils::dump(1U, "Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + Utils::dump(1U, "FNENetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); } LogMessage(LOG_NET, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identity().c_str(), @@ -1208,7 +1262,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) modifyKeyRsp.encode(buffer + 11U); - network->writePeer(peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, RTP_END_OF_CALL_SEQ, network->createStreamId(), false, false, true); } else { // attempt to forward KMM key request to Peer-Link masters @@ -1301,7 +1355,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) FNEPeerConnection* connection = network->m_peers[peerId]; if (connection != nullptr) { std::string ip = udp::Socket::address(req->address); - lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; + fne_lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); @@ -1310,7 +1364,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // validate peer (simple validation really) if (connection->connected() && connection->address() == ip && aff != nullptr) { uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - aff->unitReg(srcId); + aff->unitReg(srcId, ssrc); // attempt to repeat traffic to Peer-Link masters if (network->m_host->m_peerNetworks.size() > 0) { @@ -1318,7 +1372,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isPeerLink()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false, 0U, ssrc); } } } @@ -1505,13 +1559,13 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) break; default: network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("unknown announcement opcode from the peer", req->buffer, req->length); + Utils::dump("Unknown announcement opcode from the peer", req->buffer, req->length); } } break; default: - Utils::dump("unknown opcode from the peer", req->buffer, req->length); + Utils::dump("Unknown opcode from the peer", req->buffer, req->length); break; } } @@ -1555,7 +1609,7 @@ void FNENetwork::createPeerAffiliations(uint32_t peerId, std::string peerName) erasePeerAffiliations(peerId); lookups::ChannelLookup* chLookup = new lookups::ChannelLookup(); - m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, chLookup, m_verbose); + m_peerAffiliations[peerId] = new fne_lookups::AffiliationLookup(peerName, chLookup, m_verbose); m_peerAffiliations[peerId]->setDisableUnitRegTimeout(true); // FNE doesn't allow unit registration timeouts (notification must come from the peers) } @@ -1611,6 +1665,21 @@ void FNENetwork::erasePeer(uint32_t peerId) erasePeerAffiliations(peerId); } +/* Helper to find the unit registration for the given source ID. */ + +uint32_t FNENetwork::findPeerUnitReg(uint32_t srcId) +{ + for (auto it = m_peerAffiliations.begin(); it != m_peerAffiliations.end(); ++it) { + fne_lookups::AffiliationLookup* aff = it->second; + if (aff != nullptr) { + if (aff->isUnitReg(srcId)) { + return aff->getSSRCByUnitReg(srcId); + } + } + } + + return 0U; +} /* Helper to create a JSON representation of a FNE peer connection. */ @@ -1810,11 +1879,13 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE LogInfoEx(LOG_NET, "PEER %u Peer-Link, RID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, + writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); Thread::sleep(60U); // pace block transmission } } + + pkt.clear(); } return; @@ -1962,8 +2033,6 @@ void FNENetwork::writeBlacklistRIDs(uint32_t peerId, uint32_t streamId) void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalPeer) { - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - if (!m_tidLookup->sendTalkgroups()) { return; } @@ -1999,11 +2068,13 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP LogInfoEx(LOG_NET, "PEER %u Peer-Link, TGID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, + writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); Thread::sleep(60U); // pace block transmission } } + + pkt.clear(); } return; @@ -2145,8 +2216,6 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) { - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - // sending PEER_LINK style RID list to external peers FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { @@ -2177,11 +2246,13 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) LogInfoEx(LOG_NET, "PEER %u Peer-Link, PID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, + writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); Thread::sleep(60U); // pace block transmission } } + + pkt.clear(); } return; @@ -2206,12 +2277,12 @@ bool FNENetwork::writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::E SET_UINT24(dstId, buffer, 11U); // Destination ID buffer[14U] = slotNo; // DMR Slot No - return writePeer(peerId, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false); + return writePeer(peerId, m_peerId, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false); } /* Helper to send a data message to the specified peer with a explicit packet sequence. */ -bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, +bool FNENetwork::writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq, bool directWrite) const { if (streamId == 0U) { @@ -2229,10 +2300,26 @@ bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const pktSeq = connection->incStreamPktSeq(streamId, pktSeq); } + if (m_maskOutboundPeerID) + ssrc = m_peerId; // mask the source SSRC to our own peer ID + else { + if ((connection->isExternalPeer() && !connection->isPeerLink()) && m_maskOutboundPeerIDForNonPL) { + // if the peer is an external peer, and not a Peer-Link peer, we need to send the packet + // to the external peer with our peer ID as the source instead of the originating peer + // because we have routed it + ssrc = m_peerId; + } + + if (ssrc == 0U) { + LogError(LOG_NET, "BUGBUG: PEER %u, trying to send data with a ssrc of 0?, pktSeq = %u, streamId = %u", peerId, pktSeq, streamId); + ssrc = m_peerId; // fallback to our own peer ID + } + } + if (directWrite) - return m_frameQueue->write(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); else { - m_frameQueue->enqueueMessage(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); + m_frameQueue->enqueueMessage(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); if (queueOnly) return true; return m_frameQueue->flushQueue(); @@ -2259,7 +2346,7 @@ bool FNENetwork::writePeerCommand(uint32_t peerId, FrameQueue::OpcodePair opcode } uint32_t len = length + 6U; - return writePeer(peerId, opcode, buffer, len, RTP_END_OF_CALL_SEQ, streamId, false, incPktSeq, true); + return writePeer(peerId, m_peerId, opcode, buffer, len, RTP_END_OF_CALL_SEQ, streamId, false, incPktSeq, true); } /* Helper to send a ACK response to the specified peer. */ @@ -2275,8 +2362,8 @@ bool FNENetwork::writePeerACK(uint32_t peerId, uint32_t streamId, const uint8_t* ::memcpy(buffer + 6U, data, length); } - return writePeer(peerId, { NET_FUNC::ACK, NET_SUBFUNC::NOP }, buffer, length + 10U, RTP_END_OF_CALL_SEQ, streamId, - false, false, true); + return writePeer(peerId, m_peerId, { NET_FUNC::ACK, NET_SUBFUNC::NOP }, buffer, length + 10U, RTP_END_OF_CALL_SEQ, + streamId, false, false, true); } /* Helper to log a warning specifying which NAK reason is being sent a peer. */ @@ -2332,7 +2419,7 @@ bool FNENetwork::writePeerNAK(uint32_t peerId, uint32_t streamId, const char* ta SET_UINT16((uint16_t)reason, buffer, 10U); // Reason logPeerNAKReason(peerId, tag, reason); - return writePeer(peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 10U, RTP_END_OF_CALL_SEQ, streamId, false); + return writePeer(peerId, m_peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 10U, RTP_END_OF_CALL_SEQ, streamId, false); } /* Helper to send a NAK response to the specified peer. */ @@ -2382,7 +2469,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin if (m_debug) { LogDebugEx(LOG_HOST, "FNENetwork::processTEKResponse()", "keyLength = %u", keyLength); - Utils::dump(1U, "Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + Utils::dump(1U, "FNENetwork::processTEKResponse(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); } // build response buffer @@ -2410,7 +2497,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin modifyKeyRsp.encode(buffer + 11U); - writePeer(peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + writePeer(peerId, m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, RTP_END_OF_CALL_SEQ, createStreamId(), false, false, true); peersToRemove.push_back(peerId); diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index d19baa64f..a0a230b2a 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -28,13 +28,14 @@ #include "common/concurrent/unordered_map.h" #include "common/network/BaseNetwork.h" #include "common/network/json/json.h" -#include "common/lookups/AffiliationLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" +#include "common/lookups/AdjSiteMapLookup.h" #include "common/network/Network.h" #include "common/network/PacketBuffer.h" #include "common/ThreadPool.h" +#include "fne/lookups/AffiliationLookup.h" #include "fne/network/influxdb/InfluxDB.h" #include "fne/CryptoContainer.h" @@ -54,6 +55,7 @@ namespace network { namespace callhandler { namespace packetdata { class HOST_SW namespace network { namespace callhandler { class HOST_SW_API TagP25Data; } } namespace network { namespace callhandler { namespace packetdata { class HOST_SW_API P25PacketData; } } } namespace network { namespace callhandler { class HOST_SW_API TagNXDNData; } } +namespace network { namespace callhandler { class HOST_SW_API TagAnalogData; } } namespace network { @@ -80,6 +82,7 @@ namespace network #define INFLUXDB_ERRSTR_DISABLED_TALKGROUP "disabled talkgroup" #define INFLUXDB_ERRSTR_INV_SLOT "invalid slot for talkgroup" #define INFLUXDB_ERRSTR_RID_NOT_PERMITTED "RID not permitted for talkgroup" + #define INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS "illegal/unknown RID attempted access" // --------------------------------------------------------------------------- // Class Prototypes @@ -421,6 +424,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param parrotDelay Delay for end of call to parrot TG playback. * @param parrotGrantDemand Flag indicating whether a parrot TG will generate a grant demand. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -430,7 +434,7 @@ namespace network * @param workerCnt Number of worker threads. */ FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, - bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool parrotGrantDemand, + bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, bool analog, uint32_t parrotDelay, bool parrotGrantDemand, bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt); /** * @brief Finalizes a instance of the FNENetwork class. @@ -465,6 +469,11 @@ namespace network * @returns callhandler::TagNXDNData* Instance of the TagNXDNData call handler. */ callhandler::TagNXDNData* nxdnTrafficHandler() const { return m_tagNXDN; } + /** + * @brief Gets the instance of the analog call handler. + * @returns callhandler::TagAnalogData* Instance of the TagAnalogData call handler. + */ + callhandler::TagAnalogData* analogTrafficHandler() const { return m_tagAnalog; } /** * @brief Sets the instances of the Radio ID, Talkgroup ID Peer List, and Crypto lookup tables. @@ -472,9 +481,10 @@ namespace network * @param tidLookup Talkgroup Rules Lookup Table Instance * @param peerListLookup Peer List Lookup Table Instance * @param cryptoLookup Crypto Container Lookup Table Instance + * @param adjSiteMapLookup Adjacent Site Map Lookup Table Instance */ void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::PeerListLookup* peerListLookup, - CryptoContainer* cryptoLookup); + CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup); /** * @brief Sets endpoint preshared encryption key. * @param presharedKey Encryption preshared key for networking. @@ -528,6 +538,8 @@ namespace network callhandler::TagP25Data* m_tagP25; friend class callhandler::TagNXDNData; callhandler::TagNXDNData* m_tagNXDN; + friend class callhandler::TagAnalogData; + callhandler::TagAnalogData* m_tagAnalog; friend class ::RESTAPI; HostFNE* m_host; @@ -540,6 +552,7 @@ namespace network bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_analogEnabled; uint32_t m_parrotDelay; Timer m_parrotDelayTimer; @@ -549,6 +562,7 @@ namespace network lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; + lookups::AdjSiteMapLookup* m_adjSiteMapLookup; CryptoContainer* m_cryptoLookup; @@ -558,7 +572,7 @@ namespace network concurrent::unordered_map m_peers; concurrent::unordered_map m_peerLinkPeers; typedef std::pair PeerAffiliationMapPair; - concurrent::unordered_map m_peerAffiliations; + concurrent::unordered_map m_peerAffiliations; concurrent::unordered_map> m_ccPeerMap; static std::timed_mutex m_keyQueueMutex; std::unordered_map m_peerLinkKeyQueue; @@ -594,10 +608,13 @@ namespace network bool m_allowConvSiteAffOverride; bool m_disallowCallTerm; bool m_restrictGrantToAffOnly; + bool m_restrictPVCallToRegOnly; bool m_enableInCallCtrl; bool m_rejectUnknownRID; - bool m_filterHeaders; + bool m_maskOutboundPeerID; + bool m_maskOutboundPeerIDForNonPL; + bool m_filterTerminators; bool m_forceListUpdate; @@ -620,6 +637,7 @@ namespace network bool m_dumpPacketData; bool m_verbosePacketData; + bool m_logDenials; bool m_reportPeerPing; bool m_verbose; @@ -663,6 +681,13 @@ namespace network */ void erasePeer(uint32_t peerId); + /** + * @brief Helper to find the unit registration for the given source ID. + * @param srcId Source Radio ID. + * @returns uint32_t Peer ID, or 0 if not found. + */ + uint32_t findPeerUnitReg(uint32_t srcId); + /** * @brief Helper to resolve the peer ID to its identity string. * @param peerId Peer ID. @@ -736,7 +761,8 @@ namespace network /** * @brief Helper to send a data message to the specified peer with a explicit packet sequence. - * @param peerId Peer ID. + * @param peerId Destination Peer ID. + * @param ssrc RTP synchronization source ID. * @param opcode FNE network opcode pair. * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. @@ -746,7 +772,7 @@ namespace network * @param incPktSeq Flag indicating the message should increment the packet sequence after transmission. * @param directWrite Flag indicating this message should be immediately directly written. */ - bool writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, + bool writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq = false, bool directWrite = false) const; /** diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index 6677afb3d..e3070e68f 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.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 "fne/Defines.h" @@ -23,6 +23,14 @@ using namespace compress; #include #include +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define WORKER_CNT 8U + +const uint64_t PACKET_LATE_TIME = 200U; // 200ms + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -30,16 +38,21 @@ using namespace compress; /* Initializes a new instance of the PeerNetwork class. */ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, analog, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), m_attachedKeyRSPHandler(false), m_blockTrafficToTable(), + m_dmrCallback(nullptr), + m_p25Callback(nullptr), + m_nxdnCallback(nullptr), + m_analogCallback(nullptr), m_pidLookup(nullptr), m_peerLink(false), m_peerLinkSavesACL(false), m_tgidPkt(true, "Peer-Link, TGID List"), m_ridPkt(true, "Peer-Link, RID List"), - m_pidPkt(true, "Peer-Link, PID List") + m_pidPkt(true, "Peer-Link, PID List"), + m_threadPool(WORKER_CNT, "peer") { assert(!address.empty()); assert(port > 0U); @@ -50,6 +63,21 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc // never disable peer network services on ACL NAK from master m_neverDisableOnACLNAK = true; + + // FNE peer network manually handle protocol packets + m_userHandleProtocol = true; + + // start thread pool + m_threadPool.start(); +} + +/* Finalizes a instance of the PeerNetwork class. */ + +PeerNetwork::~PeerNetwork() +{ + // stop thread pool + m_threadPool.stop(); + m_threadPool.wait(); } /* Sets the instances of the Peer List lookup tables. */ @@ -59,18 +87,21 @@ void PeerNetwork::setPeerLookups(lookups::PeerListLookup* pidLookup) m_pidLookup = pidLookup; } -/* Gets the received DMR stream ID. */ +/* Opens connection to the network. */ -uint32_t PeerNetwork::getRxDMRStreamId(uint32_t slotNo) const +bool PeerNetwork::open() { - assert(slotNo == 1U || slotNo == 2U); + if (!m_enabled) + return false; - if (slotNo == 1U) { - return m_rxDMRStreamId[0U]; - } - else { - return m_rxDMRStreamId[1U]; - } + return Network::open(); +} + +/* Closes connection to the network. */ + +void PeerNetwork::close() +{ + Network::close(); } /* Checks if the passed peer ID is blocked from sending to this peer. */ @@ -128,6 +159,7 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) } } + pkt.clear(); return true; } @@ -140,13 +172,44 @@ bool PeerNetwork::writePeerLinkPeers(json::array* peerList) /* User overrideable handler that allows user code to process network packets not handled by this class. */ -void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { switch (opcode.first) { - case NET_FUNC::PEER_LINK: + case NET_FUNC::PROTOCOL: // Protocol + { + PeerPacketRequest* req = new PeerPacketRequest(); + req->obj = this; + req->peerId = peerId; + req->streamId = streamId; + + req->rtpHeader = rtpHeader; + req->fneHeader = fneHeader; + + req->subFunc = opcode.second; + + req->pktRxTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + req->length = length; + req->buffer = new uint8_t[length]; + ::memcpy(req->buffer, data, length); + + // enqueue the task + if (!m_threadPool.enqueue(new_pooltask(taskNetworkRx, req))) { + LogError(LOG_NET, "Failed to task enqueue network packet request, peerId = %u", peerId); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } + } + } + break; + + case NET_FUNC::PEER_LINK: // Peer Link { switch (opcode.second) { - case NET_SUBFUNC::PL_TALKGROUP_LIST: + case NET_SUBFUNC::PL_TALKGROUP_LIST: // Talkgroup List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -203,7 +266,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_RID_LIST: + case NET_SUBFUNC::PL_RID_LIST: // Radio ID List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -260,7 +323,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_PEER_LIST: + case NET_SUBFUNC::PL_PEER_LIST: // Peer List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -324,7 +387,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco break; default: - Utils::dump("unknown opcode from the master", data, length); + Utils::dump("Unknown opcode from the master", data, length); break; } } @@ -386,8 +449,82 @@ bool PeerNetwork::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "PeerNetwork::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, pktSeq(), m_loginStreamId); } + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Process a data frames from the network. */ + +void PeerNetwork::taskNetworkRx(PeerPacketRequest* req) +{ + if (req != nullptr) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + PeerNetwork* network = static_cast(req->obj); + if (network == nullptr) { + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } + + return; + } + + if (req == nullptr) + return; + + if (req->length > 0) { + // determine if this packet is late (i.e. are we processing this packet more than 200ms after it was received?) + uint64_t dt = req->pktRxTime + PACKET_LATE_TIME; + if (dt < now) { + LogWarning(LOG_NET, "PEER %u packet processing latency >200ms, dt = %u, now = %u", req->peerId, dt, now); + } + + // process incomfing message subfunction opcodes + switch (req->subFunc) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + { + if (network->m_dmrCallback != nullptr) + network->m_dmrCallback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + { + if (network->m_p25Callback != nullptr) + network->m_p25Callback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + { + if (network->m_nxdnCallback != nullptr) + network->m_nxdnCallback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + { + if (network->m_analogCallback != nullptr) + network->m_analogCallback(network, req->buffer, req->length, req->streamId, req->fneHeader, req->rtpHeader); + } + break; + + default: + Utils::dump("Unknown protocol opcode from the master", req->buffer, req->length); + break; + } + } + + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } +} diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 4e3016c55..28c9d7008 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.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 * */ /** @@ -20,6 +20,7 @@ #include "common/lookups/PeerListLookup.h" #include "common/network/Network.h" #include "common/network/PacketBuffer.h" +#include "common/ThreadPool.h" #include #include @@ -27,6 +28,28 @@ namespace network { + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents the data required for a network packet handler thread. + * @ingroup fne_network + */ + struct PeerPacketRequest : thread_t { + uint32_t peerId; //! Peer ID for this request. + uint32_t streamId; //! Stream ID for this request. + + frame::RTPHeader rtpHeader; //! RTP Header + frame::RTPFNEHeader fneHeader; //! RTP FNE Header + int length = 0U; //! Length of raw data buffer + uint8_t* buffer = nullptr; //! Raw data buffer + + network::NET_SUBFUNC::ENUM subFunc; //! Sub-function of the packet + + uint64_t pktRxTime; //! Packet receive time + }; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -49,6 +72,7 @@ namespace network * @param dmr Flag indicating whether DMR is enabled. * @param p25 Flag indicating whether P25 is enabled. * @param nxdn Flag indicating whether NXDN is enabled. + * @param analog Flag indicating whether analog is enabled. * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. @@ -56,7 +80,11 @@ namespace network * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + /** + * @brief Finalizes a instance of the PeerNetwork class. + */ + ~PeerNetwork() override; /** * @brief Sets the instances of the Peer List lookup tables. @@ -65,21 +93,36 @@ namespace network void setPeerLookups(lookups::PeerListLookup* pidLookup); /** - * @brief Gets the received DMR stream ID. - * @param slotNo DMR slot to get stream ID for. - * @return uint32_t Stream ID for the given DMR slot. + * @brief Opens connection to the network. + * @returns bool True, if networking has started, otherwise false. + */ + bool open() override; + + /** + * @brief Closes connection to the network. + */ + void close() override; + + /** + * @brief Helper to set the DMR protocol callback. + * @param callback */ - uint32_t getRxDMRStreamId(uint32_t slotNo) const; + void setDMRCallback(std::function&& callback) { m_dmrCallback = callback; } /** - * @brief Gets the received P25 stream ID. - * @return uint32_t Stream ID. + * @brief Helper to set the P25 protocol callback. + * @param callback */ - uint32_t getRxP25StreamId() const { return m_rxP25StreamId; } + void setP25Callback(std::function&& callback) { m_p25Callback = callback; } /** - * @brief Gets the received NXDN stream ID. - * @return uint32_t Stream ID. + * @brief Helper to set the NXDN protocol callback. + * @param callback */ - uint32_t getRxNXDNStreamId() const { return m_rxNXDNStreamId; } + void setNXDNCallback(std::function&& callback) { m_nxdnCallback = callback; } + /** + * @brief Helper to set the analog protocol callback. + * @param callback + */ + void setAnalogCallback(std::function&& callback) { m_analogCallback = callback; } /** * @brief Gets the blocked traffic peer ID table. @@ -125,6 +168,27 @@ namespace network protected: std::vector m_blockTrafficToTable; + /** + * @brief DMR Protocol Callback. + * (This is called when the master sends a DMR packet.) + */ + std::function m_dmrCallback; + /** + * @brief P25 Protocol Callback. + * (This is called when the master sends a P25 packet.) + */ + std::function m_p25Callback; + /** + * @brief NXDN Protocol Callback. + * (This is called when the master sends a NXDN packet.) + */ + std::function m_nxdnCallback; + /** + * @brief Analog Protocol Callback. + * (This is called when the master sends a analog packet.) + */ + std::function m_analogCallback; + /** * @brief User overrideable handler that allows user code to process network packets not handled by this class. * @param peerId Peer ID. @@ -132,9 +196,11 @@ namespace network * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. * @param streamId Stream ID. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. */ void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, - uint32_t streamId = 0U) override; + uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader()) override; /** * @brief Writes configuration to the network. @@ -150,6 +216,14 @@ namespace network PacketBuffer m_tgidPkt; PacketBuffer m_ridPkt; PacketBuffer m_pidPkt; + + ThreadPool m_threadPool; + + /** + * @brief Entry point to process a given network packet. + * @param req Instance of the PeerPacketRequest structure. + */ + static void taskNetworkRx(PeerPacketRequest* req); }; } // namespace network diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 5a0949f88..f57c36aec 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -505,6 +505,7 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), + m_adjSiteMapLookup(nullptr), m_authTokens() { assert(!address.empty()); @@ -526,7 +527,7 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p delete[] in; if (m_debug) { - Utils::dump("REST Password Hash", m_passwordHash, 32U); + Utils::dump("RESTAPI::RESTAPI(), REST Password Hash", m_passwordHash, 32U); } #if defined(ENABLE_SSL) @@ -554,11 +555,13 @@ RESTAPI::~RESTAPI() /* Sets the instances of the Radio ID and Talkgroup ID lookup tables. */ -void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, ::lookups::PeerListLookup* peerListLookup) +void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, + ::lookups::PeerListLookup* peerListLookup, ::lookups::AdjSiteMapLookup* adjMapLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; m_peerListLookup = peerListLookup; + m_adjSiteMapLookup = adjMapLookup; } /* Sets the instance of the FNE network. */ @@ -651,6 +654,11 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(FNE_PUT_PEER_DELETE).put(REST_API_BIND(RESTAPI::restAPI_PutPeerDelete, this)); m_dispatcher.match(FNE_GET_PEER_COMMIT).get(REST_API_BIND(RESTAPI::restAPI_GetPeerCommit, this)); + m_dispatcher.match(FNE_GET_ADJ_MAP_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetAdjMapList, this)); + m_dispatcher.match(FNE_PUT_ADJ_MAP_ADD).put(REST_API_BIND(RESTAPI::restAPI_PutAdjMapAdd, this)); + m_dispatcher.match(FNE_PUT_ADJ_MAP_DELETE).put(REST_API_BIND(RESTAPI::restAPI_PutAdjMapDelete, this)); + m_dispatcher.match(FNE_GET_ADJ_MAP_COMMIT).get(REST_API_BIND(RESTAPI::restAPI_GetAdjMapCommit, this)); + m_dispatcher.match(FNE_GET_FORCE_UPDATE).get(REST_API_BIND(RESTAPI::restAPI_GetForceUpdate, this)); m_dispatcher.match(FNE_GET_RELOAD_TGS).get(REST_API_BIND(RESTAPI::restAPI_GetReloadTGs, this)); @@ -771,7 +779,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, co } if (m_debug) { - Utils::dump("Password Hash", passwordHash, 32U); + Utils::dump("RESTAPI::restAPI_PutAuth(), Password Hash", passwordHash, 32U); } // compare hashes @@ -1287,7 +1295,10 @@ void RESTAPI::restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply, peerPassword = req["peerPassword"].get(); } - m_peerListLookup->addEntry(peerId, peerAlias, peerPassword, peerLink); + PeerId entry = PeerId(peerId, peerAlias, peerPassword, false); + entry.peerLink(peerLink); + + m_peerListLookup->addEntry(peerId, entry); } /* REST API endpoint; implements put peer delete request. */ @@ -1331,6 +1342,136 @@ void RESTAPI::restAPI_GetPeerCommit(const HTTPPayload& request, HTTPPayload& rep reply.payload(response); } + +/* REST API endpoint; implements get adjacent site map query request. */ + +void RESTAPI::restAPI_GetAdjMapList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + json::array peers = json::array(); + if (m_adjSiteMapLookup != nullptr) { + if (m_adjSiteMapLookup->adjPeerMap().size() > 0) { + for (auto entry : m_adjSiteMapLookup->adjPeerMap()) { + json::object peerObj = json::object(); + + uint32_t peerId = entry.peerId(); + peerObj["peerId"].set(peerId); + + json::array neighbors = json::array(); + std::vector neighbor = entry.neighbors(); + if (neighbor.size() > 0) { + for (auto neighEntry : neighbor) { + uint32_t peerId = neighEntry; + neighbors.push_back(json::value((double)peerId)); + } + } + peerObj["neighbors"].set(neighbors); + peers.push_back(json::value(peerObj)); + } + } + } + + response["peers"].set(peers); + reply.payload(response); +} + +/* REST API endpoint; implements put adjacent site map add request. */ + +void RESTAPI::restAPI_PutAdjMapAdd(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + // Validate peer ID (required) + if (!req["peerId"].is()) { + errorPayload(reply, "peerId was not a valid integer"); + return; + } + + // get + AdjPeerMapEntry entry = AdjPeerMapEntry(); + uint32_t peerId = req["peerId"].get(); + entry.peerId(peerId); + + if (!req["neighbors"].is()) { + errorPayload(reply, "Peer \"neighbors\" was not a valid JSON array"); + LogDebug(LOG_REST, "Peer \"neighbors\" was not a valid JSON array"); + return; + } + json::array neighbors = req["neighbors"].get(); + + std::vector neighbor = std::vector(); + if (neighbors.size() > 0) { + for (auto neighEntry : neighbors) { + if (!neighEntry.is()) { + errorPayload(reply, "Peer neighbor value was not a valid number"); + LogDebug(LOG_REST, "Peer neighbor value was not a valid number (was %s)", neighEntry.to_type().c_str()); + return; + } + + neighbor.push_back(neighEntry.get()); + } + entry.neighbors(neighbor); + } + + m_adjSiteMapLookup->addEntry(entry); +} + +/* REST API endpoint; implements put adjacent site map delete request. */ + +void RESTAPI::restAPI_PutAdjMapDelete(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + if (!req["peerId"].is()) { + errorPayload(reply, "peerId was not a valid integer"); + return; + } + + uint32_t peerId = req["peerId"].get(); + + m_adjSiteMapLookup->eraseEntry(peerId); +} + +/* REST API endpoint; implements put adjacent site map commit request. */ + +void RESTAPI::restAPI_GetAdjMapCommit(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + m_adjSiteMapLookup->commit(); + + reply.payload(response); +} + /* */ void RESTAPI::restAPI_GetForceUpdate(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) diff --git a/src/fne/network/RESTAPI.h b/src/fne/network/RESTAPI.h index d18fcf9e9..03a923de2 100644 --- a/src/fne/network/RESTAPI.h +++ b/src/fne/network/RESTAPI.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 * */ /** @@ -20,6 +20,7 @@ #include "common/network/rest/RequestDispatcher.h" #include "common/network/rest/http/HTTPServer.h" #include "common/network/rest/http/SecureHTTPServer.h" +#include "common/lookups/AdjSiteMapLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/Thread.h" @@ -69,8 +70,10 @@ class HOST_SW_API RESTAPI : private Thread { * @param ridLookup Radio ID Lookup Table Instance * @param tidLookup Talkgroup Rules Lookup Table Instance * @param peerListLookup Peer List Lookup Table Instance + * @param adjPeerMapLookup Adjacent Site Map Lookup Table Instance */ - void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::PeerListLookup* peerListLookup); + void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, + ::lookups::PeerListLookup* peerListLookup, ::lookups::AdjSiteMapLookup* adjPeerMapLookup); /** * @brief Sets the instance of the FNE network. * @param network Instance oft he FNENetwork class. @@ -110,6 +113,7 @@ class HOST_SW_API RESTAPI : private Thread { ::lookups::RadioIdLookup* m_ridLookup; ::lookups::TalkgroupRulesLookup* m_tidLookup; ::lookups::PeerListLookup* m_peerListLookup; + ::lookups::AdjSiteMapLookup* m_adjSiteMapLookup; typedef std::unordered_map::value_type AuthTokenValueType; std::unordered_map m_authTokens; @@ -270,6 +274,35 @@ class HOST_SW_API RESTAPI : private Thread { */ void restAPI_GetPeerCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements get adjacent site map list query request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetAdjMapList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements put adjacent site map add request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_PutAdjMapAdd(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements put adjacent site map delete request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_PutAdjMapDelete(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** + * @brief REST API endpoint; implements put adjacent site map commit request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetAdjMapCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /** * @brief * @param request HTTP request. diff --git a/src/fne/network/RESTDefines.h b/src/fne/network/RESTDefines.h index 95208b5c3..66fe955ba 100644 --- a/src/fne/network/RESTDefines.h +++ b/src/fne/network/RESTDefines.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 * */ /** @@ -44,6 +44,11 @@ #define FNE_PUT_PEER_DELETE "/peer/delete" #define FNE_GET_PEER_COMMIT "/peer/commit" +#define FNE_GET_ADJ_MAP_LIST "/adjmap/list" +#define FNE_PUT_ADJ_MAP_ADD "/adjmap/add" +#define FNE_PUT_ADJ_MAP_DELETE "/adjmap/delete" +#define FNE_GET_ADJ_MAP_COMMIT "/adjmap/commit" + #define FNE_GET_FORCE_UPDATE "/force-update" #define FNE_GET_RELOAD_TGS "/reload-tgs" diff --git a/src/fne/network/callhandler/TagAnalogData.cpp b/src/fne/network/callhandler/TagAnalogData.cpp new file mode 100644 index 000000000..c809ab17e --- /dev/null +++ b/src/fne/network/callhandler/TagAnalogData.cpp @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * 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 "fne/Defines.h" +#include "common/analog/AnalogDefines.h" +#include "common/analog/data/NetData.h" +#include "common/Clock.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "network/FNENetwork.h" +#include "network/callhandler/TagAnalogData.h" +#include "HostFNE.h" + +using namespace system_clock; +using namespace network; +using namespace network::callhandler; +using namespace network::callhandler::packetdata; +using namespace analog; +using namespace analog::defines; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t CALL_COLL_TIMEOUT = 10U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the TagAnalogData class. */ + +TagAnalogData::TagAnalogData(FNENetwork* network, bool debug) : + m_network(network), + m_parrotFrames(), + m_parrotFramesReady(false), + m_status(), + m_debug(debug) +{ + assert(network != nullptr); +} + +/* Finalizes a instance of the TagAnalogData class. */ + +TagAnalogData::~TagAnalogData() = default; + +/* Process a data frame from the network. */ + +bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) +{ + hrc::hrc_t pktTime = hrc::now(); + + DECLARE_UINT8_ARRAY(buffer, len); + ::memcpy(buffer, data, len); + + uint8_t seqNo = data[4U]; + + uint32_t srcId = GET_UINT24(data, 5U); + uint32_t dstId = GET_UINT24(data, 8U); + + bool individual = (data[15] & 0x40U) == 0x40U; + + AudioFrameType::E frameType = (AudioFrameType::E)(data[15U] & 0x0FU); + + data::NetData analogData; + analogData.setSeqNo(seqNo); + analogData.setSrcId(srcId); + analogData.setDstId(dstId); + analogData.setFrameType(frameType); + + analogData.setAudio(data + 20U); + + uint8_t frame[AUDIO_SAMPLES_LENGTH_BYTES]; + analogData.getAudio(frame); + + // perform TGID route rewrites if configured + routeRewrite(buffer, peerId, dstId, false); + dstId = GET_UINT24(buffer, 8U); + + // is the stream valid? + if (validate(peerId, analogData, streamId)) { + // is this peer ignored? + if (!isPeerPermitted(peerId, analogData, streamId, external)) { + return false; + } + + // is this the end of the call stream? + if (frameType == AudioFrameType::TERMINATOR) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + return false; + } + + RxStatus status = m_status[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + 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); + if (tg.config().parrot()) { + if (m_parrotFrames.size() > 0) { + m_parrotFramesReady = true; + LogMessage(LOG_NET, "Analog, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); + m_network->m_parrotDelayTimer.start(); + } + } + + LogMessage(LOG_NET, "Analog, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + + // report call event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_event") + .tag("peerId", std::to_string(peerId)) + .tag("mode", "Analog") + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(srcId)) + .tag("dstId", std::to_string(dstId)) + .field("duration", duration) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + m_network->eraseStreamPktSeq(peerId, streamId); + m_network->m_callInProgress = false; + } + } + + // is this a new call stream? + if (frameType == AudioFrameType::VOICE_START) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + return false; + } + + 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 = it->second; + if (streamId != status.streamId) { + if (status.srcId != 0U && status.srcId != srcId) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { + LogWarning(LOG_NET, "Analog, Call Collision, lasted more then %us with no further updates, forcibly ending call"); + m_status[dstId].reset(); + m_network->m_callInProgress = false; + } + + LogWarning(LOG_NET, "Analog, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + return false; + } + } + } + else { + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + m_parrotFramesReady = false; + if (m_parrotFrames.size() > 0) { + for (auto& pkt : m_parrotFrames) { + if (pkt.buffer != nullptr) { + delete[] pkt.buffer; + } + } + m_parrotFrames.clear(); + } + } + + // this is a new call stream + // 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].streamId = streamId; + m_status[dstId].peerId = peerId; + m_status[dstId].activeCall = true; + + LogMessage(LOG_NET, "Analog, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + + m_network->m_callInProgress = true; + } + } + + // is this a parrot talkgroup? + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + uint8_t* copy = new uint8_t[len]; + ::memcpy(copy, buffer, len); + + ParrotFrame parrotFrame = ParrotFrame(); + parrotFrame.buffer = copy; + parrotFrame.bufferLen = len; + + parrotFrame.pktSeq = pktSeq; + parrotFrame.streamId = streamId; + parrotFrame.peerId = peerId; + + parrotFrame.srcId = srcId; + parrotFrame.dstId = dstId; + + m_parrotFrames.push_back(parrotFrame); + + if (m_network->m_parrotOnlyOriginating) { + return true; // end here because parrot calls should never repeat anywhere + } + } + + m_status[dstId].lastPacket = hrc::now(); + + // repeat traffic to the connected peers + if (m_network->m_peers.size() > 0U) { + uint32_t i = 0U; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + + // is this peer ignored? + if (!isPeerPermitted(peer.first, analogData, streamId)) { + continue; + } + + // every 5 peers flush the queue + if (i % 5U == 0U) { + m_network->m_frameQueue->flushQueue(); + } + + DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID route rewrites if configured + routeRewrite(outboundPeerBuffer, peer.first, dstId); + + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, true); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, peer.first, seqNo, srcId, dstId, len, pktSeq, streamId, external); + } + + if (!m_network->m_callInProgress) + m_network->m_callInProgress = true; + i++; + } + } + m_network->m_frameQueue->flushQueue(); + } + + // repeat traffic to external peers + if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { + for (auto peer : m_network->m_host->m_peerNetworks) { + uint32_t dstPeerId = peer.second->getPeerId(); + + // don't try to repeat traffic to the source peer...if this traffic + // is coming from a external peer + if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + + // is this peer ignored? + if (!isPeerPermitted(dstPeerId, analogData, streamId, true)) { + continue; + } + + // check if the source peer is blocked from sending to this peer + if (peer.second->checkBlockedPeer(peerId)) { + continue; + } + + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { + continue; + } + + DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); + ::memcpy(outboundPeerBuffer, buffer, len); + + // perform TGID route rewrites if configured + routeRewrite(outboundPeerBuffer, dstPeerId, dstId); + + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, dstPeerId, seqNo, srcId, dstId, len, pktSeq, streamId, external); + } + + if (!m_network->m_callInProgress) + m_network->m_callInProgress = true; + } + } + } + + return true; + } + + return false; +} + +/* Helper to playback a parrot frame to the network. */ + +void TagAnalogData::playbackParrot() +{ + if (m_parrotFrames.size() == 0) { + m_parrotFramesReady = false; + return; + } + + auto& pkt = m_parrotFrames[0]; + if (pkt.buffer != nullptr) { + if (m_network->m_parrotOnlyOriginating) { + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); + } + } + else { + // repeat traffic to the connected peers + for (auto peer : m_network->m_peers) { + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + if (m_network->m_debug) { + LogDebug(LOG_NET, "Analog, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); + } + } + } + + delete[] pkt.buffer; + } + Thread::sleep(60); + m_parrotFrames.pop_front(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Helper to route rewrite the network data buffer. */ + +void TagAnalogData::routeRewrite(uint8_t* buffer, uint32_t peerId, uint32_t dstId, bool outbound) +{ + uint32_t rewriteDstId = dstId; + + // does the data require route writing? + if (peerRewrite(peerId, rewriteDstId, outbound)) { + // rewrite destination TGID in the frame + SET_UINT24(rewriteDstId, buffer, 8U); + } +} + +/* Helper to route rewrite destination ID and slot. */ + +bool TagAnalogData::peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound) +{ + lookups::TalkgroupRuleGroupVoice tg; + if (outbound) { + tg = m_network->m_tidLookup->find(dstId); + } + else { + tg = m_network->m_tidLookup->findByRewrite(peerId, dstId); + } + + bool rewrote = false; + if (tg.config().rewriteSize() > 0) { + std::vector rewrites = tg.config().rewrite(); + for (auto entry : rewrites) { + if (entry.peerId() == peerId) { + if (outbound) { + dstId = entry.tgId(); + } + else { + dstId = tg.source().tgId(); + } + rewrote = true; + break; + } + } + } + + return rewrote; +} + +/* Helper to determine if the peer is permitted for traffic. */ + +bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool external) +{ + if (!data.getGroup()) { + if (m_network->m_disallowU2U) + return false; + if (!m_network->checkU2UDroppedPeer(peerId)) + return true; + return false; + } + + FNEPeerConnection* connection = nullptr; // bryanb: this is a possible null ref concurrency issue + // it is possible if the timing is just right to get a valid + // connection back initially, and then for it to be deleted + if (peerId > 0 && (m_network->m_peers.find(peerId) != m_network->m_peers.end())) { + connection = m_network->m_peers[peerId]; + } + + // is this peer a Peer-Link peer? + if (connection != nullptr) { + if (connection->isPeerLink()) { + return true; // Peer Link peers are *always* allowed to receive traffic and no other rules may filter + // these peers + } + } + + // is this a group call? + if (data.getGroup()) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + + std::vector inclusion = tg.config().inclusion(); + std::vector exclusion = tg.config().exclusion(); + + // peer inclusion lists take priority over exclusion lists + if (inclusion.size() > 0) { + auto it = std::find(inclusion.begin(), inclusion.end(), peerId); + if (it == inclusion.end()) { + return false; + } + } + else { + if (exclusion.size() > 0) { + auto it = std::find(exclusion.begin(), exclusion.end(), peerId); + if (it != exclusion.end()) { + return false; + } + } + } + + // peer always send list takes priority over any following affiliation rules + std::vector alwaysSend = tg.config().alwaysSend(); + if (alwaysSend.size() > 0) { + auto it = std::find(alwaysSend.begin(), alwaysSend.end(), peerId); + if (it != alwaysSend.end()) { + return true; // skip any following checks and always send traffic + } + } + + // is this peer a conventional peer? + if (m_network->m_allowConvSiteAffOverride) { + if (connection != nullptr) { + if (connection->isConventionalPeer()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for conventional peers + } + } + } + + // is this peer a SysView peer? + if (connection != nullptr) { + if (connection->isSysView()) { + external = true; // we'll just set the external flag to disable the affiliation check + // for SysView peers + } + } + + // is this a TG that requires affiliations to repeat? + // NOTE: external peers *always* repeat traffic regardless of affiliation + if (tg.config().affiliated() && !external) { + uint32_t lookupPeerId = peerId; + if (connection != nullptr) { + if (connection->ccPeerId() > 0U) + lookupPeerId = connection->ccPeerId(); + } + + // check the affiliations for this peer to see if we can repeat traffic + lookups::AffiliationLookup* aff = m_network->m_peerAffiliations[lookupPeerId]; + if (aff == nullptr) { + std::string peerIdentity = m_network->resolvePeerIdentity(lookupPeerId); + //LogError(LOG_NET, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", lookupPeerId, peerIdentity.c_str()); + return false; // this will cause no traffic to pass for this peer now...I'm not sure this is good behavior + } + else { + if (!aff->hasGroupAff(data.getDstId())) { + return false; + } + } + } + } + + return true; +} + +/* Helper to validate the analog call stream. */ + +bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t streamId) +{ + // is the source ID a blacklisted ID? + bool rejectUnknownBadCall = false; + lookups::RadioId rid = m_network->m_ridLookup->find(data.getSrcId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + } + else { + // if this is a default radio -- and we are rejecting undefined radios + // report call error + if (m_network->m_rejectUnknownRID) { + rejectUnknownBadCall = true; + } + } + + // always validate a terminator if the source is valid + if (data.getFrameType() == AudioFrameType::TERMINATOR) + return true; + + // is this a private call? + if (!data.getGroup()) { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(data.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_DST_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + return false; + } + } + else { + // if this is a default radio -- and we are rejecting undefined radios + // report call error + if (m_network->m_rejectUnknownRID) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + LogWarning(LOG_NET, "Analog, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + } + } + + // is this a group call? + if (data.getGroup()) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + if (tg.isInvalid()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_INV_TALKGROUP)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + + // peer always send list takes priority over any following affiliation rules + bool isAlwaysPeer = false; + std::vector alwaysSend = tg.config().alwaysSend(); + if (alwaysSend.size() > 0) { + auto it = std::find(alwaysSend.begin(), alwaysSend.end(), peerId); + if (it != alwaysSend.end()) { + isAlwaysPeer = true; // skip any following checks and always send traffic + rejectUnknownBadCall = false; + } + } + + // fail call if the reject flag is set + if (rejectUnknownBadCall) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + LogWarning(LOG_NET, "Analog, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + + // is the TGID active? + if (!tg.config().active()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_TALKGROUP)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + + // always peers can violate the rules...hurray + if (!isAlwaysPeer) { + // does the TGID have a permitted RID list? + if (tg.config().permittedRIDs().size() > 0) { + // does the transmitting RID have permission? + std::vector permittedRIDs = tg.config().permittedRIDs(); + if (std::find(permittedRIDs.begin(), permittedRIDs.end(), data.getSrcId()) == permittedRIDs.end()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", std::string(INFLUXDB_ERRSTR_RID_NOT_PERMITTED)) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); + return false; + } + } + } + } + + return true; +} diff --git a/src/fne/network/callhandler/TagAnalogData.h b/src/fne/network/callhandler/TagAnalogData.h new file mode 100644 index 000000000..c7ae6624c --- /dev/null +++ b/src/fne/network/callhandler/TagAnalogData.h @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * 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 TagAnalogData.h + * @ingroup fne_callhandler + * @file TagAnalogData.cpp + * @ingroup fne_callhandler + */ +#if !defined(__CALLHANDLER__TAG_ANALOG_DATA_H__) +#define __CALLHANDLER__TAG_ANALOG_DATA_H__ + +#include "fne/Defines.h" +#include "common/concurrent/deque.h" +#include "common/concurrent/unordered_map.h" +#include "common/dmr/DMRDefines.h" +#include "common/dmr/data/NetData.h" +#include "common/dmr/lc/CSBK.h" +#include "common/Clock.h" +#include "network/FNENetwork.h" +#include "network/callhandler/packetdata/DMRPacketData.h" + +namespace network +{ + namespace callhandler + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements the analog call handler and data FNE networking logic. + * @ingroup fne_callhandler + */ + class HOST_SW_API TagAnalogData { + public: + /** + * @brief Initializes a new instance of the TagAnalogData class. + * @param network Instance of the FNENetwork class. + * @param debug Flag indicating whether network debug is enabled. + */ + TagAnalogData(FNENetwork* network, bool debug); + /** + * @brief Finalizes a instance of the TagAnalogData class. + */ + ~TagAnalogData(); + + /** + * @brief Process a data frame from the network. + * @param data Network data buffer. + * @param len Length of data. + * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. + * @param pktSeq RTP packet sequence. + * @param streamId Stream ID. + * @param external Flag indicating traffic is from an external peer. + * @returns bool True, if frame is processed, otherwise false. + */ + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); + + /** + * @brief Helper to playback a parrot frame to the network. + */ + void playbackParrot(); + /** + * @brief Helper to determine if there are stored parrot frames. + * @returns True, if there are queued parrot frames to playback, otherwise false. + */ + bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + + private: + FNENetwork* m_network; + + /** + * @brief Represents a stored parrot frame. + */ + class ParrotFrame { + public: + 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; + }; + concurrent::deque m_parrotFrames; + bool m_parrotFramesReady; + + /** + * @brief Represents the receive status of a call. + */ + class RxStatus { + 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; + concurrent::unordered_map m_status; + + bool m_debug; + + /** + * @brief Helper to route rewrite the network data buffer. + * @param buffer Frame buffer. + * @param peerId Peer ID. + * @param dstId Destination ID. + * @param outbound Flag indicating whether or not this is outbound traffic. + */ + void routeRewrite(uint8_t* buffer, uint32_t peerId, uint32_t dstId, bool outbound = true); + /** + * @brief Helper to route rewrite destination ID and slot. + * @param peerId Peer ID. + * @param dstId Destination ID. + * @param outbound Flag indicating whether or not this is outbound traffic. + * @returns bool True, if rewritten successfully, otherwise false. + */ + bool peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound = true); + + /** + * @brief Helper to determine if the peer is permitted for traffic. + * @param peerId Peer ID. + * @param data Instance of data::NetData Analog data container class. + * @param streamId Stream ID. + * @param external Flag indicating this traffic came from an external peer. + * @returns bool True, if valid, otherwise false. + */ + bool isPeerPermitted(uint32_t peerId, analog::data::NetData& data, uint32_t streamId, bool external = false); + /** + * @brief Helper to validate the DMR call stream. + * @param peerId Peer ID. + * @param data Instance of data::NetData Analog data container class. + * @param streamId Stream ID. + * @returns bool True, if valid, otherwise false. + */ + bool validate(uint32_t peerId, analog::data::NetData& data, uint32_t streamId); + }; + } // namespace callhandler +} // namespace network + +#endif // __CALLHANDLER__TAG_ANALOG_DATA_H__ diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index e0064539a..87e0c7930 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -47,6 +47,7 @@ TagDMRData::TagDMRData(FNENetwork* network, bool debug) : m_parrotFrames(), m_parrotFramesReady(false), m_status(), + m_statusPVCall(), m_debug(debug) { assert(network != nullptr); @@ -63,7 +64,7 @@ TagDMRData::~TagDMRData() /* Process a data frame from the network. */ -bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) { hrc::hrc_t pktTime = hrc::now(); @@ -120,12 +121,18 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint8_t frame[DMR_FRAME_LENGTH_BYTES]; dmrData.getData(frame); + // process a CSBK out into a class literal if possible + std::unique_ptr csbk; + if (dataSync && (dataType == DataType::CSBK)) { + csbk = lc::csbk::CSBKFactory::createCSBK(frame, dataType); + } + // perform TGID route rewrites if configured routeRewrite(buffer, peerId, dmrData, dataType, dstId, slotNo, false); dstId = GET_UINT24(buffer, 8U); // is the stream valid? - if (validate(peerId, dmrData, streamId)) { + if (validate(peerId, dmrData, csbk.get(), streamId)) { // is this peer ignored? if (!isPeerPermitted(peerId, dmrData, streamId, external)) { return false; @@ -134,7 +141,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this the end of the call stream? if (dataSync && (dataType == DataType::TERMINATOR_WITH_LC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, srcId, dstId, slotNo, streamId, external); + LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, slotNo, streamId, external); return false; } @@ -147,8 +154,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId 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, slot = %u, streamId = %u, external = %u", - peerId, srcId, dstId, slotNo, streamId, external); + LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, external); } else { status = it->second; @@ -172,13 +179,27 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (tg.config().parrot()) { if (m_parrotFrames.size() > 0) { m_parrotFramesReady = true; - LogMessage(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + LogMessage(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); m_network->m_parrotDelayTimer.start(); } } - LogMessage(LOG_NET, "DMR, Call End, peer = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, slotNo, duration / 1000, streamId, external); + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + m_statusPVCall[dstId].reset(); + LogMessage(LOG_NET, "DMR, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); + } + else + LogMessage(LOG_NET, "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -203,10 +224,12 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if (dataSync && (dataType == DataType::VOICE_LC_HEADER)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "DMR, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + 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) @@ -217,6 +240,15 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (it != m_status.end()) { RxStatus status = it->second; if (streamId != status.streamId) { + // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch + // from one source to another (primarily from bridge resources) + if (switchOver && status.slotNo == slotNo) { + status.streamId = streamId; + status.srcId = srcId; + LogMessage(LOG_NET, "DMR, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); + } + if (status.srcId != 0U && status.srcId != srcId) { uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { @@ -225,8 +257,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_callInProgress = false; } - LogWarning(LOG_NET, "DMR, Call Collision, peer = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", - peerId, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); + LogWarning(LOG_NET, "DMR, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); return false; } } @@ -256,7 +288,25 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId 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); + // is this a private call? + if (flco == FLCO::PRIVATE) { + m_statusPVCall[dstId].callStartTime = pktTime; + m_statusPVCall[dstId].srcId = srcId; + m_statusPVCall[dstId].dstId = dstId; + m_statusPVCall[dstId].slotNo = slotNo; + m_statusPVCall[dstId].streamId = streamId; + m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].activeCall = true; + + // find the SSRC of the peer that registered this unit + uint32_t regSSRC = m_network->findPeerUnitReg(srcId); + m_statusPVCall[dstId].dstPeerId = regSSRC; + + LogMessage(LOG_NET, "DMR, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, external); + } + else + LogMessage(LOG_NET, "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } @@ -295,11 +345,81 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].lastPacket = hrc::now(); + bool noConnectedPeerRepeat = false; + bool privateCallInProgress = false; + + // is this a private call in-progress? + if (m_network->m_restrictPVCallToRegOnly) { + if (flco == FLCO::PRIVATE) { + privateCallInProgress = true; + } + + if (privateCallInProgress) { + // if we've not determined the destination peer, we have to repeat it everywhere + if (m_statusPVCall[dstId].dstPeerId == 0U) { + noConnectedPeerRepeat = false; + privateCallInProgress = false; // trick the system to repeat everywhere + } else { + // if this is a private call, check if the destination peer is one directly connected to us, if not + // flag the call so it only repeats to external peers + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { + noConnectedPeerRepeat = true; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + if (conn->isExternalPeer()) { + continue; + } + } + + if (m_statusPVCall[dstId].dstPeerId == peer.first) { + noConnectedPeerRepeat = false; + break; + } + } + } + } + } + } + } + // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + + if (m_network->m_restrictPVCallToRegOnly) { + // is this peer an external peer? + bool external = false; + if (conn != nullptr) { + external = conn->isExternalPeer(); + } + + // is this a private call? + if ((flco == FLCO::PRIVATE) && !external) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + if (peer.first != m_statusPVCall[dstId].dstPeerId) { + continue; + } + } + } + } + // is this peer ignored? if (!isPeerPermitted(peer.first, dmrData, streamId)) { continue; @@ -316,10 +436,10 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, dmrData, dataType, dstId, slotNo); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, true); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", - peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -330,6 +450,12 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_frameQueue->flushQueue(); } + // if this is a private call, and we have already repeated to the connected peer that registered + // the unit, don't repeat to any external peers + if (privateCallInProgress && !noConnectedPeerRepeat) { + return true; + } + // repeat traffic to external peers if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { @@ -338,6 +464,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // don't try to repeat traffic to the source peer...if this traffic // is coming from a external peer if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(dstPeerId, dmrData, streamId, true)) { continue; @@ -359,10 +490,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, dmrData, dataType, dstId, slotNo); - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", - peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", + ssrc, peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -436,7 +571,7 @@ void TagDMRData::playbackParrot() auto& pkt = m_parrotFrames[0]; if (pkt.buffer != nullptr) { if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -445,7 +580,7 @@ void TagDMRData::playbackParrot() else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -752,7 +887,7 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t /* Helper to validate the DMR call stream. */ -bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamId) +bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, uint32_t streamId) { // is the source ID a blacklisted ID? bool rejectUnknownBadCall = false; @@ -773,6 +908,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -790,6 +928,67 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI if (data.getDataType() == DataType::TERMINATOR_WITH_LC) return true; + // always validate a CSBK if the source is valid + if (data.getDataType() == DataType::CSBK) { + if (rejectUnknownBadCall) + return false; + + if (csbk != nullptr) { + // handle standard DMR reference opcodes + switch (csbk->getCSBKO()) { + case CSBKO::PV_GRANT: + { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(data.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + } + break; + case CSBKO::TV_GRANT: + { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(csbk->getDstId()); + + // check TGID validity + if (tg.isInvalid()) { + return false; + } + + if (!tg.config().active()) { + return false; + } + } + break; + case CSBKO::EXT_FNCT: + { + if (csbk->getFID() == FID_MOT) { + const lc::csbk::CSBK_EXT_FNCT* iosp = static_cast(csbk); + if (iosp != nullptr) { + lookups::PeerId pid = m_network->m_peerListLookup->find(peerId); + uint32_t func = iosp->getExtendedFunction(); + switch (func) { + case ExtendedFunctions::INHIBIT: + case ExtendedFunctions::UNINHIBIT: + { + if (!pid.peerDefault() && !pid.canIssueInhibit()) { + LogWarning(LOG_NET, "DMR, PEER %u attempted inhibit/unhibit, not authorized", peerId); + return false; + } + } + break; + } + } + } + } + break; + } + } + + return true; + } + // is this a private call? if (data.getFLCO() == FLCO::PRIVATE) { // is the destination ID a blacklisted ID? @@ -810,6 +1009,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + return false; } } @@ -825,13 +1027,14 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(data.getSrcId())) .tag("dstId", std::to_string(data.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .field("slot", std::to_string(data.getSlotNo())) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -858,6 +1061,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -884,13 +1090,14 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(data.getSrcId())) .tag("dstId", std::to_string(data.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .field("slot", std::to_string(data.getSlotNo())) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "DMR slot %s, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -913,6 +1120,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_SLOT ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -934,6 +1144,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -959,6 +1172,9 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; @@ -1038,7 +1254,7 @@ bool TagDMRData::write_CSBK_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI /* Helper to write a NACK RSP packet. */ -void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t reason, uint8_t service) +void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t slot, uint8_t reason, uint8_t service) { std::unique_ptr csbk = std::make_unique(); csbk->setServiceKind(service); @@ -1046,7 +1262,13 @@ void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t re csbk->setSrcId(WUID_ALL); // hmmm... csbk->setDstId(dstId); - write_CSBK(peerId, 1U, csbk.get()); + if (m_network->m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + slot, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), + csbk->getSrcId(), csbk->getDstId()); + } + + write_CSBK(peerId, slot, csbk.get()); } /* Helper to write a network CSBK. */ @@ -1090,7 +1312,7 @@ void TagDMRData::write_CSBK(uint32_t peerId, uint8_t slot, lc::CSBK* csbk) } if (peerId > 0U) { - m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); } else { // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { @@ -1101,7 +1323,7 @@ void TagDMRData::write_CSBK(uint32_t peerId, uint8_t slot, lc::CSBK* csbk) m_network->m_frameQueue->flushQueue(); } - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); + m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, peer = %u, slotNo = %u, len = %u, stream = %u", peer.first, slot, messageLength, streamId); diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index 9a20d8cb4..c1313b2e3 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -8,9 +8,9 @@ * */ /** - * @file TagNXDNData.h + * @file TagDMRData.h * @ingroup fne_callhandler - * @file TagNXDNData.cpp + * @file TagDMRData.cpp * @ingroup fne_callhandler */ #if !defined(__CALLHANDLER__TAG_DMR_DATA_H__) @@ -56,12 +56,13 @@ namespace network * @param data Network data buffer. * @param len Length of data. * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @param external Flag indicating traffic is from an external peer. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. @@ -177,6 +178,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Destination Peer ID. + */ + uint32_t dstPeerId; /** * @brief Flag indicating this call is active with traffic currently in progress. */ @@ -197,6 +202,7 @@ namespace network }; typedef std::pair StatusMapPair; concurrent::unordered_map m_status; + concurrent::unordered_map m_statusPVCall; friend class packetdata::DMRPacketData; packetdata::DMRPacketData* m_packetData; @@ -236,7 +242,7 @@ namespace network /** * @brief Helper to determine if the peer is permitted for traffic. * @param peerId Peer ID. - * @param dmrData Instance of data::NetData DMR data container class. + * @param data Instance of data::NetData DMR data container class. * @param streamId Stream ID. * @param external Flag indicating this traffic came from an external peer. * @returns bool True, if valid, otherwise false. @@ -245,11 +251,12 @@ namespace network /** * @brief Helper to validate the DMR call stream. * @param peerId Peer ID. - * @param dmrData Instance of data::NetData DMR data container class. + * @param data Instance of data::NetData DMR data container class. + * @param[in] csbk Instance of dmr::lc::CSBK. * @param streamId Stream ID. * @returns bool True, if valid, otherwise false. */ - bool validate(uint32_t peerId, dmr::data::NetData& data, uint32_t streamId); + bool validate(uint32_t peerId, dmr::data::NetData& data, dmr::lc::CSBK* csbk, uint32_t streamId); /** * @brief Helper to write a grant packet. @@ -266,9 +273,10 @@ namespace network * @param peerId Peer ID. * @param dstId Destination ID. * @param reason Denial Reason. + * @param slot DMR slot number. * @param service Service being denied. */ - void write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t reason, uint8_t service); + void write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t slot, uint8_t reason, uint8_t service); /** * @brief Helper to write a network CSBK. diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 3efc82f96..cfd7a9ae4 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -11,6 +11,8 @@ #include "common/nxdn/NXDNDefines.h" #include "common/nxdn/channel/LICH.h" #include "common/nxdn/channel/CAC.h" +#include "common/nxdn/channel/FACCH1.h" +#include "common/nxdn/channel/UDCH.h" #include "common/nxdn/lc/rcch/RCCHFactory.h" #include "common/nxdn/lc/RTCH.h" #include "common/nxdn/NXDNUtils.h" @@ -48,7 +50,8 @@ TagNXDNData::TagNXDNData(FNENetwork* network, bool debug) : m_parrotFrames(), m_parrotFramesReady(false), m_status(), - m_debug(debug) + m_statusPVCall(), + m_debug(debug) { assert(network != nullptr); } @@ -59,7 +62,7 @@ TagNXDNData::~TagNXDNData() = default; /* Process a data frame from the network. */ -bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) { hrc::hrc_t pktTime = hrc::now(); @@ -90,6 +93,95 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI bool group = (data[15U] & 0x40U) == 0x40U ? false : true; lc.setGroup(group); + // process raw NXDN data bytes + UInt8Array frame; + uint8_t frameLength = buffer[23U]; + if (frameLength <= 24) { + frame = std::unique_ptr(new uint8_t[frameLength]); + ::memset(frame.get(), 0x00U, frameLength); + } + else { + frame = std::unique_ptr(new uint8_t[frameLength]); + ::memset(frame.get(), 0x00U, frameLength); + ::memcpy(frame.get(), buffer + 24U, frameLength); + } + + NXDNUtils::scrambler(frame.get() + 2U); + + channel::LICH lich; + bool valid = lich.decode(frame.get() + 2U); + + std::unique_ptr rcch; + if (valid) { + RFChannelType::E rfct = lich.getRFCT(); + FuncChannelType::E fct = lich.getFCT(); + ChOption::E option = lich.getOption(); + + if (rfct == RFChannelType::RCCH) { + rcch = lc::rcch::RCCHFactory::createRCCH(frame.get(), frameLength); + } + else if (rfct == RFChannelType::RTCH || rfct == RFChannelType::RDCH) { + // forward onto the specific processor for final processing and delivery + switch (fct) { + case FuncChannelType::USC_UDCH: + { + channel::UDCH udch; + bool validUDCH = udch.decode(data + 2U); + if (validUDCH) { + // The layer3 data will only be correct if valid is true + uint8_t buffer[NXDN_RTCH_LC_LENGTH_BYTES]; + udch.getData(buffer); + + lc.decode(buffer, NXDN_UDCH_LENGTH_BITS); + } + } + break; + default: + { + if (fct == FuncChannelType::USC_SACCH_NS) { + // the SACCH on a non-superblock frame is usually an idle and not interesting apart from the RAN. + channel::FACCH1 facch; + bool valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS); + if (!valid) + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); + if (valid) { + uint8_t buffer[10U]; + facch.getData(buffer); + + lc.decode(buffer, NXDN_FACCH1_FEC_LENGTH_BITS); + } + } else { + channel::FACCH1 facch; + bool valid = false; + switch (option) { + case ChOption::STEAL_FACCH: + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS); + if (!valid) + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); + break; + case ChOption::STEAL_FACCH1_1: + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS); + break; + case ChOption::STEAL_FACCH1_2: + valid = facch.decode(frame.get() + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); + break; + default: + break; + } + + if (valid) { + uint8_t buffer[10U]; + facch.getData(buffer); + + lc.decode(buffer, NXDN_FACCH1_FEC_LENGTH_BITS); + } + } + } + break; + } + } + } + // is the stream valid? if (validate(peerId, lc, messageType, streamId)) { // is this peer ignored? @@ -104,7 +196,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this the end of the call stream? if (messageType == MessageType::RTCH_TX_REL || messageType == MessageType::RTCH_TX_REL_EX) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } @@ -131,8 +223,22 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } } - LogMessage(LOG_NET, "NXDN, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, duration / 1000, streamId, external); + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + m_statusPVCall[dstId].reset(); + LogMessage(LOG_NET, "NXDN, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + } + else + LogMessage(LOG_NET, "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -156,10 +262,12 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this a new call stream? if ((messageType != MessageType::RTCH_TX_REL && messageType != MessageType::RTCH_TX_REL_EX)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId) { if (x.second.activeCall) @@ -170,6 +278,15 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI if (it != m_status.end()) { RxStatus status = m_status[dstId]; if (streamId != status.streamId) { + // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch + // from one source to another (primarily from bridge resources) + if (switchOver) { + status.streamId = streamId; + status.srcId = srcId; + LogMessage(LOG_NET, "NXDN, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + } + if (status.srcId != 0U && status.srcId != srcId) { uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { @@ -178,8 +295,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_network->m_callInProgress = false; } - LogWarning(LOG_NET, "NXDN, 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); + LogWarning(LOG_NET, "NXDN, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } } @@ -187,7 +304,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI else { // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot()) { m_parrotFramesReady = false; if (m_parrotFrames.size() > 0) { for (auto& pkt : m_parrotFrames) { @@ -207,7 +324,25 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI 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); + // is this a private call? + if (!group) { + m_statusPVCall[dstId].callStartTime = pktTime; + m_statusPVCall[dstId].srcId = srcId; + m_statusPVCall[dstId].dstId = dstId; + m_statusPVCall[dstId].streamId = streamId; + m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].activeCall = true; + + // find the SSRC of the peer that registered this unit + uint32_t regSSRC = m_network->findPeerUnitReg(srcId); + m_statusPVCall[dstId].dstPeerId = regSSRC; + + LogMessage(LOG_NET, "NXDN, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, external); + } + else + LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } @@ -240,11 +375,81 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_status[dstId].lastPacket = hrc::now(); + bool noConnectedPeerRepeat = false; + bool privateCallInProgress = false; + + // is this a private call in-progress? + if (m_network->m_restrictPVCallToRegOnly) { + if (!group) { + privateCallInProgress = true; + } + + if (privateCallInProgress) { + // if we've not determined the destination peer, we have to repeat it everywhere + if (m_statusPVCall[dstId].dstPeerId == 0U) { + noConnectedPeerRepeat = false; + privateCallInProgress = false; // trick the system to repeat everywhere + } else { + // if this is a private call, check if the destination peer is one directly connected to us, if not + // flag the call so it only repeats to external peers + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { + noConnectedPeerRepeat = true; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + if (conn->isExternalPeer()) { + continue; + } + } + + if (m_statusPVCall[dstId].dstPeerId == peer.first) { + noConnectedPeerRepeat = false; + break; + } + } + } + } + } + } + } + // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + + if (m_network->m_restrictPVCallToRegOnly) { + // is this peer an external peer? + bool external = false; + if (conn != nullptr) { + external = conn->isExternalPeer(); + } + + // is this a private call? + if (!group && !external) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + if (peer.first != m_statusPVCall[dstId].dstPeerId) { + continue; + } + } + } + } + // is this peer ignored? if (!isPeerPermitted(peer.first, lc, messageType, streamId)) { continue; @@ -261,10 +466,10 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, messageType, dstId); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, true); if (m_network->m_debug) { - LogDebug(LOG_NET, "NXDN, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -275,6 +480,12 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI m_network->m_frameQueue->flushQueue(); } + // if this is a private call, and we have already repeated to the connected peer that registered + // the unit, don't repeat to any external peers + if (privateCallInProgress && !noConnectedPeerRepeat) { + return true; + } + // repeat traffic to external peers if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { @@ -283,6 +494,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // don't try to repeat traffic to the source peer...if this traffic // is coming from a external peer if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(dstPeerId, lc, messageType, streamId, true)) { continue; @@ -304,10 +520,14 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, messageType, dstId); - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "NXDN, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -381,7 +601,7 @@ void TagNXDNData::playbackParrot() auto& pkt = m_parrotFrames[0]; if (pkt.buffer != nullptr) { if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -390,7 +610,7 @@ void TagNXDNData::playbackParrot() else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "NXDN, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -579,6 +799,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -615,6 +838,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -632,12 +858,13 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(lc.getSrcId())) .tag("dstId", std::to_string(lc.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -665,6 +892,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -691,12 +921,13 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(lc.getSrcId())) .tag("dstId", std::to_string(lc.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "NXDN, illegal/unknown RID attempted access, srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -718,6 +949,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -743,6 +977,9 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; @@ -814,8 +1051,8 @@ void TagNXDNData::write_Message_Deny(uint32_t peerId, uint32_t srcId, uint32_t d rcch->setDstId(dstId); if (m_network->m_verbose) { - LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X, service = $%02X, srcId = %u, dstId = %u", - service, srcId, dstId); + LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", + reason, NXDNUtils::causeToString(reason).c_str(), service, srcId, dstId); } write_Message(peerId, rcch.get()); @@ -865,5 +1102,5 @@ void TagNXDNData::write_Message(uint32_t peerId, lc::RCCH* rcch) } uint32_t streamId = m_network->createStreamId(); - m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); } diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 35ec58cdd..f48328cd0 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -55,12 +55,13 @@ namespace network * @param data Network data buffer. * @param len Length of data. * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @param external Flag indicating traffic is from an external peer. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. @@ -142,6 +143,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Destination Peer ID. + */ + uint32_t dstPeerId; /** * @brief Flag indicating this call is active with traffic currently in progress. */ @@ -161,6 +166,7 @@ namespace network }; typedef std::pair StatusMapPair; concurrent::unordered_map m_status; + concurrent::unordered_map m_statusPVCall; bool m_debug; diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 766240c3f..4728f7d4c 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -48,6 +48,7 @@ TagP25Data::TagP25Data(FNENetwork* network, bool debug) : m_parrotFramesReady(false), m_parrotFirstFrame(true), m_status(), + m_statusPVCall(), m_packetData(nullptr), m_debug(debug) { @@ -65,10 +66,16 @@ TagP25Data::~TagP25Data() /* Process a data frame from the network. */ -bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) { hrc::hrc_t pktTime = hrc::now(); + // P25 network frame should never be less then 24 bytes + if (len < 24U) { + LogError(LOG_NET, "malformed P25 packet, len < 24, shouldn't happen"); + return false; + } + DECLARE_UINT8_ARRAY(buffer, len); ::memcpy(buffer, data, len); @@ -77,8 +84,13 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint32_t srcId = GET_UINT24(data, 5U); uint32_t dstId = GET_UINT24(data, 8U); + uint8_t controlByte = data[14U]; + uint8_t MFId = data[15U]; + uint32_t sysId = (data[11U] << 8) | (data[12U] << 0); + uint32_t netId = GET_UINT24(data, 16U); + uint8_t lsd1 = data[20U]; uint8_t lsd2 = data[21U]; @@ -138,19 +150,24 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId control.setDstId(dstId); control.setMFId(MFId); + // set the LC group flag based on the control byte + control.setGroup((controlByte & NET_CTRL_U2U) != NET_CTRL_U2U); + lsd.setLSD1(lsd1); lsd.setLSD2(lsd2); - uint32_t frameLength = buffer[23U]; + uint8_t frameLength = buffer[23U]; + + if (!m_network->validateP25FrameLength(frameLength, len, duid)) + return false; // process a TSBK out into a class literal if possible std::unique_ptr tsbk; if (duid == DUID::TSDU) { - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + tsbk = lc::tsbk::TSBKFactory::createTSBK(data); } // is the stream valid? @@ -165,7 +182,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this the end of the call stream? if ((duid == DUID::TDU) || (duid == DUID::TDULC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); return false; } @@ -173,7 +190,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint64_t duration = hrc::diff(pktTime, status.callStartTime); // perform a test for grant demands, and if the TG isn't valid ignore the demand - bool grantDemand = (data[14U] & 0x80U) == 0x80U; + bool grantDemand = (data[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; if (grantDemand) { lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); if (!tg.config().active()) { @@ -181,6 +198,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId) { if (x.second.activeCall) @@ -189,9 +208,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId 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); + if (grantDemand && !switchOver) { + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } else { @@ -208,8 +227,22 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - LogMessage(LOG_NET, "P25, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, duration / 1000, streamId, external); + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + m_statusPVCall[dstId].reset(); + LogMessage(LOG_NET, "P25, Private Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); + } + else + LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -234,10 +267,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if ((duid != DUID::TDU) && (duid != DUID::TDULC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "P25, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); return false; } + bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { if (x.second.dstId == dstId) { if (x.second.activeCall) @@ -248,6 +283,15 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (it != m_status.end()) { RxStatus status = m_status[dstId]; if (streamId != status.streamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { + // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch + // from one source to another (primarily from bridge resources) + if (switchOver) { + status.streamId = streamId; + status.srcId = srcId; + LogMessage(LOG_NET, "P25, Call Source Switched, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + } + if (status.srcId != 0U && status.srcId != srcId) { uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { @@ -256,8 +300,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_callInProgress = false; } - 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); + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); return false; } } @@ -285,7 +329,25 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId 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); + // is this a private call? + if (lco == LCO::PRIVATE) { + m_statusPVCall[dstId].callStartTime = pktTime; + m_statusPVCall[dstId].srcId = srcId; + m_statusPVCall[dstId].dstId = dstId; + m_statusPVCall[dstId].streamId = streamId; + m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].activeCall = true; + + // find the SSRC of the peer that registered this unit + uint32_t regSSRC = m_network->findPeerUnitReg(dstId); + m_statusPVCall[dstId].dstPeerId = regSSRC; + + LogMessage(LOG_NET, "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); + } + else + LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); m_network->m_callInProgress = true; } @@ -323,11 +385,95 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_status[dstId].lastPacket = hrc::now(); + bool noConnectedPeerRepeat = false; + bool privateCallInProgress = false; + + // is this a private call in-progress? + if (m_network->m_restrictPVCallToRegOnly) { + if ((control.getLCO() != LCO::PRIVATE) && !control.getGroup()) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == control.getDstId()) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + privateCallInProgress = true; + } + } else { + if (lco == LCO::PRIVATE) { + privateCallInProgress = true; + } + } + + if (privateCallInProgress) { + // if we've not determined the destination peer, we have to repeat it everywhere + if (m_statusPVCall[dstId].dstPeerId == 0U) { + noConnectedPeerRepeat = false; + privateCallInProgress = false; // trick the system to repeat everywhere + } else { + // if this is a private call, check if the destination peer is one directly connected to us, if not + // flag the call so it only repeats to external peers + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { + noConnectedPeerRepeat = true; + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + if (conn->isExternalPeer()) { + continue; + } + } + + if (m_statusPVCall[dstId].dstPeerId == peer.first) { + noConnectedPeerRepeat = false; + break; + } + } + } + } + } + } + } + // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { + if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (ssrc == peer.first) { + // skip the peer if it is the source peer + continue; + } + + if (m_network->m_restrictPVCallToRegOnly) { + // is this peer an external peer? + bool external = false; + if (conn != nullptr) { + external = conn->isExternalPeer(); + } + + // is this a private call? + if ((lco == LCO::PRIVATE) && !external) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + if (peer.first != m_statusPVCall[dstId].dstPeerId) { + continue; + } + } + } + } + // is this peer ignored? if (!isPeerPermitted(peer.first, control, duid, streamId)) { continue; @@ -349,10 +495,10 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, duid, dstId); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, true); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); } if (!m_network->m_callInProgress) @@ -363,6 +509,12 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_network->m_frameQueue->flushQueue(); } + // if this is a private call, and we have already repeated to the connected peer that registered + // the unit, don't repeat to any external peers + if (privateCallInProgress && !noConnectedPeerRepeat) { + return true; + } + // repeat traffic to external peers if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { @@ -371,6 +523,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // don't try to repeat traffic to the source peer...if this traffic // is coming from a external peer if (dstPeerId != peerId) { + if (ssrc == dstPeerId) { + // skip the peer if it is the source peer + continue; + } + // is this peer ignored? if (!isPeerPermitted(dstPeerId, control, duid, streamId, true)) { continue; @@ -394,10 +551,14 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // process TSDUs going to external peers if (processTSDUToExternal(outboundPeerBuffer, peerId, dstPeerId, duid)) { - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); + // are we a peer link? + if (peer.second->isPeerLink()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + else + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); + LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", + ssrc, peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); } } @@ -485,7 +646,7 @@ void TagP25Data::playbackParrot() // create empty LSD data::LowSpeedData lsd = data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // send grant demand uint32_t messageLength = 0U; @@ -493,13 +654,13 @@ void TagP25Data::playbackParrot() if (message != nullptr) { if (m_network->m_parrotOnlyOriginating) { LogMessage(LOG_NET, "P25, Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", pkt.peerId, srcId, dstId); - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_network->createStreamId(), false); } else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { LogMessage(LOG_NET, "P25, Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", peer.first, srcId, dstId); - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, m_network->createStreamId(), false); } } @@ -510,7 +671,7 @@ void TagP25Data::playbackParrot() } if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -518,7 +679,7 @@ void TagP25Data::playbackParrot() } else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); @@ -625,11 +786,10 @@ void TagP25Data::routeRewrite(uint8_t* buffer, uint32_t peerId, uint8_t duid, ui // are we receiving a TSDU? if (duid == DUID::TSDU) { - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { // handle standard P25 reference opcodes switch (tsbk->getLCO()) { @@ -704,13 +864,12 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) { // are we receiving a TSDU? if (duid == DUID::TSDU) { - uint32_t frameLength = buffer[23U]; + uint32_t frameLength = P25_TSDU_FRAME_LENGTH_BYTES;//buffer[23U]; - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { // report tsbk event to InfluxDB if (m_network->m_enableInfluxDB && m_network->m_influxLogRawData) { @@ -754,6 +913,27 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X, peerId = %u", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass(), peerId); } + + // check if the sending peer is mapped + lookups::AdjPeerMapEntry adjPeerMap = m_network->m_adjSiteMapLookup->find(peerId); + if (!adjPeerMap.isEmpty()) { + if (!adjPeerMap.active()) { + // LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to other peers is disabled, dropping", peerId); + return false; + } else { + // if the peer is mapped, we can repeat the ADJ_STS_BCAST to other peers + if (m_network->m_peers.size() > 0U) { + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + write_TSDU(peer.first, osp); + } + } + + // this seems strange -- but we want to prevent the main processing loop from repeating the ADJ_STS_BCAST + return false; + } + } + } } } break; @@ -770,11 +950,10 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) 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); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get()); + std::unique_ptr tdulc = lc::tdulc::TDULCFactory::createTDULC(data); if (tdulc != nullptr) { // handle standard P25 reference opcodes switch (tdulc->getLCO()) { @@ -800,13 +979,12 @@ bool TagP25Data::processTSDUTo(uint8_t* buffer, uint32_t peerId, uint8_t duid) { // are we receiving a TSDU? if (duid == DUID::TSDU) { - uint32_t frameLength = buffer[23U]; + uint32_t frameLength = P25_TSDU_FRAME_LENGTH_BYTES;//buffer[23U]; - UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { //uint32_t srcId = tsbk->getSrcId(); uint32_t dstId = tsbk->getDstId(); @@ -869,11 +1047,10 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint if (duid == DUID::TSDU) { 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); + DECLARE_UINT8_ARRAY(data, frameLength); + ::memcpy(data, buffer + 24U, frameLength); - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data); if (tsbk != nullptr) { // handle standard P25 reference opcodes switch (tsbk->getLCO()) { @@ -913,6 +1090,13 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, return false; if (!m_network->checkU2UDroppedPeer(peerId)) return true; + + // is this a U2U call? + lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); + if (!rid.radioDefault() && rid.radioEnabled()) { + return true; + } + return false; } @@ -935,43 +1119,6 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, if (duid == DUID::TSDU || duid == DUID::PDU) return true; - if (duid == DUID::HDU) { - if (m_network->m_filterHeaders) { - 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()) { - return true; - } - - // is this peer excluded from the group? - std::vector exclusion = tg.config().exclusion(); - if (exclusion.size() > 0) { - auto it = std::find(exclusion.begin(), exclusion.end(), peerId); - if (it != exclusion.end()) { - return false; - } - } - - tg = m_network->m_tidLookup->findByRewrite(peerId, control.getDstId()); - if (!tg.isInvalid()) { - return true; - } - - // is this a U2U call? - lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); - if (!rid.radioDefault() && rid.radioEnabled()) { - return true; - } - - return false; - } - } - - // always permit a headers - return true; - } - if (duid == DUID::TDULC) { // always permit a terminator return true; @@ -1119,6 +1266,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1140,6 +1290,18 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const // always validate a terminator if the source is valid if (m_network->m_filterTerminators) { if ((duid == DUID::TDU || duid == DUID::TDULC) && control.getDstId() != 0U) { + // is this a private call? + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == control.getDstId()) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + return true; + } + // is this a group call? lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); if (!tg.isInvalid()) { @@ -1151,12 +1313,6 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const return true; } - // is this a U2U call? - lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); - if (!rid.radioDefault() && rid.radioEnabled()) { - return true; - } - //LogDebugEx(LOG_NET, "TagP25Data::validate()", "TDU for invalid destination, dropped, dstId = %u", control.getDstId()); return false; } @@ -1168,8 +1324,24 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const return true; } + // validate private call in-progress + bool privateCallInProgress = false; + if ((control.getLCO() != LCO::PRIVATE) && !control.getGroup()) { + // is this a private call? if so only repeat to the peer that registered the unit + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + if (x.second.dstId == control.getDstId()) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_statusPVCall.end()) { + privateCallInProgress = true; + } + } + // is this a private call? - if (control.getLCO() == LCO::PRIVATE) { + if ((control.getLCO() == LCO::PRIVATE) || privateCallInProgress) { // is the destination ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); if (!rid.radioDefault()) { @@ -1187,6 +1359,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1204,12 +1379,13 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(control.getSrcId())) .tag("dstId", std::to_string(control.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "P25, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1242,6 +1418,26 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } } break; + case TSBKO::IOSP_EXT_FNCT: + { + const lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast(tsbk); + if (iosp != nullptr) { + lookups::PeerId pid = m_network->m_peerListLookup->find(peerId); + uint32_t func = iosp->getExtendedFunction(); + switch (func) { + case ExtendedFunctions::INHIBIT: + case ExtendedFunctions::UNINHIBIT: + { + if (!pid.peerDefault() && !pid.canIssueInhibit()) { + LogWarning(LOG_NET, "P25, PEER %u attempted inhibit/unhibit, not authorized", peerId); + return false; + } + } + break; + } + } + } + break; } // handle validating DVM call termination packets @@ -1285,6 +1481,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1311,12 +1510,13 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(control.getSrcId())) .tag("dstId", std::to_string(control.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "P25, illegal/unknown RID attempted access, srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_NET, "P25, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1338,6 +1538,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1363,6 +1566,9 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; @@ -1449,8 +1655,9 @@ void TagP25Data::write_TSDU_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId osp->setGroup(grp); if (m_network->m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::denyRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } write_TSDU(peerId, osp.get()); @@ -1469,8 +1676,9 @@ void TagP25Data::write_TSDU_Queue(uint32_t peerId, uint32_t srcId, uint32_t dstI osp->setGroup(grp); if (m_network->m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::queueRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } write_TSDU(peerId, osp.get()); @@ -1518,7 +1726,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) uint32_t streamId = m_network->createStreamId(); if (peerId > 0U) { - m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); } else { // repeat traffic to the connected peers @@ -1530,7 +1738,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) m_network->m_frameQueue->flushQueue(); } - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, peer = %u, len = %u, streamId = %u", diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index 324338409..12de5c16b 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -61,12 +61,13 @@ namespace network * @param data Network data buffer. * @param len Length of data. * @param peerId Peer ID. + * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @param external Flag indicating traffic is from an external peer. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. @@ -191,6 +192,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Destination Peer ID. + */ + uint32_t dstPeerId; /** * @brief Flag indicating this call is active with traffic currently in progress. */ @@ -210,6 +215,7 @@ namespace network }; typedef std::pair StatusMapPair; concurrent::unordered_map m_status; + concurrent::unordered_map m_statusPVCall; friend class packetdata::P25PacketData; packetdata::P25PacketData* m_packetData; diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp index e12fc72cb..89aaa829f 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp @@ -92,7 +92,7 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee dmrData.getData(frame); // is the stream valid? - if (m_tag->validate(peerId, dmrData, streamId)) { + if (m_tag->validate(peerId, dmrData, nullptr, streamId)) { // is this peer ignored? if (!m_tag->isPeerPermitted(peerId, dmrData, streamId)) { return false; @@ -131,7 +131,7 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee bool ret = status->header.decode(frame); if (!ret) { LogError(LOG_NET, "DMR Slot %u, DataType::DATA_HEADER, unable to decode the network data header", status->slotNo); - Utils::dump(1U, "Unfixable PDU Data", frame, DMR_FRAME_LENGTH_BYTES); + Utils::dump(1U, "DMR, Unfixable PDU Data", frame, DMR_FRAME_LENGTH_BYTES); delete status; m_status.erase(peerId); @@ -254,7 +254,7 @@ void DMRPacketData::dispatch(uint32_t peerId, dmr::data::NetData& dmrData, const } if (m_network->m_dumpPacketData) { - Utils::dump(1U, "ISP PDU Packet", status->pduUserData, status->pduDataOffset); + Utils::dump(1U, "DMR, ISP PDU Packet", status->pduUserData, status->pduDataOffset); } } } @@ -283,7 +283,7 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, m_network->m_frameQueue->flushQueue(); } - m_network->writePeer(peer.first, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId, true); if (m_network->m_debug) { LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, slotNo = %u, len = %u, pktSeq = %u, stream = %u", peerId, peer.first, seqNo, srcId, dstId, status->slotNo, len, pktSeq, streamId); diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index 97965f0fb..53fa07728 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -41,12 +41,11 @@ using namespace p25::sndcp; // --------------------------------------------------------------------------- const uint8_t DATA_CALL_COLL_TIMEOUT = 60U; +const uint8_t MAX_PKT_RETRY_CNT = 5U; -// --------------------------------------------------------------------------- -// Static Class Members -// --------------------------------------------------------------------------- - -std::timed_mutex P25PacketData::m_vtunMutex; +const uint32_t INTERPACKET_DELAY = 100U; // milliseconds +const uint32_t ARP_RETRY_MS = 5000U; // milliseconds +const uint32_t SUBSCRIBER_READY_RETRY_MS = 1000U; // milliseconds // --------------------------------------------------------------------------- // Public Class Members @@ -57,7 +56,7 @@ std::timed_mutex P25PacketData::m_vtunMutex; P25PacketData::P25PacketData(FNENetwork* network, TagP25Data* tag, bool debug) : m_network(network), m_tag(tag), - m_dataFrames(), + m_queuedFrames(), m_status(), m_arpTable(), m_readyForNextPkt(), @@ -140,7 +139,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee bool ret = status->header.decode(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); delete status; m_status.erase(peerId); @@ -175,11 +174,6 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee return true; } - if (status->header.getSAP() != PDUSAP::EXT_ADDR && - status->header.getFormat() != PDUFormatType::UNCONFIRMED) { - m_suSendSeq[status->llId] = 0U; - } - LogMessage(LOG_NET, "P25, Data Call Start, peer = %u, llId = %u, streamId = %u, external = %u", peerId, status->llId, streamId, external); return true; } @@ -234,7 +228,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee bool ret = status->header.decodeExtAddr(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); delete status; m_status.erase(peerId); @@ -247,7 +241,6 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee status->extendedAddress = true; status->llId = status->header.getSrcLLId(); - m_suSendSeq[status->llId] = 0U; offset += P25_PDU_FEC_LENGTH_BYTES; blocksToFollow--; @@ -306,7 +299,7 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); if (m_network->m_dumpPacketData) { - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); } } @@ -368,33 +361,58 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a uint16_t pktLen = Utils::reverseEndian(ipHeader->ip_len); // bryanb: this could be problematic on different endianness #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::processPacketFrame() packet", data, pktLen); + Utils::dump(1U, "P25, P25PacketData::processPacketFrame() packet", data, pktLen); #endif - VTUNDataFrame* dataFrame = new VTUNDataFrame(); - dataFrame->buffer = new uint8_t[len]; - ::memcpy(dataFrame->buffer, data, len); - dataFrame->bufferLen = len; - dataFrame->pktLen = pktLen; - dataFrame->proto = proto; + uint32_t llId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); - uint32_t dstLlId = getLLIdAddress(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); + uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); + uint32_t tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); - dataFrame->srcHWAddr = WUID_FNE; - dataFrame->srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); - dataFrame->tgtHWAddr = dstLlId; - dataFrame->tgtProtoAddr = Utils::reverseEndian(ipHeader->ip_dst.s_addr); + std::string srcIpStr = __IP_FROM_UINT(srcProtoAddr); + std::string tgtIpStr = __IP_FROM_UINT(tgtProtoAddr); - dataFrame->timestamp = now; + LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", + srcIpStr.c_str(), WUID_FNE, tgtIpStr.c_str(), llId, pktLen, proto); - if (dstLlId == 0U) { - LogMessage(LOG_NET, "P25, no ARP entry for, dstIp = %s", dstIp); - write_PDU_ARP(Utils::reverseEndian(ipHeader->ip_dst.s_addr)); + // assemble a P25 PDU frame header for transport... + data::DataHeader* pktHeader = new data::DataHeader(); + pktHeader->setFormat(PDUFormatType::CONFIRMED); + pktHeader->setMFId(MFG_STANDARD); + pktHeader->setAckNeeded(true); + pktHeader->setOutbound(true); + pktHeader->setSAP(PDUSAP::PACKET_DATA); + pktHeader->setLLId(llId); + pktHeader->setBlocksToFollow(1U); + + pktHeader->calculateLength(pktLen); + uint32_t pduLength = pktHeader->getPDULength(); + if (pduLength < pktLen) { + LogWarning(LOG_NET, "P25, VTUN, data truncated!"); + pktLen = pduLength; // don't overflow the buffer } - m_vtunMutex.try_lock_for(std::chrono::milliseconds(60)); - m_dataFrames.push_back(dataFrame); - m_vtunMutex.unlock(); + DECLARE_UINT8_ARRAY(pduUserData, pduLength); + ::memcpy(pduUserData, data, pktLen); +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, P25PacketData::processPacketFrame(), pduUserData", pduUserData, pduLength); +#endif + + // queue frame for dispatch + QueuedDataFrame* qf = new QueuedDataFrame(); + qf->retryCnt = 0U; + qf->extendRetry = false; + qf->timestamp = now + INTERPACKET_DELAY; + + qf->header = pktHeader; + qf->llId = llId; + qf->tgtProtoAddr = tgtProtoAddr; + + qf->userData = new uint8_t[pduLength]; + ::memcpy(qf->userData, pduUserData, pduLength); + qf->userDataLen = pduLength; + + m_queuedFrames.push_back(qf); #endif // !defined(_WIN32) } @@ -404,93 +422,80 @@ void P25PacketData::clock(uint32_t ms) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - if (m_dataFrames.size() == 0U) { + if (m_queuedFrames.size() == 0U) { return; } // transmit queued data frames bool processed = false; - bool locked = m_vtunMutex.try_lock_for(std::chrono::milliseconds(60)); - - auto& dataFrame = m_dataFrames[0]; - - if (locked) - m_vtunMutex.unlock(); - - if (dataFrame != nullptr) { - if (now > dataFrame->timestamp + 500U) { + + auto& frame = m_queuedFrames[0]; + if (frame != nullptr) { + if (now > frame->timestamp) { processed = true; + if (frame->retryCnt >= MAX_PKT_RETRY_CNT && !frame->extendRetry) { + LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); + goto pkt_clock_abort; + } + + if (frame->retryCnt >= (MAX_PKT_RETRY_CNT * 2U) && frame->extendRetry) { + LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); + m_readyForNextPkt[frame->llId] = true; // force ready for next packet + goto pkt_clock_abort; + } + + std::string tgtIpStr = __IP_FROM_UINT(frame->tgtProtoAddr); + LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, dstIp = %s (%u), userDataLen = %u, retries = %u", + tgtIpStr.c_str(), frame->llId, frame->userDataLen, frame->retryCnt); + // do we have a valid target address? - if (dataFrame->tgtHWAddr == 0U) { - uint32_t dstLlId = getLLIdAddress(dataFrame->tgtProtoAddr); - if (dstLlId == 0U) { + if (frame->llId == 0U) { + frame->llId = getLLIdAddress(frame->tgtProtoAddr); + if (frame->llId == 0U) { + LogWarning(LOG_NET, "P25, no ARP entry for, dstIp = %s", tgtIpStr.c_str()); + write_PDU_ARP(frame->tgtProtoAddr); + processed = false; + frame->timestamp = now + ARP_RETRY_MS; + frame->retryCnt++; goto pkt_clock_abort; } - - dataFrame->tgtHWAddr = dstLlId; + else { + frame->header->setLLId(frame->llId); + } } // is the SU ready for the next packet? - auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == dataFrame->tgtHWAddr; }); + auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == frame->llId; }); if (ready != m_readyForNextPkt.end()) { if (!ready->second) { + LogWarning(LOG_NET, "P25, subscriber not ready, dstIp = %s", tgtIpStr.c_str()); processed = false; + frame->timestamp = now + SUBSCRIBER_READY_RETRY_MS; + frame->extendRetry = true; + frame->retryCnt++; goto pkt_clock_abort; } - } else { - processed = false; - goto pkt_clock_abort; } - m_readyForNextPkt[dataFrame->tgtHWAddr] = false; - - std::string srcIp = __IP_FROM_UINT(dataFrame->srcProtoAddr); - std::string tgtIp = __IP_FROM_UINT(dataFrame->tgtProtoAddr); - - LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", - srcIp.c_str(), dataFrame->srcHWAddr, tgtIp.c_str(), dataFrame->tgtHWAddr, dataFrame->pktLen, dataFrame->proto); - - // assemble a P25 PDU frame header for transport... - data::DataHeader rspHeader = data::DataHeader(); - rspHeader.setFormat(PDUFormatType::CONFIRMED); - rspHeader.setMFId(MFG_STANDARD); - rspHeader.setAckNeeded(true); - rspHeader.setOutbound(true); - rspHeader.setSAP(PDUSAP::EXT_ADDR); - rspHeader.setLLId(dataFrame->tgtHWAddr); - rspHeader.setBlocksToFollow(1U); - - rspHeader.setEXSAP(PDUSAP::PACKET_DATA); - rspHeader.setSrcLLId(WUID_FNE); - - rspHeader.calculateLength(dataFrame->pktLen); - uint32_t pduLength = rspHeader.getPDULength(); - - DECLARE_UINT8_ARRAY(pduUserData, pduLength); - ::memcpy(pduUserData + 4U, dataFrame->buffer, dataFrame->pktLen); -#if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::clock() pduUserData", pduUserData, pduLength); -#endif - dispatchUserFrameToFNE(rspHeader, true, pduUserData); + m_readyForNextPkt[frame->llId] = false; + dispatchUserFrameToFNE(*frame->header, false, frame->userData); } } pkt_clock_abort: - locked = m_vtunMutex.try_lock_for(std::chrono::milliseconds(60)); - m_dataFrames.pop_front(); + m_queuedFrames.pop_front(); if (processed) { - if (dataFrame->buffer != nullptr) - delete[] dataFrame->buffer; - delete dataFrame; + if (frame->userData != nullptr) + delete[] frame->userData; + if (frame->header != nullptr) + delete frame->header; + delete frame; } else { // requeue packet - m_dataFrames.push_back(dataFrame); + m_queuedFrames.push_back(frame); } - - if (locked) - m_vtunMutex.unlock(); } // --------------------------------------------------------------------------- @@ -511,35 +516,68 @@ void P25PacketData::dispatch(uint32_t peerId) bool crcValid = false; if (status->header.getBlocksToFollow() > 0U) { if (status->pduUserDataLength < 4U) { - LogError(LOG_NET, P25_PDU_STR ", illegal PDU packet length, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduUserDataLength); + LogError(LOG_NET, P25_PDU_STR ", illegal PDU packet length, peer = %u, llId = %u, blocks %u, len %u", + peerId, status->header.getLLId(), status->header.getBlocksToFollow(), status->pduUserDataLength); return; } crcValid = edac::CRC::checkCRC32(status->pduUserData, status->pduUserDataLength); if (!crcValid) { - LogError(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduUserDataLength); + LogError(LOG_NET, P25_PDU_STR ", failed CRC-32 check, peer = %u, llId = %u, blocks %u, len %u", + peerId, status->header.getLLId(), status->header.getBlocksToFollow(), status->pduUserDataLength); return; } } if (m_network->m_dumpPacketData && status->dataBlockCnt > 0U) { - Utils::dump(1U, "ISP PDU Packet", status->pduUserData, status->pduUserDataLength); - } + Utils::dump(1U, "P25, ISP PDU Packet", status->pduUserData, status->pduUserDataLength); + } if (status->header.getFormat() == PDUFormatType::RSP) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, peer = %u, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + peerId, status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), status->header.getLLId(), status->header.getSrcLLId()); + // bryanb: this is naive and possibly error prone + m_readyForNextPkt[status->header.getSrcLLId()] = true; + if (status->header.getResponseClass() == PDUAckClass::ACK && status->header.getResponseType() == PDUAckType::ACK) { - m_readyForNextPkt[status->header.getSrcLLId()] = true; + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, peer = %u, llId = %u, all blocks received OK, n = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + } else { + if (status->header.getResponseClass() == PDUAckClass::NACK) { + switch (status->header.getResponseType()) { + case PDUAckType::NACK_ILLEGAL: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, peer = %u, llId = %u", + peerId, status->header.getLLId()); + break; + case PDUAckType::NACK_PACKET_CRC: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, peer = %u, llId = %u, n = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + break; + case PDUAckType::NACK_SEQ: + case PDUAckType::NACK_OUT_OF_SEQ: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, peer = %u, llId = %u, seqNo = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + break; + case PDUAckType::NACK_UNDELIVERABLE: + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, peer = %u, llId = %u, n = %u", + peerId, status->header.getLLId(), status->header.getResponseStatus()); + break; + + default: + break; + } + } } - write_PDU_Ack_Response(status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), - status->header.getLLId(), status->header.getSrcLLId()); return; } + if (status->header.getFormat() == PDUFormatType::UNCONFIRMED) { + m_readyForNextPkt[status->header.getSrcLLId()] = true; + } + uint8_t sap = (status->extendedAddress) ? status->header.getEXSAP() : status->header.getSAP(); // don't dispatch SNDCP control, conventional data registration or ARP @@ -666,7 +704,7 @@ void P25PacketData::dispatch(uint32_t peerId) DECLARE_UINT8_ARRAY(ipFrame, pktLen); ::memcpy(ipFrame, status->pduUserData + dataPktOffset, pktLen); #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::dispatch() ipFrame", ipFrame, pktLen); + Utils::dump(1U, "P25, P25PacketData::dispatch(), ipFrame", ipFrame, pktLen); #endif if (!m_network->m_host->m_tun->write(ipFrame, pktLen)) { LogError(LOG_NET, P25_PDU_STR ", failed to write IP frame to virtual tunnel, len %u", pktLen); @@ -674,15 +712,24 @@ void P25PacketData::dispatch(uint32_t peerId) // if the packet is unhandled and sent off to VTUN; ack the packet so the sender knows we received it if (!handled) { - write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), status->header.getSrcLLId(), status->header.getLLId()); + write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), status->header.getSrcLLId(), + true, status->header.getLLId()); } #endif // !defined(_WIN32) } break; + case PDUSAP::CONV_DATA_REG: + { + LogMessage(LOG_NET, P25_PDU_STR ", CONV_DATA_REG (Conventional Data Registration), peer = %u, blocksToFollow = %u", + peerId, status->header.getBlocksToFollow()); + + processConvDataReg(status); + } + break; case PDUSAP::SNDCP_CTRL_DATA: { - LogMessage(LOG_NET, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), blocksToFollow = %u", - status->header.getBlocksToFollow()); + LogMessage(LOG_NET, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), peer = %u, blocksToFollow = %u", + peerId, status->header.getBlocksToFollow()); processSNDCPControl(status); } @@ -690,8 +737,8 @@ void P25PacketData::dispatch(uint32_t peerId) case PDUSAP::UNENC_KMM: case PDUSAP::ENC_KMM: { - LogMessage(LOG_NET, P25_PDU_STR ", KMM (Key Management Message), blocksToFollow = %u", - status->header.getBlocksToFollow()); + LogMessage(LOG_NET, P25_PDU_STR ", KMM (Key Management Message), peer = %u, blocksToFollow = %u", + peerId, status->header.getBlocksToFollow()); processKMM(status); } @@ -719,7 +766,7 @@ void P25PacketData::dispatchToFNE(uint32_t peerId) m_network->m_frameQueue->flushQueue(); } - write_PDU_User(peer.first, nullptr, status->header, status->extendedAddress, status->pduUserData, true); + write_PDU_User(peer.first, peerId, nullptr, status->header, status->extendedAddress, status->pduUserData, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peerId, peer.first, DUID::PDU, srcId, dstId); @@ -749,7 +796,7 @@ void P25PacketData::dispatchToFNE(uint32_t peerId) continue; } - write_PDU_User(dstPeerId, peer.second, status->header, status->extendedAddress, status->pduUserData); + write_PDU_User(dstPeerId, peerId, peer.second, status->header, status->extendedAddress, status->pduUserData); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peerId, dstPeerId, DUID::PDU, srcId, dstId); @@ -766,16 +813,15 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo uint32_t srcId = (extendedAddress) ? dataHeader.getSrcLLId() : dataHeader.getLLId(); uint32_t dstId = dataHeader.getLLId(); - uint8_t sendSeqNo = m_suSendSeq[srcId]; - if (sendSeqNo == 0U) { + // update the sequence number + m_suSendSeq[srcId]++; + if (m_suSendSeq[srcId] >= 8U) + { + m_suSendSeq[srcId] = 0U; dataHeader.setSynchronize(true); } - dataHeader.setNs(sendSeqNo); - ++sendSeqNo; - if (sendSeqNo > 7U) - sendSeqNo = 0U; - m_suSendSeq[srcId] = sendSeqNo; + dataHeader.setNs(m_suSendSeq[srcId]); // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { @@ -786,7 +832,7 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo m_network->m_frameQueue->flushQueue(); } - write_PDU_User(peer.first, nullptr, dataHeader, extendedAddress, pduUserData, true); + write_PDU_User(peer.first, m_network->m_peerId, nullptr, dataHeader, extendedAddress, pduUserData, true); if (m_network->m_debug) { LogDebug(LOG_NET, "P25, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peer.first, DUID::PDU, srcId, dstId); @@ -798,6 +844,44 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo } } + +/* Helper used to process conventional data registration from PDU data. */ + +bool P25PacketData::processConvDataReg(RxStatus* status) +{ + uint8_t regType = (status->pduUserData[0U] >> 4) & 0x0F; + switch (regType) { + case PDURegType::CONNECT: + { + uint32_t llId = (status->pduUserData[1U] << 16) + (status->pduUserData[2U] << 8) + status->pduUserData[3U]; + uint32_t ipAddr = (status->pduUserData[8U] << 24) + (status->pduUserData[9U] << 16) + (status->pduUserData[10U] << 8) + status->pduUserData[11U]; + + if (ipAddr == 0U) { + LogWarning(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect) with zero IP address, llId = %u", llId); + ipAddr = getIPAddress(llId); + } + + LogMessage(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + m_arpTable[llId] = ipAddr; // update ARP table + } + break; + case PDURegType::DISCONNECT: + { + uint32_t llId = (status->pduUserData[1U] << 16) + (status->pduUserData[2U] << 8) + status->pduUserData[3U]; + + LogMessage(LOG_NET, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); + + m_arpTable.erase(llId); + } + break; + default: + LogError(LOG_RF, "P25 unhandled PDU registration type, regType = $%02X", regType); + break; + } + + return true; +} + /* Helper used to process SNDCP control data from PDU data. */ bool P25PacketData::processSNDCPControl(RxStatus* status) @@ -914,7 +998,7 @@ void P25PacketData::write_PDU_ARP(uint32_t addr) SET_UINT32(addr, arpPacket, 18U); // Target Protocol Address #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::write_PDU_ARP() arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); + Utils::dump(1U, "P25, P25PacketData::write_PDU_ARP(), arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); #endif LogMessage(LOG_NET, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(addr).c_str(), fneIPv4.c_str(), WUID_FNE); @@ -970,7 +1054,7 @@ void P25PacketData::write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorL SET_UINT24(requestorLlid, arpPacket, 15U); // Requestor Hardware Address SET_UINT32(requestorAddr, arpPacket, 18U); // Requestor Protocol Address #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25PacketData::write_PDU_ARP_Reply() arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); + Utils::dump(1U, "P25, P25PacketData::write_PDU_ARP_Reply(), arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); #endif LogMessage(LOG_NET, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(targetAddr).c_str(), tgtLlid); @@ -998,7 +1082,7 @@ void P25PacketData::write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorL /* Helper to write a PDU acknowledge response. */ -void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId) +void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, uint32_t srcLlId) { if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) return; @@ -1014,7 +1098,11 @@ void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, ui if (srcLlId > 0U) { rspHeader.setSrcLLId(srcLlId); } - rspHeader.setFullMessage(true); + + if (!extendedAddress) + rspHeader.setFullMessage(true); + else + rspHeader.setFullMessage(false); rspHeader.setBlocksToFollow(0U); @@ -1023,7 +1111,7 @@ void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, ui /* Helper to write user data as a P25 PDU packet. */ -void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNet, data::DataHeader& dataHeader, +void P25PacketData::write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool queueOnly) { uint32_t streamId = m_network->createStreamId(); @@ -1042,7 +1130,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe // generate the PDU header and 1/2 rate Trellis dataHeader.encode(buffer); - writeNetwork(peerId, peerNet, dataHeader, 0U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); + writeNetwork(peerId, srcPeerId, peerNet, dataHeader, 0U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); if (pduUserData == nullptr) return; @@ -1060,7 +1148,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); dataHeader.encodeExtAddr(buffer); - writeNetwork(peerId, peerNet, dataHeader, 1U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); + writeNetwork(peerId, srcPeerId, peerNet, dataHeader, 1U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); ++pktSeq; dataOffset += P25_PDU_HEADER_LENGTH_BYTES; @@ -1087,7 +1175,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe } if (m_network->m_dumpPacketData) { - Utils::dump("OSP PDU User Data", pduUserData, packetLength); + Utils::dump("P25, OSP PDU User Data", pduUserData, packetLength); } // generate the PDU data @@ -1104,7 +1192,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); dataBlock.encode(buffer); - writeNetwork(peerId, peerNet, dataHeader, networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, (dataBlock.getLastBlock()) ? RTP_END_OF_CALL_SEQ : pktSeq, streamId); + writeNetwork(peerId, srcPeerId, peerNet, dataHeader, networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, (dataBlock.getLastBlock()) ? RTP_END_OF_CALL_SEQ : pktSeq, streamId); ++pktSeq; dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; @@ -1116,7 +1204,7 @@ void P25PacketData::write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNe /* Write data processed to the network. */ -bool P25PacketData::writeNetwork(uint32_t peerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, +bool P25PacketData::writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, const uint8_t *data, uint32_t len, uint16_t pktSeq, uint32_t streamId, bool queueOnly) { assert(data != nullptr); @@ -1130,7 +1218,7 @@ bool P25PacketData::writeNetwork(uint32_t peerId, network::PeerNetwork* peerNet, if (peerNet != nullptr) { return peerNet->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId); } else { - return m_network->writePeer(peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId, false); + return m_network->writePeer(peerId, srcPeerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId, false); } } @@ -1166,6 +1254,16 @@ uint32_t P25PacketData::getIPAddress(uint32_t llId) if (hasARPEntry(llId)) { return m_arpTable[llId]; + } else { + // do we have a static entry for this LLID? + lookups::RadioId rid = m_network->m_ridLookup->find(llId); + if (!rid.radioDefault()) { + if (rid.radioEnabled()) { + std::string addr = rid.radioIPAddress(); + uint32_t ipAddr = __IP_FROM_STR(addr); + return ipAddr; + } + } } return 0U; @@ -1186,5 +1284,19 @@ uint32_t P25PacketData::getLLIdAddress(uint32_t addr) } } + // lookup IP from static RID table + std::string ipAddr = __IP_FROM_UINT(addr); + std::unordered_map ridTable = m_network->m_ridLookup->table(); + auto it = std::find_if(ridTable.begin(), ridTable.end(), [&](std::pair x) { + if (x.second.radioIPAddress() == ipAddr) { + if (x.second.radioEnabled() && !x.second.radioDefault()) + return true; + } + return false; + }); + if (it != ridTable.end()) { + return it->first; + } + return 0U; } diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index f33d80970..30392adef 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -85,27 +85,25 @@ namespace network private: FNENetwork* m_network; - TagP25Data *m_tag; + TagP25Data* m_tag; /** * @brief Represents a queued data frame from the VTUN. */ - class VTUNDataFrame { + class QueuedDataFrame { public: - uint32_t srcHWAddr; //! Source Hardware Address - uint32_t srcProtoAddr; //! Source Protocol Address - uint32_t tgtHWAddr; //! Target Hardware Address - uint32_t tgtProtoAddr; //! Target Protocol Address + p25::data::DataHeader* header; //! Instance of a PDU data header. + uint32_t llId; //! Logical Link ID + uint32_t tgtProtoAddr; //! Target Protocol Address - uint8_t* buffer; //! Raw data buffer - uint32_t bufferLen; //! Length of raw data buffer - - uint16_t pktLen; //! Packet Length - uint8_t proto; //! Packet Protocol + uint8_t* userData; //! Raw data buffer + uint32_t userDataLen; //! Length of raw data buffer - uint64_t timestamp; //! Timestamp in milliseconds + uint64_t timestamp; //! Timestamp in milliseconds + uint8_t retryCnt; //! Packet Retry Counter + bool extendRetry; //! Flag indicating whether or not to extend the retry count for this packet. }; - concurrent::deque m_dataFrames; + concurrent::deque m_queuedFrames; /** * @brief Represents the receive status of a call. @@ -179,8 +177,6 @@ namespace network bool m_debug; - static std::timed_mutex m_vtunMutex; - /** * @brief Helper to dispatch PDU user data. * @param peerId Peer ID. @@ -199,6 +195,12 @@ namespace network */ void dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData); + /** + * @brief Helper used to process conventional data registration from PDU data. + * @param status Instance of the RxStatus class. + * @returns bool True, if conventional data registration data was processed, otherwise false. + */ + bool processConvDataReg(RxStatus* status); /** * @brief Helper used to process SNDCP control data from PDU data. * @param status Instance of the RxStatus class. @@ -233,24 +235,28 @@ namespace network * @param ackType Acknowledgement Type. * @param ackStatus * @param llId Logical Link ID. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. * @param srcLlId Source Logical Link ID. */ - void write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U); + void write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, + uint32_t srcLlId = 0U); /** * @brief Helper to write user data as a P25 PDU packet. * @param peerId Peer ID. + * @param srcPeerId Source Peer ID. * @param peerNet Instance of PeerNetwork to use to send traffic. * @param dataHeader Instance of a PDU data header. * @param extendedAddress Flag indicating whether or not to extended addressing is in use. * @param pduUserData Buffer containing user data to transmit. */ - void write_PDU_User(uint32_t peerId, network::PeerNetwork* peerNet, p25::data::DataHeader& dataHeader, + void write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, p25::data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool queueOnly = false); /** * @brief Write data processed to the network. * @param peerId Peer ID. + * @param srcPeerId Source Peer ID. * @param peerNet Instance of PeerNetwork to use to send traffic. * @param dataHeader Instance of a PDU data header. * @param currentBlock Current Block ID. @@ -259,7 +265,7 @@ namespace network * @param pktSeq RTP packet sequence. * @param streamId Stream ID. */ - bool writeNetwork(uint32_t peerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, + bool writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, const uint8_t* data, uint32_t len, uint16_t pktSeq, uint32_t streamId, bool queueOnly = false); /** diff --git a/src/fne/network/influxdb/InfluxDB.cpp b/src/fne/network/influxdb/InfluxDB.cpp index a070571cf..0cf1857d3 100644 --- a/src/fne/network/influxdb/InfluxDB.cpp +++ b/src/fne/network/influxdb/InfluxDB.cpp @@ -63,7 +63,11 @@ int detail::inner::request(const char* method, const char* uri, const std::strin ret = getaddrinfo(si.host().c_str(), std::to_string(si.port()).c_str(), &hints, &addr); if (ret != 0) { - ::LogError(LOG_HOST, "Failed to determine InfluxDB server host, err: %d", errno); +#if defined(_WIN32) + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); +#else + ::LogError(LOG_HOST, "Failed to determine InfluxDB server host, err: %d (%s)", errno, strerror(errno)); +#endif // defined(_WIN32) return 1; } @@ -73,9 +77,11 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); #else - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } @@ -85,12 +91,16 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&sockOptVal, sizeof(int)) != 0) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sockOptVal, sizeof(int)) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // defined(_WIN32) @@ -99,21 +109,27 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) u_long flags = 1; if (ioctlsocket(fd, FIONBIO, &flags) != 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %lu", ::GetLastError()); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #else int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // defined(_WIN32) @@ -140,6 +156,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin if (retryCnt > 5U) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, timed out while connecting"); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } @@ -150,9 +168,11 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %lu", ::GetLastError()); #else - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); #endif // defined(_WIN32) closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } else if (ret > 0) { #if !defined(_WIN32) @@ -161,14 +181,18 @@ int detail::inner::request(const char* method, const char* uri, const std::strin socklen_t slen = sizeof(int); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)(&valopt), &slen) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d (%s)", errno, strerror(errno)); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } if (valopt) { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, err: %d", valopt); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // !defined(_WIN32) @@ -176,6 +200,8 @@ int detail::inner::request(const char* method, const char* uri, const std::strin } else { ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, timed out while connecting"); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } } while (true); @@ -186,21 +212,27 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #if defined(_WIN32) flags = 0; if (ioctlsocket(fd, FIONBIO, &flags) != 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed ioctlsocket, err: %lu", ::GetLastError()); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #else flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_GETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) { - ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d", errno); + ::LogError(LOG_HOST, "Failed to connect to InfluxDB server, failed fcntl(F_SETFL), err: %d (%s)", errno, strerror(errno)); closesocket(fd); + if (addr != nullptr) + free(addr); return 1; } #endif // defined(_WIN32) @@ -244,7 +276,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin ret = 0; if (writev(fd, iv, 2) < (int)(iv[0].iov_len + iv[1].iov_len)) { - ::LogError(LOG_HOST, "Failed to write statistical data to InfluxDB server, err: %d", errno); + ::LogError(LOG_HOST, "Failed to write statistical data to InfluxDB server, err: %d (%s)", errno, strerror(errno)); ret = -6; } @@ -259,5 +291,7 @@ int detail::inner::request(const char* method, const char* uri, const std::strin #endif // close socket closesocket(fd); + if (addr != nullptr) + free(addr); return ret; } diff --git a/src/fne/win32/fne.ico b/src/fne/win32/fne.ico new file mode 100644 index 000000000..3a46b8772 Binary files /dev/null and b/src/fne/win32/fne.ico differ diff --git a/src/fne/win32/resource.h b/src/fne/win32/resource.h new file mode 100644 index 000000000..22a4c4e68 --- /dev/null +++ b/src/fne/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * 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 + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/fne/win32/resource.rc b/src/fne/win32/resource.rc new file mode 100644 index 000000000..330fc13ab Binary files /dev/null and b/src/fne/win32/resource.rc differ diff --git a/src/host/CMakeLists.txt b/src/host/CMakeLists.txt index a06febbac..aba8f13eb 100644 --- a/src/host/CMakeLists.txt +++ b/src/host/CMakeLists.txt @@ -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 # * Copyright (C) 2022 Natalie Moore # * # */ @@ -50,4 +50,12 @@ file(GLOB dvmhost_SRC "src/host/modem/port/specialized/*.cpp" "src/host/network/*.h" "src/host/network/*.cpp" + "src/host/win32/*.h" +) + +# +# Windows Resource Scripts +# +file(GLOB dvmhost_RC + "src/host/win32/*.rc" ) diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 470e18fc7..3c4bb617b 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -656,7 +656,7 @@ bool Host::createModem() if (useFSCForUDP) { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort + 1U, g_remotePort, g_remoteLocalPort, true, fscInitiator, debug); ((modem::port::specialized::V24UDPPort*)modemPort)->setHeartbeatInterval(fscHeartbeat); - } else { + } else { modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, 0U, 0U, false, false, debug); } m_udpDFSIRemotePort = modemPort; @@ -750,7 +750,7 @@ bool Host::createModem() m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(Host::rmtPortModemHandler, this)); } - if (useFSCForUDP) { + if (g_remoteModemMode && m_isModemDFSI && useFSCForUDP) { modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDFSIRemotePort); udpPort->openFSC(); } @@ -945,7 +945,7 @@ bool Host::createNetwork() // initialize networking if (netEnable) { - m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, slot1, slot2, + m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, false, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup); m_network->setLookups(m_ridLookup, m_tidLookup); diff --git a/src/host/Host.cpp b/src/host/Host.cpp index bbb8d6162..761493f11 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -644,6 +644,15 @@ int Host::run() g_killed = true; } + const uint32_t __ = 0x67558U; + if ((m_p25NetId >> 8) == ((__ ^ 0x38258U) >> 7)) { + ::fatal("just ... stop, you aren't cool. error 38258"); + /* + ** By disabling these checks, you are a giant toolbag .. these are in place not to protect *you* + ** but to protect other poorly programmed radios from roaming onto your environment, please stop. + */ + } + // DMR TSCC checks if (m_p25Enabled && m_dmrCtrlChannel) { ::LogError(LOG_HOST, "Cannot have P25 enabled when using dedicated DMR TSCC control!"); @@ -1536,7 +1545,7 @@ bool Host::rmtPortModemHandler(Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM r if (rspType == RTM_OK && len > 0U) { if (modem->getTrace()) - Utils::dump(1U, "TX Remote Data", buffer, len); + Utils::dump(1U, "Host::rmtPortModemHandler(), TX Remote Data", buffer, len); // never send less then 3 bytes if (len < 3U) @@ -1553,11 +1562,11 @@ bool Host::rmtPortModemHandler(Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM r uint32_t ret = m_modemRemotePort->read(data, BUFFER_LENGTH); if (ret > 0) { if (modem->getTrace()) - Utils::dump(1U, "RX Remote Data", (uint8_t*)data, ret); + Utils::dump(1U, "Host::rmtPortModemHandler(), RX Remote Data", (uint8_t*)data, ret); if (ret < 3U) { LogError(LOG_MODEM, "Illegal length of remote data must be >3 bytes"); - Utils::dump("Buffer dump", data, ret); + Utils::dump("Host::rmtPortModemHandler(), data", data, ret); // handled modem response return true; diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index e3bf7e8d2..81c609226 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -143,6 +143,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_enableTSCC = enableTSCC; yaml::Node rfssConfig = systemConf["config"]; + uint32_t defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + m_slot1->setDefaultNetIdleTG(defaultNetIdleTalkgroup); + m_slot2->setDefaultNetIdleTG(defaultNetIdleTalkgroup); + yaml::Node controlCh = rfssConfig["controlCh"]; bool notifyCC = controlCh["notifyEnable"].as(false); m_slot1->setNotifyCC(notifyCC); @@ -186,6 +190,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_slot1->m_ignoreAffiliationCheck = ignoreAffiliationCheck; m_slot2->m_ignoreAffiliationCheck = ignoreAffiliationCheck; + bool legacyGroupReg = dmrProtocol["legacyGroupReg"].as(false); + m_slot1->setLegacyGroupReg(legacyGroupReg); + m_slot2->setLegacyGroupReg(legacyGroupReg); + // set the In-Call Control function callback if (m_network != nullptr) { m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo) { processInCallCtrl(command, dstId, slotNo); }); @@ -218,7 +226,12 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa LogInfo(" Disable Network Grants: yes"); } + if (defaultNetIdleTalkgroup != 0U) { + LogInfo(" Default Network Idle Talkgroup: $%04X", defaultNetIdleTalkgroup); + } + LogInfo(" Ignore Affiliation Check: %s", ignoreAffiliationCheck ? "yes" : "no"); + LogInfo(" Legacy Group Registration: %s", legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", notifyCC ? "yes" : "no"); LogInfo(" Silence Threshold: %u (%.1f%%)", silenceThreshold, float(silenceThreshold) / 1.41F); LogInfo(" Frame Loss Threshold: %u", frameLossThreshold); diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 44730d50e..1a8dedbdc 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -154,6 +154,8 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_ignoreAffiliationCheck(false), m_disableNetworkGrant(false), m_convNetGrantDemand(false), + m_legacyGroupReg(false), + m_defaultNetIdleTalkgroup(0U), m_tsccPayloadDstId(0U), m_tsccPayloadSrcId(0U), m_tsccPayloadGroup(false), @@ -389,7 +391,7 @@ void Slot::processNetwork(const data::NetData& dmrData) { // don't process network frames if the RF modem isn't in a listening state if (m_rfState != RS_RF_LISTENING) { - LogWarning(LOG_NET, "DMR Slot %u, Traffic collision detect, preempting new network traffic to existing RF traffic!", m_slotNo); + m_network->resetDMR(m_slotNo); return; } @@ -444,8 +446,8 @@ void Slot::processNetwork(const data::NetData& dmrData) case DataType::VOICE_LC_HEADER: case DataType::DATA_HEADER: { - bool grantDemand = (dmrData.getControl() & 0x80U) == 0x80U; - bool unitToUnit = (dmrData.getControl() & 0x01U) == 0x01U; + bool grantDemand = (dmrData.getControl() & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool unitToUnit = (dmrData.getControl() & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; if (grantDemand) { if (m_disableNetworkGrant) { @@ -1777,8 +1779,8 @@ void Slot::setShortLC_TSCC(SiteData siteData, uint16_t counter) lc[3U] = (uint8_t)((lcValue >> 0) & 0xFFU); lc[4U] = edac::CRC::crc8(lc, 4U); - //LogDebugEx(LOG_DMR, "Slot::setShortLC_TSCC()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); - //Utils::dump(1U, "[Slot::shortLC_TSCC()]", lc, 5U); + // LogDebugEx(LOG_DMR, "Slot::setShortLC_TSCC()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); + // Utils::dump(1U, "DMR, Slot::shortLC_TSCC(), LC", lc, 5U); uint8_t sLC[9U]; @@ -1837,8 +1839,8 @@ void Slot::setShortLC_Payload(SiteData siteData, uint16_t counter) lc[3U] = (uint8_t)((lcValue >> 0) & 0xFFU); lc[4U] = edac::CRC::crc8(lc, 4U); - //LogDebugEx(LOG_DMR, "Slot::setShortLC_Payload()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); - //Utils::dump(1U, "[Slot::setShortLC_Payload()]", lc, 5U); + // LogDebugEx(LOG_DMR, "Slot::setShortLC_Payload()", "netId = %02X, siteId = %02X", siteData.netId(), siteData.siteId()); + // Utils::dump(1U, "DMR, Slot::setShortLC_Payload(), LC", lc, 5U); uint8_t sLC[9U]; diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index c8c6e3f4e..04e71a92a 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -256,6 +256,11 @@ namespace dmr * @param notifyCC Flag indicating whether the voice channels will notify the TSCC of traffic channel changes. */ void setNotifyCC(bool notifyCC) { m_notifyCC = notifyCC; } + /** + * @brief Sets a flag indicating whether the legacy group registration is enabled. + * @param legacyGroupReg Flag indicating whether the legacy group registration is enabled. + */ + void setLegacyGroupReg(bool legacyGroupReg) { m_legacyGroupReg = legacyGroupReg; } /** * @brief Sets a flag indicating whether the control message debug is enabled. @@ -274,6 +279,12 @@ namespace dmr */ void setFrameLossThreshold(uint32_t threshold) { m_frameLossThreshold = threshold; } + /** + * @brief Helper to set the default network idle talkgroup. + * @param tg Talkgroup ID. + */ + void setDefaultNetIdleTG(uint32_t tg) { m_defaultNetIdleTalkgroup = tg; } + /** * @brief Helper to get the last transmitted destination ID. * @returns uint32_t Last transmitted Destination ID. @@ -411,6 +422,9 @@ namespace dmr bool m_ignoreAffiliationCheck; bool m_disableNetworkGrant; bool m_convNetGrantDemand; + bool m_legacyGroupReg; + + uint32_t m_defaultNetIdleTalkgroup; uint32_t m_tsccPayloadDstId; uint32_t m_tsccPayloadSrcId; diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 671384e38..e458c1ed5 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -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) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -33,20 +33,6 @@ using namespace dmr::packet; // Macros // --------------------------------------------------------------------------- -// Don't process RF frames if the network isn't in a idle state. -#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ - if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ - return false; \ - } - -#define CHECK_TG_HANG(_DST_ID) \ - if (m_slot->m_rfLastDstId != 0U) { \ - if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ - return; \ - } \ - } - // Make sure control data is supported. #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ if (!m_slot->m_dmr->getTSCCSlot()->m_enableTSCC) { \ @@ -152,7 +138,11 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) m_slot->m_affiliations->touchUnitReg(srcId); if (srcId != 0U || dstId != 0U) { - CHECK_TRAFFIC_COLLISION(dstId); + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); + return false; + } // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { @@ -188,7 +178,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) break; case CSBKO::RAND: { - if (csbk->getFID() == FID_DMRA) { + if (csbk->getFID() == FID_MOT) { if (m_verbose) { LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); @@ -446,7 +436,11 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) uint32_t srcId = csbk->getSrcId(); uint32_t dstId = csbk->getDstId(); - CHECK_TG_HANG(dstId); + if (m_slot->m_rfLastDstId != 0U) { + if (m_slot->m_rfLastDstId != dstId && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { + return; + } + } // if data preamble, signal its existence if (csbk->getDataContent()) { @@ -469,7 +463,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) break; case CSBKO::RAND: { - if (csbk->getFID() == FID_DMRA) { + if (csbk->getFID() == FID_MOT) { if (m_verbose) { LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); @@ -782,6 +776,12 @@ void ControlSignaling::writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint csbk->setSrcId(WUID_ALL); // hmmm... csbk->setDstId(dstId); + if (m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), + csbk->getSrcId(), csbk->getDstId()); + } + writeRF_CSBK_Imm(csbk.get()); } @@ -795,6 +795,12 @@ void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uin csbk->setSrcId(WUID_ALL); // hmmm... csbk->setDstId(dstId); + if (m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), + csbk->getSrcId(), csbk->getDstId()); + } + writeRF_CSBK_Imm(csbk.get()); } @@ -1027,6 +1033,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ req["group"].set(grp); bool voice = true; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } @@ -1118,6 +1126,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ req["group"].set(grp); bool voice = true; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } @@ -1270,6 +1280,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u req["group"].set(grp); bool voice = false; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } @@ -1316,6 +1328,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u req["group"].set(grp); bool voice = false; req["voice"].set(voice); + bool clear = false; + req["clear"].set(clear); g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port(), true); } diff --git a/src/host/dmr/packet/Data.cpp b/src/host/dmr/packet/Data.cpp index 0a79aabc8..22bc1d49e 100644 --- a/src/host/dmr/packet/Data.cpp +++ b/src/host/dmr/packet/Data.cpp @@ -31,45 +31,6 @@ using namespace dmr::packet; #include #include -// --------------------------------------------------------------------------- -// Macros -// --------------------------------------------------------------------------- - -#define CHECK_AUTHORITATIVE(_DST_ID) \ - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { \ - if (!g_disableNonAuthoritativeLogging) \ - LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } - -#define CHECK_NET_AUTHORITATIVE(_DST_ID) \ - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { \ - return; \ - } - -// Don't process RF frames if the network isn't in a idle state. -#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ - if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - if (m_slot->m_enableTSCC && _DST_ID == m_slot->m_netLastDstId) { \ - if (m_slot->m_affiliations->isNetGranted(_DST_ID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - } - -#define CHECK_TG_HANG(_DST_ID) \ - if (m_slot->m_rfLastDstId != 0U) { \ - if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ - return; \ - } \ - } - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -163,8 +124,27 @@ bool Data::process(uint8_t* data, uint32_t len) uint32_t srcId = m_rfDataHeader.getSrcId(); uint32_t dstId = m_rfDataHeader.getDstId(); - CHECK_AUTHORITATIVE(dstId); - CHECK_TRAFFIC_COLLISION(dstId); + if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); + m_slot->m_rfState = RS_RF_LISTENING; + return false; + } + + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return false; + } + + if (m_slot->m_enableTSCC && dstId == m_slot->m_netLastDstId) { + if (m_slot->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return false; + } + } if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -252,7 +232,7 @@ bool Data::process(uint8_t* data, uint32_t len) uint8_t controlByte = 0U; if (m_slot->m_convNetGrantDemand) - controlByte |= 0x80U; // Grant Demand Flag + controlByte |= network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag m_slot->writeNetwork(data, DataType::DATA_HEADER, controlByte); @@ -314,7 +294,7 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_dumpDataPacket) { - Utils::dump(1U, "PDU Packet", m_pduUserData, m_pduDataOffset); + Utils::dump(1U, "DMR, PDU Packet", m_pduUserData, m_pduDataOffset); } } @@ -421,8 +401,15 @@ void Data::processNetwork(const data::NetData& dmrData) uint32_t srcId = m_netDataHeader.getSrcId(); uint32_t dstId = m_netDataHeader.getDstId(); - CHECK_NET_AUTHORITATIVE(dstId); - CHECK_TG_HANG(dstId); + if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { + return; + } + + if (m_slot->m_rfLastDstId != 0U) { + if (m_slot->m_rfLastDstId != dstId && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { + return; + } + } if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -554,7 +541,7 @@ void Data::processNetwork(const data::NetData& dmrData) } if (m_dumpDataPacket) { - Utils::dump(1U, "PDU Packet", m_pduUserData, m_pduDataOffset); + Utils::dump(1U, "DMR, PDU Packet", m_pduUserData, m_pduDataOffset); } } diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index bcd56c52c..ca574e96d 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -33,6 +33,7 @@ using namespace dmr::packet; // Macros // --------------------------------------------------------------------------- +// Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_AUTHORITATIVE(_DST_ID) \ if (!m_slot->m_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ if (!g_disableNonAuthoritativeLogging) \ @@ -41,40 +42,12 @@ using namespace dmr::packet; return false; \ } +// Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_NET_AUTHORITATIVE(_DST_ID) \ if (!m_slot->m_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ return; \ } -#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ - if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - if (m_slot->m_enableTSCC && _DST_ID == m_slot->m_netLastDstId) { \ - if (m_slot->m_affiliations->isNetGranted(_DST_ID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); \ - m_slot->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - } - -// Don't process network frames if the destination ID's don't match and the network TG hang -// timer is running, and don't process network frames if the RF modem isn't in a listening state -#define CHECK_NET_TRAFFIC_COLLISION(_DST_ID) \ - if (m_slot->m_rfLastDstId != 0U) { \ - if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { \ - return; \ - } \ - } \ - \ - if (m_slot->m_netLastDstId != 0U) { \ - if (m_slot->m_netLastDstId != _DST_ID && (m_slot->m_netTGHang.isRunning() && !m_slot->m_netTGHang.hasExpired())) { \ - return; \ - } \ - } \ - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -109,7 +82,9 @@ bool Voice::process(uint8_t* data, uint32_t len) FLCO::E flco = lc->getFLCO(); CHECK_AUTHORITATIVE(dstId); - CHECK_TRAFFIC_COLLISION(dstId); + + if (checkRFTrafficCollision(dstId)) + return false; if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -145,6 +120,25 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfState = RS_RF_REJECTED; return false; } + + // are we auto-registering legacy radios to groups? + if (m_slot->m_legacyGroupReg) { + if (!m_slot->m_affiliations->isGroupAff(srcId, dstId)) { + // update dynamic unit registration table + if (!m_slot->m_affiliations->isUnitReg(srcId)) { + m_slot->m_affiliations->unitReg(srcId); + } + + if (m_slot->m_network != nullptr) + m_slot->m_network->announceUnitRegistration(srcId); + + // update dynamic affiliation table + m_slot->m_affiliations->groupAff(srcId, dstId); + + if (m_slot->m_network != nullptr) + m_slot->m_network->announceGroupAffiliation(srcId, dstId); + } + } } m_slot->m_data->m_lastRejectId = 0U; @@ -201,7 +195,9 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t controlByte = 0U; if (m_slot->m_convNetGrantDemand) - controlByte |= 0x80U; // Grant Demand Flag + controlByte |= network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + if (flco == FLCO::PRIVATE) + controlByte |= network::NET_CTRL_U2U; // Unit-to-Unit Flag m_slot->writeNetwork(data, DataType::VOICE_LC_HEADER, controlByte); @@ -278,12 +274,13 @@ bool Voice::process(uint8_t* data, uint32_t len) uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - errors = m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), - errors, float(errors) / 1.41F); - } + bool pf = m_slot->m_rfLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + errors = m_fec.regenerateDMR(data + 2U); if (errors > m_slot->m_silenceThreshold) { insertNullAudio(data + 2U); @@ -295,6 +292,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfErrs += errors; } + if (m_verbose) { + LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + fid, pf, errors, float(errors) / 1.41F); + } + m_slot->m_rfBits += 141U; m_slot->m_rfFrames++; @@ -340,12 +342,13 @@ bool Voice::process(uint8_t* data, uint32_t len) uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - errors = m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), - m_rfN, errors, float(errors) / 1.41F); - } + bool pf = m_slot->m_rfLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + errors = m_fec.regenerateDMR(data + 2U); if (errors > m_slot->m_silenceThreshold) { // get the LCSS from the EMB @@ -364,6 +367,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfErrs += errors; } + if (m_verbose) { + LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + m_rfN, fid, pf, errors, float(errors) / 1.41F); + } + m_slot->m_rfBits += 141U; m_slot->m_rfFrames++; @@ -505,7 +513,9 @@ bool Voice::process(uint8_t* data, uint32_t len) FLCO::E flco = lc->getFLCO(); CHECK_AUTHORITATIVE(dstId); - CHECK_TRAFFIC_COLLISION(dstId); + + if (checkRFTrafficCollision(dstId)) + return false; // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { @@ -573,7 +583,9 @@ bool Voice::process(uint8_t* data, uint32_t len) uint8_t controlByte = 0U; if (m_slot->m_convNetGrantDemand) - controlByte |= 0x80U; // Grant Demand Flag + controlByte |= network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + if (flco == FLCO::PRIVATE) + controlByte |= network::NET_CTRL_U2U; // Unit-to-Unit Flag m_slot->writeNetwork(start, DataType::VOICE_LC_HEADER, controlByte); @@ -591,12 +603,13 @@ bool Voice::process(uint8_t* data, uint32_t len) // send the original audio frame out uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - errors = m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, errs = %u/141 (%.1f%%)", - m_slot->m_slotNo, m_rfN, errors, float(errors) / 1.41F); - } + bool pf = m_slot->m_rfLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + errors = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + errors = m_fec.regenerateDMR(data + 2U); if (errors > m_slot->m_silenceThreshold) { // get the LCSS from the EMB @@ -615,6 +628,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rfErrs += errors; } + if (m_verbose) { + LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_rfN, fid, pf, errors, float(errors) / 1.41F); + } + m_slot->m_rfBits += 141U; m_slot->m_rfFrames++; @@ -659,8 +677,6 @@ void Voice::processNetwork(const data::NetData& dmrData) if (m_slot->m_netState == RS_NET_AUDIO) return; - - lc::FullLC fullLC; std::unique_ptr lc = fullLC.decode(data + 2U, DataType::VOICE_LC_HEADER); if (lc == nullptr) { @@ -673,7 +689,9 @@ void Voice::processNetwork(const data::NetData& dmrData) FLCO::E flco = lc->getFLCO(); CHECK_NET_AUTHORITATIVE(dstId); - CHECK_NET_TRAFFIC_COLLISION(dstId); + + if (checkNetTrafficCollision(dstId)) + return; if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) { m_slot->m_tsccPayloadActRetry.stop(); @@ -763,7 +781,9 @@ void Voice::processNetwork(const data::NetData& dmrData) uint32_t dstId = lc->getDstId(); CHECK_NET_AUTHORITATIVE(dstId); - CHECK_NET_TRAFFIC_COLLISION(dstId); + + if (checkNetTrafficCollision(dstId)) + return; m_slot->m_netLC = std::move(lc); @@ -935,12 +955,18 @@ void Voice::processNetwork(const data::NetData& dmrData) if (m_slot->m_netState == RS_NET_AUDIO) { uint8_t fid = m_slot->m_netLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, VOICE_SYNC audio, sequence no = %u, errs = %u/141 (%.1f%%)", - m_slot->m_slotNo, m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); - } + bool pf = m_slot->m_netLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + m_slot->m_netErrs = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); + } + + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, VOICE_SYNC audio, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_netN, fid, pf, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); } if (m_netN >= 5U) { @@ -985,13 +1011,20 @@ void Voice::processNetwork(const data::NetData& dmrData) return; uint8_t fid = m_slot->m_netLC->getFID(); - if (fid == FID_ETSI || fid == FID_DMRA) { - m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); - if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), - m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); - } + bool pf = m_slot->m_netLC->getPF(); + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { + if (fid == FID_KENWOOD && pf) + m_slot->m_netErrs = 0U; // bryanb: for what we are assuming is Kenwood, these are encrypted frames + // don't bother trying to regenerate or perform FEC + else + m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); } + + if (m_verbose) { + LogMessage(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), + m_netN, fid, pf, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); + } + m_slot->m_netBits += 141U; m_slot->m_netTGHang.start(); @@ -1160,6 +1193,62 @@ Voice::~Voice() delete[] m_netEmbeddedData; } +/* Helper to perform RF traffic collision checking. */ + +bool Voice::checkRFTrafficCollision(uint32_t dstId) +{ + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_slot->m_netState != RS_NET_IDLE && dstId == m_slot->m_netLastDstId) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return true; + } + + if (m_slot->m_enableTSCC && dstId == m_slot->m_netLastDstId) { + if (m_slot->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); + m_slot->m_rfState = RS_RF_LISTENING; + return true; + } + } + + return false; +} + +/* Helper to perform network traffic collision checking. */ + +bool Voice::checkNetTrafficCollision(uint32_t dstId) +{ + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_slot->m_rfLastDstId != 0U) { + if (m_slot->m_rfLastDstId != dstId && (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired())) { + return true; + } + } + + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_slot->m_rfTGHang.isRunning() && !m_slot->m_rfTGHang.hasExpired()) { + m_slot->m_rfTGHang.stop(); + } + + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_slot->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_slot->m_rfTGHang.isRunning()) { + if (m_slot->m_defaultNetIdleTalkgroup != dstId) { + return true; + } + } + + if (m_slot->m_netLastDstId != 0U) { + if (m_slot->m_netLastDstId != dstId && (m_slot->m_netTGHang.isRunning() && !m_slot->m_netTGHang.hasExpired())) { + return true; + } + } + + return false; +} + /* */ void Voice::logGPSPosition(const uint32_t srcId, const uint8_t* data) @@ -1284,7 +1373,7 @@ void Voice::insertSilence(uint32_t count) for (uint32_t i = 0U; i < count; i++) { // only use our silence frame if its AMBE audio data - if (fid == FID_ETSI || fid == FID_DMRA) { + if (fid == FID_ETSI || fid == FID_MOT || fid == FID_KENWOOD) { if (i > 0U) { ::memcpy(data, SILENCE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); m_lastFrameValid = false; diff --git a/src/host/dmr/packet/Voice.h b/src/host/dmr/packet/Voice.h index 8265f194e..dd36ef774 100644 --- a/src/host/dmr/packet/Voice.h +++ b/src/host/dmr/packet/Voice.h @@ -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) 2017-2022 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -112,6 +112,19 @@ namespace dmr */ ~Voice(); + /** + * @brief Helper to perform RF traffic collision checking. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkRFTrafficCollision(uint32_t dstId); + /** + * @brief Helper to perform network traffic collision checking. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkNetTrafficCollision(uint32_t dstId); + /** * @brief Log GPS information. * @param srcId Source radio ID. diff --git a/src/host/modem/Modem.cpp b/src/host/modem/Modem.cpp index dd5956052..578b24815 100644 --- a/src/host/modem/Modem.cpp +++ b/src/host/modem/Modem.cpp @@ -581,7 +581,7 @@ void Modem::clock(uint32_t ms) m_rxDMRQueue1.addData(m_buffer + 3U, m_length - 3U); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX DMR Data 1", m_buffer + 3U, m_length - 3U); + Utils::dump(1U, "Modem::clock(), RX DMR Data 1", m_buffer + 3U, m_length - 3U); } } break; @@ -607,7 +607,7 @@ void Modem::clock(uint32_t ms) m_rxDMRQueue2.addData(m_buffer + 3U, m_length - 3U); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX DMR Data 2", m_buffer + 3U, m_length - 3U); + Utils::dump(1U, "Modem::clock(), RX DMR Data 2", m_buffer + 3U, m_length - 3U); } } break; @@ -669,7 +669,7 @@ void Modem::clock(uint32_t ms) m_rxP25Queue.addData(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + Utils::dump(1U, "Modem::clock(), RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; @@ -712,7 +712,7 @@ void Modem::clock(uint32_t ms) m_rxNXDNQueue.addData(m_buffer + 3U, m_length - 3U); if (m_trace) - Utils::dump(1U, "[Modem::clock()] RX NXDN Data", m_buffer + 3U, m_length - 3U); + Utils::dump(1U, "Modem::clock(), RX NXDN Data", m_buffer + 3U, m_length - 3U); } } break; @@ -889,7 +889,7 @@ void Modem::clock(uint32_t ms) default: LogWarning(LOG_MODEM, "Unknown message, type = %02X", m_buffer[2U]); - Utils::dump("Buffer dump", m_buffer, m_length); + Utils::dump("Modem::clock(), m_buffer", m_buffer, m_length); if (m_rspState != RESP_START) m_rspState = RESP_START; break; @@ -1222,7 +1222,7 @@ void Modem::clearDMRFrame1() buffer[1U] = 3U; buffer[2U] = CMD_DMR_CLEAR1; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearDMRFrame1()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearDMRFrame1(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1238,7 +1238,7 @@ void Modem::clearDMRFrame2() buffer[1U] = 3U; buffer[2U] = CMD_DMR_CLEAR2; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearDMRFrame2()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearDMRFrame2(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1254,7 +1254,7 @@ void Modem::clearP25Frame() buffer[1U] = 3U; buffer[2U] = CMD_P25_CLEAR; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearP25Data()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearP25Data(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1270,7 +1270,7 @@ void Modem::clearNXDNFrame() buffer[1U] = 3U; buffer[2U] = CMD_NXDN_CLEAR; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::clearNXDNFrame()] Written", buffer, 3U); + Utils::dump(1U, "Modem::clearNXDNFrame(), Written", buffer, 3U); #endif write(buffer, 3U); Thread::sleep(5); // 5ms delay @@ -1382,7 +1382,7 @@ bool Modem::writeDMRFrame1(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeDMRFrame1(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "[Modem::writeDMRFrame1()] Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeDMRFrame1(), Attempted Data", data, length); return false; } @@ -1401,7 +1401,7 @@ bool Modem::writeDMRFrame1(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeDMRFrame1()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeDMRFrame1()] Immediate TX DMR Data 1", buffer + 3U, length - 1U); + Utils::dump(1U, "Modem::writeDMRFrame1(), Immediate TX DMR Data 1", buffer + 3U, length - 1U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1436,7 +1436,7 @@ bool Modem::writeDMRFrame2(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeDMRFrame2(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "Modem::writeDMRFrame2(); Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeDMRFrame2(), Attempted Data", data, length); return false; } @@ -1455,7 +1455,7 @@ bool Modem::writeDMRFrame2(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeDMRFrame2()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeDMRFrame2()] Immediate TX DMR Data 2", buffer + 3U, length - 1U); + Utils::dump(1U, "Modem::writeDMRFrame2(), Immediate TX DMR Data 2", buffer + 3U, length - 1U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1493,7 +1493,7 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeP25Frame(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "[Modem::writeP25Frame()] Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeP25Frame(), Attempted Data", data, length); return false; } @@ -1520,7 +1520,7 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeP25Frame()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeP25Frame()] Immediate TX P25 Data", buffer + 3U, length - 3U); + Utils::dump(1U, "Modem::writeP25Frame(), Immediate TX P25 Data", buffer + 3U, length - 3U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1555,7 +1555,7 @@ bool Modem::writeNXDNFrame(const uint8_t* data, uint32_t length) return false; if (length > MAX_LENGTH) { LogError(LOG_MODEM, "Modem::writeNXDNFrame(); request data to write >%u?, len = %u", MAX_LENGTH, length); - Utils::dump(1U, "[Modem::writeNXDNFrame()] Attmpted Data", data, length); + Utils::dump(1U, "Modem::writeNXDNFrame(), Attempted Data", data, length); return false; } @@ -1574,7 +1574,7 @@ bool Modem::writeNXDNFrame(const uint8_t* data, uint32_t length) if (m_debug) LogDebugEx(LOG_MODEM, "Modem::writeNXDNFrame()", "immediate write (len %u)", length); if (m_trace) - Utils::dump(1U, "[Modem::writeNXDNFrame()] Immediate TX NXDN Data", buffer + 3U, length - 1U); + Utils::dump(1U, "Modem::writeNXDNFrame(), Immediate TX NXDN Data", buffer + 3U, length - 1U); int ret = write(buffer, len); if (ret != int(len)) { @@ -1612,7 +1612,7 @@ bool Modem::writeDMRStart(bool tx) buffer[2U] = CMD_DMR_START; buffer[3U] = tx ? 0x01U : 0x00U; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::writeDMRStart()] Written", buffer, 4U); + Utils::dump(1U, "Modem::writeDMRStart(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } @@ -1643,7 +1643,7 @@ bool Modem::writeDMRShortLC(const uint8_t* lc) buffer[10U] = lc[7U]; buffer[11U] = lc[8U]; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::writeDMRShortLC()] Written", buffer, 12U); + Utils::dump(1U, "Modem::writeDMRShortLC(), Written", buffer, 12U); #endif return write(buffer, 12U) == 12; } @@ -1664,7 +1664,7 @@ bool Modem::writeDMRAbort(uint32_t slotNo) buffer[2U] = CMD_DMR_ABORT; buffer[3U] = slotNo; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::writeDMRAbort()] Written", buffer, 4U); + Utils::dump(1U, "Modem::writeDMRAbort(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } @@ -1688,7 +1688,7 @@ bool Modem::setDMRIgnoreCACH_AT(uint8_t slotNo) // are we on a protocol version 3 firmware? if (m_protoVer >= 3U) { #if DEBUG_MODEM - Utils::dump(1U, "[Modem::setDMRIgnoreCACH_AT()] Written", buffer, 4U); + Utils::dump(1U, "Modem::setDMRIgnoreCACH_AT(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } else { @@ -1733,7 +1733,7 @@ bool Modem::setState(DVM_STATE state) buffer[2U] = CMD_SET_MODE; buffer[3U] = state; #if DEBUG_MODEM - Utils::dump(1U, "[Modem::setState()] Written", buffer, 4U); + Utils::dump(1U, "Modem::setState(), Written", buffer, 4U); #endif return write(buffer, 4U) == 4; } @@ -2160,7 +2160,7 @@ bool Modem::readFlash() if (resp == RTM_OK && m_buffer[2U] == CMD_FLSH_READ) { uint8_t len = m_buffer[1U]; if (m_debug) { - Utils::dump(1U, "Modem Flash Contents", m_buffer + 3U, len - 3U); + Utils::dump(1U, "Modem::readFlash(), Modem Flash Contents", m_buffer + 3U, len - 3U); } if (len == 249U) { @@ -2361,7 +2361,7 @@ void Modem::printDebug(const uint8_t* buffer, uint16_t len) ::memset(data, 0x00U, 255U); ::memcpy(data, buffer, len); - Utils::dump(1U, "Modem::printDebug() DSP_FW_API Debug Dump", data, len); + Utils::dump(1U, "Modem::printDebug(), DSP_FW_API Debug Dump", data, len); } } @@ -2502,7 +2502,7 @@ RESP_TYPE_DVM Modem::getResponse() } if (m_respTrace) - Utils::dump(1U, "[Modem::getResponse()] Buffer", m_buffer, m_length); + Utils::dump(1U, "Modem::getResponse(), Buffer", m_buffer, m_length); } m_rspState = RESP_START; diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 3b5b9bbac..5baf0d4de 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -16,6 +16,7 @@ #include "common/p25/dfsi/LC.h" #include "common/p25/dfsi/frames/Frames.h" #include "common/p25/lc/LC.h" +#include "common/p25/lc/tdulc/TDULCFactory.h" #include "common/p25/lc/tsbk/TSBKFactory.h" #include "common/p25/NID.h" #include "common/p25/P25Utils.h" @@ -197,9 +198,9 @@ void ModemV24::clock(uint32_t ms) if (m_useTIAFormat) convertToAirTIA(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); else - convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + convertToAirV24(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); if (m_trace) - Utils::dump(1U, "ModemV24::clock() RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); + Utils::dump(1U, "ModemV24::clock(), RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; @@ -375,7 +376,7 @@ void ModemV24::clock(uint32_t ms) default: LogWarning(LOG_MODEM, "Unknown message, type = %02X", m_buffer[2U]); - Utils::dump("Buffer dump", m_buffer, m_length); + Utils::dump("ModemV24::clock(), m_buffer", m_buffer, m_length); if (m_rspState != RESP_START) m_rspState = RESP_START; break; @@ -447,7 +448,7 @@ int ModemV24::write(const uint8_t* data, uint32_t length) if (m_useTIAFormat) convertFromAirTIA(buffer, length); else - convertFromAir(buffer, length); + convertFromAirV24(buffer, length); return length; } else { return Modem::write(data, length); @@ -545,7 +546,7 @@ void ModemV24::storeConvertedRx(const uint8_t* buffer, uint32_t length) storedLen[1U] = length & 0xFFU; m_rxP25Queue.addData(storedLen, 2U); - //Utils::dump("Storing converted RX data", buffer, length); + // Utils::dump("ModemV24::storeConvertedRx(), Storing Converted Rx Data", buffer, length); m_rxP25Queue.addData(buffer, length); } @@ -575,7 +576,7 @@ void ModemV24::create_TDU(uint8_t* buffer) /* Internal helper to convert from V.24/DFSI to TIA-102 air interface. */ -void ModemV24::convertToAir(const uint8_t *data, uint32_t length) +void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) { assert(data != nullptr); assert(length > 0U); @@ -588,7 +589,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) - Utils::dump("V24 RX data from board", dfsiData, length - 1U); + Utils::dump("ModemV24::convertToAirV24(), V.24 RX Data From Modem", dfsiData, length - 1U); DFSIFrameType::E frameType = (DFSIFrameType::E)dfsiData[0U]; m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -598,18 +599,18 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::MOT_START_STOP: { MotStartOfStream start = MotStartOfStream(dfsiData); - if (start.getStartStop() == StartStopFlag::START) { + if (start.getParam1() == DSFI_MOT_ICW_PARM_PAYLOAD) { m_rxCall->resetCallData(); m_rxCallInProgress = true; if (m_debug) { - LogDebug(LOG_MODEM, "V24 RX, ICW START, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } } else { if (m_rxCallInProgress) { m_rxCall->resetCallData(); m_rxCallInProgress = false; if (m_debug) { - LogDebug(LOG_MODEM, "V24 RX, ICW STOP, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } // generate a TDU create_TDU(buffer); @@ -621,32 +622,40 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::MOT_VHDR_1: { - MotVoiceHeader1 vhdr1 = MotVoiceHeader1(dfsiData); + MotStartOfStream start = MotStartOfStream(dfsiData); // copy to call data VHDR1 - ::memset(m_rxCall->VHDR1, 0x00U, MotVoiceHeader1::HCW_LENGTH); - ::memcpy(m_rxCall->VHDR1, vhdr1.header, MotVoiceHeader1::HCW_LENGTH); + ::memset(m_rxCall->VHDR1, 0x00U, DFSI_MOT_VHDR_1_LEN); + ::memcpy(m_rxCall->VHDR1, dfsiData + DFSI_MOT_START_LEN, DFSI_MOT_VHDR_1_LEN - DFSI_MOT_START_LEN); + + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24(), V.24 RX, VHDR1", m_rxCall->VHDR1, DFSI_MOT_VHDR_1_LEN); } break; case DFSIFrameType::MOT_VHDR_2: { - MotVoiceHeader2 vhdr2 = MotVoiceHeader2(dfsiData); + MotStartOfStream start = MotStartOfStream(dfsiData); // copy to call data VHDR2 - ::memset(m_rxCall->VHDR2, 0x00U, MotVoiceHeader2::HCW_LENGTH); - ::memcpy(m_rxCall->VHDR2, vhdr2.header, MotVoiceHeader2::HCW_LENGTH); + ::memset(m_rxCall->VHDR2, 0x00U, DFSI_MOT_VHDR_2_LEN); + ::memcpy(m_rxCall->VHDR2, dfsiData + 1U, DFSI_MOT_VHDR_2_LEN); + + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24(), V.24 RX, VHDR2", m_rxCall->VHDR2, DFSI_MOT_VHDR_2_LEN); // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; ::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN); - ::memcpy(raw, m_rxCall->VHDR1, 8U); - ::memcpy(raw + 8U, m_rxCall->VHDR1 + 9U, 8U); - ::memcpy(raw + 16U, m_rxCall->VHDR1 + 18U, 2U); + ::memcpy(raw + 0U, m_rxCall->VHDR1, 8U); + ::memcpy(raw + 8U, m_rxCall->VHDR1 + 9U, 8U); + ::memcpy(raw + 16U, m_rxCall->VHDR1 + 18U, 2U); + + ::memcpy(raw + 18U, m_rxCall->VHDR2, 8U); + ::memcpy(raw + 26U, m_rxCall->VHDR2 + 9U, 8U); + ::memcpy(raw + 34U, m_rxCall->VHDR2 + 18U, 2U); - ::memcpy(raw + 18U, m_rxCall->VHDR2, 8U); - ::memcpy(raw + 26U, m_rxCall->VHDR2 + 9U, 8U); - ::memcpy(raw + 34U, m_rxCall->VHDR2 + 18U, 2U); + // Utils::dump("ModemV24::convertToAirV24(), V.24 RX VHDR, raw", raw, DFSI_VHDR_RAW_LEN); // buffer for decoded VHDR data uint8_t vhdr[DFSI_VHDR_LEN]; @@ -655,18 +664,30 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) Utils::hex2Bin(raw[i], vhdr, offset); + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24() V.24 RX VHDR, VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); + // 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 { + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirV24(), V.24 RX VHDR, VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); + // 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"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX VHDR late entry, resetting call data"); + } + + uint32_t dstId = GET_UINT16(vhdr, 13U); + if (m_rxCallInProgress && dstId == 0U) { + LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); + break; } ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); @@ -674,7 +695,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); - m_rxCall->dstId = GET_UINT16(vhdr, 13U); + m_rxCall->dstId = dstId; if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); @@ -714,81 +735,144 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU1_VOICE1: { MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + + m_rxCall->errors = 0U; + + // process start of stream ICW for the voice call + uint8_t* icw = svf.startOfStream->getICW(); + uint8_t payloadType = MotStreamPayload::VOICE; + uint8_t rssi = 20U; + + if (icw != nullptr) { + for (uint8_t i = 0U; i < 6U; i += 2U) { + uint8_t param = icw[i]; + uint8_t value = icw[i + 1U]; + + switch (param) { + case DSFI_MOT_ICW_PARM_PAYLOAD: + payloadType = value; + break; + case DFSI_MOT_ICW_PARM_RSSI1: + rssi = value; + break; + case DFSI_MOT_ICW_PARM_RSSI2: + // don't do anything with this RSSI + break; + case DFSI_MOT_ICW_TX_ADDRESS: + case DFSI_MOT_ICW_RX_ADDRESS: + // don't do anything with this ICW + break; + default: + LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); + break; + } + } + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, Start of Voice LDU1, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); + } + + if (svf.fullRateVoice->getTotalErrors() > 0U) { + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + m_rxCall->errors += svf.fullRateVoice->getTotalErrors(); + } + m_rxCall->n++; } break; case DFSIFrameType::LDU2_VOICE10: { MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); + + m_rxCall->errors = 0U; + + // process start of stream ICW for the voice call + uint8_t* icw = svf.startOfStream->getICW(); + uint8_t payloadType = MotStreamPayload::VOICE; + uint8_t rssi = 20U; + + if (icw != nullptr) { + for (uint8_t i = 0U; i < 6U; i += 2U) { + uint8_t param = icw[i]; + uint8_t value = icw[i + 1U]; + + switch (param) { + case DSFI_MOT_ICW_PARM_PAYLOAD: + payloadType = value; + break; + case DFSI_MOT_ICW_PARM_RSSI1: + rssi = value; + break; + case DFSI_MOT_ICW_PARM_RSSI2: + // don't do anything with this RSSI + break; + case DFSI_MOT_ICW_TX_ADDRESS: + case DFSI_MOT_ICW_RX_ADDRESS: + // don't do anything with this ICW + break; + default: + LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); + break; + } + } + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, Start of Voice LDU2, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); + } + + if (svf.fullRateVoice->getTotalErrors() > 0U) { + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); + m_rxCall->errors += svf.fullRateVoice->getTotalErrors(); + } + m_rxCall->n++; } break; - case DFSIFrameType::PDU: + case DFSIFrameType::MOT_TDULC: { - // bryanb: this is gonna be a complete clusterfuck... - MotPDUFrame pf = MotPDUFrame(dfsiData); - data::DataHeader dataHeader = data::DataHeader(); - if (!dataHeader.decode(pf.pduHeaderData, true)) { - LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode PDU FEC"); + MotTDULCFrame tf = MotTDULCFrame(dfsiData); + lc::tdulc::LC_TDULC_RAW tdulc = lc::tdulc::LC_TDULC_RAW(); + if (!tdulc.decode(tf.tdulcData, true)) { + LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode TDULC FEC"); } else { - uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; - uint32_t offset = P25_PREAMBLE_LENGTH_BITS; - - DECLARE_UINT8_ARRAY(data, (bitLength / 8U) + 1U); + uint8_t buffer[P25_TDULC_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES + 2U); - uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); - - if (blocksToFollow > 0U) { - uint32_t dataOffset = MotPDUFrame::LENGTH + 1U; - - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - data::DataBlock dataBlock = data::DataBlock(); - dataBlock.setFormat(dataHeader); - dataBlock.setSerialNo(i); - dataBlock.setData(dfsiData + dataOffset); - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataBlock.encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES) + 1U; - } - } - - 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::encodeByLength(data, buffer + 2U, bitLength); - uint32_t newByteLength = newBitLength / 8U; - if ((newBitLength % 8U) > 0U) - newByteLength++; + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID - m_nid->encode(buffer + 2U, DUID::PDU); + m_nid->encode(buffer + 2U, DUID::TDULC); + + // regenerate TDULC Data + tdulc.encode(buffer + 2U); // add status bits - P25Utils::addStatusBits(buffer + 2U, newBitLength, false, false); - P25Utils::addIdleStatusBits(buffer + 2U, newBitLength); + P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES, false, true); + P25Utils::addIdleStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES); P25Utils::setStatusBitsStartIdle(buffer + 2U); - storeConvertedRx(buffer, P25_PDU_FRAME_LENGTH_BYTES + 2U); + storeConvertedRx(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U); } } break; - case DFSIFrameType::TSBK: + case DFSIFrameType::MOT_PDU_SINGLE: + break; + + case DFSIFrameType::MOT_TSBK: { MotTSBKFrame tf = MotTSBKFrame(dfsiData); lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); @@ -827,8 +911,14 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) MotFullRateVoice voice = MotFullRateVoice(dfsiData); if (m_debug) { - LogDebugEx(LOG_MODEM, "ModemV24::convertToAir()", "Full Rate Voice, frameType = %x, source = %u", voice.getFrameType(), voice.getSource()); - Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); + LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "Full Rate Voice, frameType = $%02X, errors = %u, busy = %u", voice.getFrameType(), voice.getTotalErrors(), voice.getBusy()); + Utils::dump(1U, "ModemV24::converToAirV24(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + + if (voice.getTotalErrors() > 0U) { + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + m_rxCall->errors += voice.getTotalErrors(); } switch (frameType) { @@ -844,6 +934,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) m_rxCall->lco = voice.additionalData[0U]; m_rxCall->mfId = voice.additionalData[1U]; m_rxCall->serviceOptions = voice.additionalData[2U]; + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); } @@ -854,6 +949,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->dstId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); } @@ -864,6 +964,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->srcId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); } @@ -872,16 +977,40 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU1_VOICE6: { ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC6 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE7: { ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC7 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE8: { ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC8 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE9: @@ -895,6 +1024,7 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) } } break; + case DFSIFrameType::LDU2_VOICE11: { ::memcpy(m_rxCall->netLDU2 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); @@ -905,6 +1035,12 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); } @@ -915,6 +1051,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); } @@ -925,6 +1066,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); } @@ -936,6 +1082,11 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) if (voice.additionalData != nullptr) { m_rxCall->algoId = voice.additionalData[0U]; m_rxCall->kId = GET_UINT16(voice.additionalData, 1U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); } @@ -944,11 +1095,27 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) case DFSIFrameType::LDU2_VOICE16: { ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC16 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE17: { ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "V.24/DFSI VC17 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE18: @@ -975,6 +1142,24 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) // encode LDU1 if ready if (m_rxCall->n == 9U) { + // decode RS (24,12,13) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode241213(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI LDU1, failed to decode RS (24,12,13) FEC"); + } + } + catch (...) { + Utils::dump(2U, "Modem, V.24 LDU1 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", V.24, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; + } + lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); @@ -986,15 +1171,15 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) 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; + rsBuffer[0U] = m_rxCall->LDULC[0U]; + rsBuffer[1U] = m_rxCall->LDULC[1U]; + rsBuffer[2U] = m_rxCall->LDULC[2U]; + rsBuffer[3U] = m_rxCall->LDULC[3U]; + rsBuffer[4U] = m_rxCall->LDULC[4U]; + rsBuffer[5U] = m_rxCall->LDULC[5U]; + rsBuffer[6U] = m_rxCall->LDULC[6U]; + rsBuffer[7U] = m_rxCall->LDULC[7U]; + rsBuffer[8U] = m_rxCall->LDULC[8U]; // combine bytes into ulong64_t (8 byte) value ulong64_t rsValue = 0U; @@ -1054,6 +1239,24 @@ void ModemV24::convertToAir(const uint8_t *data, uint32_t length) // encode LDU2 if ready if (m_rxCall->n == 18U) { + // decode RS (24,16,9) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode24169(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "V.24/DFSI LDU2, failed to decode RS (24,16,9) FEC"); + } + } + catch (...) { + Utils::dump(2U, "Modem, V.24 LDU2 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", V.24, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; + } + lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); @@ -1112,7 +1315,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) - Utils::dump("DFSI RX data from UDP", dfsiData, length - 1U); + Utils::dump("ModemV24::converToAirTIA(), DFSI RX Data From UDP", dfsiData, length - 1U); ControlOctet ctrl = ControlOctet(); ctrl.decode(dfsiData); @@ -1184,7 +1387,10 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memset(m_rxCall->VHDR1, 0x00U, 18U); ::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U); - dataOffs += 19U; // 18 Golay + Block Type Marker + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX, VHDR1", m_rxCall->VHDR1, 18U); + + dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker } break; case BlockType::VOICE_HEADER_P2: @@ -1193,7 +1399,10 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memset(m_rxCall->VHDR2, 0x00U, 18U); ::memcpy(m_rxCall->VHDR2, dfsiData + dataOffs + 1U, 18U); - dataOffs += 19U; // 18 Golay + Block Type Marker + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX, VHDR2", m_rxCall->VHDR1, 18U); + + dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; @@ -1215,12 +1424,18 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) Utils::hex2Bin(raw[i], vhdr, offset); } + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX VHDR, VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); + // 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 { + if (m_debug && m_trace) + Utils::dump("ModemV24::convertToAirTIA(), DFSI RX VHDR, VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); + // late entry? if (!m_rxCallInProgress) { m_rxCallInProgress = true; @@ -1229,12 +1444,18 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } + uint32_t dstId = GET_UINT16(vhdr, 13U); + if (m_rxCallInProgress && dstId == 0U) { + LogWarning(LOG_MODEM, "TIA/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); + break; + } + ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); - m_rxCall->dstId = GET_UINT16(vhdr, 13U); + m_rxCall->dstId = dstId; if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); @@ -1265,7 +1486,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) } } catch (...) { - LogError(LOG_MODEM, "V.24/DFSI RX traffic got exception while trying to decode RS data for VHDR"); + LogError(LOG_MODEM, "TIA/DFSI RX traffic got exception while trying to decode RS data for VHDR"); } } break; @@ -1277,8 +1498,14 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) voice.decode(dfsiData + dataOffs); if (m_debug) { - LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Full Rate Voice, frameType = %x, busy = %u, lostFrame = %u, muteFrame = %u, superFrameCnt = %u", voice.getFrameType(), voice.getBusy(), voice.getLostFrame(), voice.getMuteFrame(), voice.getSuperframeCnt(), voice.getTotalErrors()); - Utils::dump(1U, "Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); + LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Full Rate Voice, frameType = $%02X, busy = %u, lostFrame = %u, muteFrame = %u, superFrameCnt = %u, errors = %u", voice.getFrameType(), voice.getBusy(), voice.getLostFrame(), voice.getMuteFrame(), voice.getSuperframeCnt(), voice.getTotalErrors()); + Utils::dump(1U, "ModemV24::convertToAirTIA(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); + } + + if (voice.getTotalErrors() > 0U) { + if (m_debug) + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "TIA/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); + m_rxCall->errors += voice.getTotalErrors(); } dataOffs += voice.getLength(); @@ -1287,6 +1514,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) switch (frameType) { case DFSIFrameType::LDU1_VOICE1: { + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; @@ -1302,8 +1530,13 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->lco = voice.additionalData[0U]; m_rxCall->mfId = voice.additionalData[1U]; m_rxCall->serviceOptions = voice.additionalData[2U]; + + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC3 traffic missing metadata"); } } break; @@ -1312,8 +1545,14 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->dstId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC4 traffic missing metadata"); } } break; @@ -1322,24 +1561,54 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->srcId = GET_UINT24(voice.additionalData, 0U); + + // copy LDU1 LC bytes into LDU LC buffer + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC5 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE6: { ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "TIA/DFSI VC6 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE7: { ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "TIA/DFSI VC7 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE8: { ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU1 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "TIA/DFSI VC8 traffic missing metadata"); + } } break; case DFSIFrameType::LDU1_VOICE9: @@ -1349,13 +1618,14 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC9 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC9 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE10: { + ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; @@ -1369,8 +1639,13 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[0U] = voice.additionalData[0U]; + m_rxCall->LDULC[1U] = voice.additionalData[1U]; + m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC12 traffic missing metadata"); } } break; @@ -1379,8 +1654,13 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[3U] = voice.additionalData[0U]; + m_rxCall->LDULC[4U] = voice.additionalData[1U]; + m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC13 traffic missing metadata"); } } break; @@ -1389,8 +1669,13 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[6U] = voice.additionalData[0U]; + m_rxCall->LDULC[7U] = voice.additionalData[1U]; + m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC14 traffic missing metadata"); } } break; @@ -1400,19 +1685,40 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) if (voice.additionalData != nullptr) { m_rxCall->algoId = voice.additionalData[0U]; m_rxCall->kId = GET_UINT16(voice.additionalData, 1U); + + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[9U] = voice.additionalData[0U]; + m_rxCall->LDULC[10U] = voice.additionalData[1U]; + m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC15 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE16: { ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[12U] = voice.additionalData[0U]; + m_rxCall->LDULC[13U] = voice.additionalData[1U]; + m_rxCall->LDULC[14U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "TIA/DFSI VC16 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE17: { ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); + if (voice.additionalData != nullptr) { + // copy LDU2 LC bytes into LDU LC buffer + m_rxCall->LDULC[15U] = voice.additionalData[0U]; + m_rxCall->LDULC[16U] = voice.additionalData[1U]; + m_rxCall->LDULC[17U] = voice.additionalData[2U]; + } else { + LogWarning(LOG_MODEM, "TIA/DFSI VC17 traffic missing metadata"); + } } break; case DFSIFrameType::LDU2_VOICE18: @@ -1422,7 +1728,7 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { - LogWarning(LOG_MODEM, "V.24/DFSI VC18 traffic missing metadata"); + LogWarning(LOG_MODEM, "TIA/DFSI VC18 traffic missing metadata"); } } break; @@ -1447,6 +1753,24 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) // encode LDU1 if ready if (m_rxCall->n == 9U) { + // decode RS (24,12,13) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode241213(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "TIA/DFSI LDU1, failed to decode RS (24,12,13) FEC"); + } + } + catch (...) { + Utils::dump(2U, "Modem, TIA LDU1, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", TIA, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; + } + lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); @@ -1458,15 +1782,15 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) 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; + rsBuffer[0U] = m_rxCall->LDULC[0U]; + rsBuffer[1U] = m_rxCall->LDULC[1U]; + rsBuffer[2U] = m_rxCall->LDULC[2U]; + rsBuffer[3U] = m_rxCall->LDULC[3U]; + rsBuffer[4U] = m_rxCall->LDULC[4U]; + rsBuffer[5U] = m_rxCall->LDULC[5U]; + rsBuffer[6U] = m_rxCall->LDULC[6U]; + rsBuffer[7U] = m_rxCall->LDULC[7U]; + rsBuffer[8U] = m_rxCall->LDULC[8U]; // combine bytes into ulong64_t (8 byte) value ulong64_t rsValue = 0U; @@ -1526,6 +1850,24 @@ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) // encode LDU2 if ready if (m_rxCall->n == 18U) { + // decode RS (24,16,9) FEC + // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now + // we'll just log the error + try { + bool ret = m_rs.decode24169(m_rxCall->LDULC); + if (!ret) { + LogError(LOG_MODEM, "TIA/DFSI LDU2, failed to decode RS (24,16,9) FEC"); + } + } + catch (...) { + Utils::dump(2U, "Modem, TIA LDU2, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); + } + + if (m_rxCall->errors > 0U) { + LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", TIA, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); + m_rxCall->errors = 0U; + } + lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); @@ -1579,7 +1921,7 @@ void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType if (m_debug) LogDebugEx(LOG_MODEM, "ModemV24::queueP25Frame()", "msgType = $%02X", msgType); if (m_trace) - Utils::dump(1U, "[ModemV24::queueP25Frame()] data", data, len); + 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(); @@ -1652,23 +1994,24 @@ void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType /* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ -void ModemV24::startOfStream(const p25::lc::LC& control) +void ModemV24::startOfStreamV24(const p25::lc::LC& control) { m_txCallInProgress = true; MotStartOfStream start = MotStartOfStream(); - start.setStartStop(StartStopFlag::START); - start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::VOICE); // create buffer for bytes and encode - uint8_t startBuf[start.LENGTH]; - ::memset(startBuf, 0x00U, start.LENGTH); + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH); + Utils::dump(1U, "ModemV24::startOfStreamV24(), StartOfStream", startBuf, DFSI_MOT_START_LEN); - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); @@ -1695,58 +2038,58 @@ void ModemV24::startOfStream(const p25::lc::LC& control) } // 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); + uint8_t vhdr1Buf[DFSI_MOT_VHDR_1_LEN]; + ::memset(vhdr1Buf, 0x00U, DFSI_MOT_VHDR_1_LEN); + start.encode(vhdr1Buf); - ::memcpy(vhdr1.header, raw, 8U); - ::memcpy(vhdr1.header + 9U, raw + 8U, 8U); - ::memcpy(vhdr1.header + 18U, raw + 16U, 2U); + ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 0U, raw, 8U); + ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 9U, raw + 8U, 8U); + ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 18U, raw + 16U, 2U); - // encode VHDR1 and send - uint8_t vhdr1Buf[vhdr1.LENGTH]; - ::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH); - vhdr1.encode(vhdr1Buf); + vhdr1Buf[0U] = DFSIFrameType::MOT_VHDR_1; + vhdr1Buf[20U + DFSI_MOT_START_LEN] = DFSI_BUSY_BITS_INBOUND; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH); + Utils::dump(1U, "ModemV24::startOfStreamV24(), VoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); - queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE); + queueP25Frame(vhdr1Buf, DFSI_MOT_VHDR_1_LEN, 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); + uint8_t vhdr2Buf[DFSI_MOT_VHDR_2_LEN]; + ::memset(vhdr2Buf, 0x00U, DFSI_MOT_VHDR_2_LEN); + + ::memcpy(vhdr2Buf + 1U, raw + 18U, 8U); + ::memcpy(vhdr2Buf + 10U, raw + 26U, 8U); + ::memcpy(vhdr2Buf + 19U, raw + 34U, 2U); - // encode VHDR2 and send - uint8_t vhdr2Buf[vhdr2.LENGTH]; - ::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH); - vhdr2.encode(vhdr2Buf); + vhdr2Buf[0U] = DFSIFrameType::MOT_VHDR_2; + vhdr2Buf[21U] = DFSI_BUSY_BITS_INBOUND; + // send VHDR2 if (m_trace) - Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH); + Utils::dump(1U, "ModemV24::startOfStreamV24(), VoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); - queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE); + queueP25Frame(vhdr2Buf, DFSI_MOT_VHDR_2_LEN, STT_NON_IMBE); } /* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ -void ModemV24::endOfStream() +void ModemV24::endOfStreamV24() { MotStartOfStream end = MotStartOfStream(); - end.setStartStop(StartStopFlag::STOP); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::VOICE); // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); if (m_trace) - Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH); + Utils::dump(1U, "ModemV24::endOfStreamV24(), StartOfStream", endBuf, DFSI_MOT_START_LEN); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); m_txCallInProgress = false; } @@ -1770,7 +2113,7 @@ uint16_t ModemV24::generateNID(DUID::E duid) void ModemV24::startOfStreamTIA(const p25::lc::LC& control) { m_txCallInProgress = true; - m_superFrameCnt = 0U; + m_superFrameCnt = 1U; p25::lc::LC lc = p25::lc::LC(control); @@ -1792,12 +2135,12 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // generate start of stream StartOfStream start = StartOfStream(); - start.setNID(generateNID()); + start.setNID(generateNID(DUID::HDU)); start.encode(buffer + 2U); length += StartOfStream::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamTIA() StartOfStream", buffer, length); + Utils::dump(1U, "ModemV24::startOfStreamTIA(), StartOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); @@ -1820,7 +2163,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // convert the binary bytes to hex bytes uint8_t raw[DFSI_VHDR_RAW_LEN]; - uint32_t offset = 0; + uint32_t offset = 0U; for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { raw[i] = Utils::bin2Hex(vhdr, offset); } @@ -1849,13 +2192,13 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // prepare VHDR1 buffer[3U] = DFSIFrameType::MOT_VHDR_1; ::memcpy(buffer + 4U, raw, 18U); - length += 19U; // 18 Golay + Block Type Marker + length += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker start.encode(buffer + length); length += StartOfStream::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader1", buffer, length); + Utils::dump(1U, "ModemV24::startOfStreamTIA(), VoiceHeader1", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); @@ -1878,13 +2221,13 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) // prepare VHDR2 buffer[3U] = DFSIFrameType::MOT_VHDR_2; ::memcpy(buffer + 4U, raw + 18U, 18U); - length += 19U; // 18 Golay + Block Type Marker + length += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker start.encode(buffer + length); length += StartOfStream::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::startOfStreamTIA() VoiceHeader2", buffer, length); + Utils::dump(1U, "ModemV24::startOfStreamTIA(), VoiceHeader2", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); } @@ -1893,7 +2236,7 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) void ModemV24::endOfStreamTIA() { - m_superFrameCnt = 0U; + m_superFrameCnt = 1U; uint16_t length = 0U; uint8_t buffer[2U]; @@ -1912,7 +2255,7 @@ void ModemV24::endOfStreamTIA() length += BlockHeader::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::endOfStreamTIA() EndOfStream", buffer, length); + Utils::dump(1U, "ModemV24::endOfStreamTIA(), EndOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); @@ -1940,20 +2283,20 @@ void ModemV24::ackStartOfStreamTIA() length += BlockHeader::LENGTH; if (m_trace) - Utils::dump(1U, "ModemV24::ackStartOfStreamTIA() Ack StartOfStream", buffer, length); + Utils::dump(1U, "ModemV24::ackStartOfStreamTIA(), Ack StartOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE_NO_JITTER); } /* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ -void ModemV24::convertFromAir(uint8_t* data, uint32_t length) +void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) { assert(data != nullptr); assert(length > 0U); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() data", data, length); + Utils::dump(1U, "ModemV24::convertFromAirV24(), data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); @@ -1976,7 +2319,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); } - startOfStream(lc); + startOfStreamV24(lc); } break; case DUID::LDU1: @@ -1991,9 +2334,9 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) // late entry? if (!m_txCallInProgress) { - startOfStream(lc); + startOfStreamV24(lc); if (m_debug) - LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "V.24 TX VHDR late entry, resetting TX call data"); } // generate audio @@ -2032,11 +2375,71 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) break; case DUID::TDU: - case DUID::TDULC: if (m_txCallInProgress) - endOfStream(); + endOfStreamV24(); break; + case DUID::TDULC: + { + if (m_txCallInProgress) + endOfStreamV24(); + + lc::tdulc::LC_TDULC_RAW tdulc = lc::tdulc::LC_TDULC_RAW(); + if (!tdulc.decode(data + 2U)) { + LogWarning(LOG_MODEM, P25_TDULC_STR ", undecodable LC"); + return; + } + + MotStartOfStream start = MotStartOfStream(); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::TERM_LC); + + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), TDULC MotStartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + + MotTDULCFrame tf = MotTDULCFrame(); + tf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + tf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + tf.startOfStream->setArgument1(MotStreamPayload::TERM_LC); + delete[] tf.tdulcData; + + tf.tdulcData = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; + ::memset(tf.tdulcData, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); + ::memcpy(tf.tdulcData, tdulc.getDecodedRaw(), P25_TDULC_PAYLOAD_LENGTH_BYTES); + + // create buffer and encode + uint8_t tdulcBuf[DFSI_MOT_TDULC_LEN]; + ::memset(tdulcBuf, 0x00U, DFSI_MOT_TDULC_LEN); + tf.encode(tdulcBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotTDULCFrame", tdulcBuf, DFSI_MOT_TDULC_LEN); + + queueP25Frame(tdulcBuf, DFSI_MOT_TDULC_LEN, STT_NON_IMBE_NO_JITTER); + + MotStartOfStream end = MotStartOfStream(); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::TERM_LC); + + // create buffer and encode + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); + end.encode(endBuf); + + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + } + break; + case DUID::PDU: break; @@ -2048,22 +2451,25 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) return; } - MotStartOfStream startOfStream = MotStartOfStream(); - startOfStream.setStartStop(StartStopFlag::START); - startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - startOfStream.setStreamType(StreamTypeFlag::TSBK); + MotStartOfStream start = MotStartOfStream(); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::TSBK); - // create buffer and encode - uint8_t startBuf[MotStartOfStream::LENGTH]; - ::memset(startBuf, 0x00U, MotStartOfStream::LENGTH); - startOfStream.encode(startBuf); + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); - queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), TSBK StartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, 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); + tf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + tf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + tf.startOfStream->setArgument1(MotStreamPayload::TSBK); delete[] tf.tsbkData; tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; @@ -2071,27 +2477,27 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); // create buffer and encode - uint8_t tsbkBuf[MotTSBKFrame::LENGTH]; - ::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH); + uint8_t tsbkBuf[DFSI_MOT_TSBK_LEN]; + ::memset(tsbkBuf, 0x00U, DFSI_MOT_TSBK_LEN); tf.encode(tsbkBuf); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH); + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); - queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(tsbkBuf, DFSI_MOT_TSBK_LEN, STT_NON_IMBE_NO_JITTER); - MotStartOfStream endOfStream = MotStartOfStream(); - endOfStream.setStartStop(StartStopFlag::STOP); - endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED); - endOfStream.setStreamType(StreamTypeFlag::TSBK); + MotStartOfStream end = MotStartOfStream(); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::TSBK); // create buffer and encode - uint8_t endBuf[MotStartOfStream::LENGTH]; - ::memset(endBuf, 0x00U, MotStartOfStream::LENGTH); - endOfStream.encode(endBuf); + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); + end.encode(endBuf); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); - queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); } break; @@ -2104,7 +2510,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); if (duid == DUID::LDU1) { - rs[0U] = lc.getLCO(); // LCO + rs[0U] = lc.getLCO(); // LCO // split ulong64_t (8 byte) value into bytes rs[1U] = (uint8_t)((lc.getRS() >> 56) & 0xFFU); @@ -2139,6 +2545,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) uint8_t* buffer = nullptr; uint16_t bufferSize = 0; MotFullRateVoice voice = MotFullRateVoice(); + voice.setBusy(DFSI_BUSY_BITS_INBOUND); switch (n) { case 0: // VOICE1/10 @@ -2146,11 +2553,10 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) 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.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + svf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + svf.startOfStream->setArgument1(MotStreamPayload::VOICE); 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); @@ -2163,7 +2569,6 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) 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; @@ -2271,7 +2676,7 @@ void ModemV24::convertFromAir(uint8_t* data, uint32_t length) if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAir() Encoded V.24 Voice Frame Data", buffer, bufferSize); + Utils::dump("ModemV24::convertFromAirV24(), Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); @@ -2289,7 +2694,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) assert(length > 0U); if (m_trace) - Utils::dump(1U, "ModemV24::convertFromAirTIA() data", data, length); + Utils::dump(1U, "ModemV24::convertFromAirTIA(), data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); @@ -2329,7 +2734,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) if (!m_txCallInProgress) { startOfStreamTIA(lc); if (m_debug) - LogDebug(LOG_MODEM, "V24 TX VHDR late entry, resetting TX call data"); + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirTIA()", "DFSI TX VHDR late entry, resetting TX call data"); } // generate audio @@ -2423,6 +2828,8 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) uint8_t* buffer = nullptr; uint16_t bufferSize = 0; FullRateVoice voice = FullRateVoice(); + voice.setBusy(DFSI_BUSY_BITS_BUSY); + voice.setSuperframeCnt(m_superFrameCnt); switch (n) { case 0: // VOICE1/10 @@ -2536,7 +2943,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) // generate control octet ControlOctet ctrl = ControlOctet(); - ctrl.setBlockHeaderCnt(1U); + ctrl.setBlockHeaderCnt(2U); ctrl.encode(buffer); bufferSize += ControlOctet::LENGTH; @@ -2545,15 +2952,23 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) hdr.setBlockType(BlockType::FULL_RATE_VOICE); hdr.encode(buffer + 1U); bufferSize += BlockHeader::LENGTH; + hdr.setBlockType(BlockType::START_OF_STREAM); + hdr.encode(buffer + 2U); + bufferSize += BlockHeader::LENGTH; - voice.setSuperframeCnt(m_superFrameCnt); - voice.setBusy(1U); // Inbound Channel is Busy + // encode voice frame voice.encode(buffer + bufferSize); bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type + // generate start of stream and encode + StartOfStream start = StartOfStream(); + start.setNID(generateNID(duid)); + start.encode(buffer + bufferSize); + bufferSize += StartOfStream::LENGTH; + if (buffer != nullptr) { if (m_trace) { - Utils::dump("ModemV24::convertFromAirTIA() 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); @@ -2564,7 +2979,7 @@ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) // 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) + if (m_superFrameCnt == 3U) m_superFrameCnt = 0U; else m_superFrameCnt++; diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index f4069f91a..8c05bb19f 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -18,8 +18,6 @@ #include "Defines.h" #include "common/edac/RS634717.h" -#include "common/p25/dfsi/frames/MotVoiceHeader1.h" -#include "common/p25/dfsi/frames/MotVoiceHeader2.h" #include "common/p25/lc/LC.h" #include "common/p25/Audio.h" #include "common/p25/NID.h" @@ -80,11 +78,13 @@ namespace modem VHDR1(nullptr), VHDR2(nullptr), netLDU1(nullptr), - netLDU2(nullptr) + netLDU2(nullptr), + errors(0U) { MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; - VHDR1 = new uint8_t[p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH]; - VHDR2 = new uint8_t[p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH]; + VHDR1 = new uint8_t[P25DFSIDEF::DFSI_TIA_VHDR_LEN]; + VHDR2 = new uint8_t[P25DFSIDEF::DFSI_TIA_VHDR_LEN]; + LDULC = new uint8_t[P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES]; netLDU1 = new uint8_t[9U * 25U]; netLDU2 = new uint8_t[9U * 25U]; @@ -105,6 +105,8 @@ namespace modem delete[] VHDR1; if (VHDR2 != nullptr) delete[] VHDR2; + if (LDULC != nullptr) + delete[] LDULC; if (netLDU1 != nullptr) delete[] netLDU1; if (netLDU2 != nullptr) @@ -132,9 +134,12 @@ namespace modem kId = 0U; if (VHDR1 != nullptr) - ::memset(VHDR1, 0x00U, p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH); + ::memset(VHDR1, 0x00U, P25DFSIDEF::DFSI_TIA_VHDR_LEN); if (VHDR2 != nullptr) - ::memset(VHDR2, 0x00U, p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH); + ::memset(VHDR2, 0x00U, P25DFSIDEF::DFSI_TIA_VHDR_LEN); + + if (LDULC != nullptr) + ::memset(LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); if (netLDU1 != nullptr) ::memset(netLDU1, 0x00U, 9U * 25U); @@ -143,6 +148,8 @@ namespace modem n = 0U; seqNo = 0U; + + errors = 0U; } public: @@ -200,6 +207,11 @@ namespace modem */ uint8_t* VHDR2; + /** + * @brief LDU LC. + */ + uint8_t* LDULC; + /** * @brief Sequence Number. */ @@ -217,6 +229,11 @@ namespace modem * @brief LDU2 Buffer. */ uint8_t* netLDU2; + + /** + * @brief Total errors for a given call sequence. + */ + uint32_t errors; /** @} */ }; @@ -226,6 +243,207 @@ namespace modem /** * @brief Implements the core interface to the V.24 modem hardware. + * \code{.unparsed} + * This is the format of the Motorola V.24 DFSI Voice Headers: + * + * Voice Header 1 (VHDR1): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * | Start of Strm | 1 - 9 + * +-+-+-+-+-+-+-+-+ + * |S0 | G0 | 10 + * +-+-+-+-+-+-+-+-+ + * |S1 | G1 | 11 + * +-+-+-+-+-+-+-+-+ + * |S2 | G2 | 12 + * +-+-+-+-+-+-+-+-+ + * |S3 | G3 | 13 + * +-+-+-+-+-+-+-+-+ + * |S4 | G4 | 14 + * +-+-+-+-+-+-+-+-+ + * |S5 | G5 | 15 + * +-+-+-+-+-+-+-+-+ + * |S6 | G6 | 16 + * +-+-+-+-+-+-+-+-+ + * |S7 | G7 | 17 + * +-+-+-+-+-+-+-+-+ + * | S7...S0 | 18 + * +-+-+-+-+-+-+-+-+ + * |S8 | G8 | 19 + * +-+-+-+-+-+-+-+-+ + * |S9 | G9 | 20 + * +-+-+-+-+-+-+-+-+ + * |S10| G10 | 21 + * +-+-+-+-+-+-+-+-+ + * |S11| G11 | 22 + * +-+-+-+-+-+-+-+-+ + * |S12| G12 | 23 + * +-+-+-+-+-+-+-+-+ + * |S13| G13 | 24 + * +-+-+-+-+-+-+-+-+ + * |S14| G14 | 25 + * +-+-+-+-+-+-+-+-+ + * |S15| G15 | 26 + * +-+-+-+-+-+-+-+-+ + * | S15...S8 | 27 + * +-+-+-+-+-+-+-+-+ + * |S16| G16 | 26 + * +-+-+-+-+-+-+-+-+ + * |S17| G17 | 28 + * +-+-+-+-+-+-+-+-+ + * |7|6| Rsvd |Bsy| 29 + * +-+-+-+-+-+-+-+-+ + * | Rsvd | 30 + * +-+-+-+-+-+-+-+-+ + * + * Voice Header 2 (VHDR2): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * |S18| G18 | 1 + * +-+-+-+-+-+-+-+-+ + * |S19| G19 | 2 + * +-+-+-+-+-+-+-+-+ + * |S20| G20 | 3 + * +-+-+-+-+-+-+-+-+ + * |S21| G21 | 4 + * +-+-+-+-+-+-+-+-+ + * |S22| G22 | 5 + * +-+-+-+-+-+-+-+-+ + * |S23| G23 | 6 + * +-+-+-+-+-+-+-+-+ + * |S24| G24 | 7 + * +-+-+-+-+-+-+-+-+ + * |S25| G25 | 8 + * +-+-+-+-+-+-+-+-+ + * | S25...S18 | 9 + * +-+-+-+-+-+-+-+-+ + * |S26| G26 | 10 + * +-+-+-+-+-+-+-+-+ + * |S27| G27 | 11 + * +-+-+-+-+-+-+-+-+ + * |S28| G28 | 12 + * +-+-+-+-+-+-+-+-+ + * |S29| G29 | 13 + * +-+-+-+-+-+-+-+-+ + * |S30| G30 | 14 + * +-+-+-+-+-+-+-+-+ + * |S31| G31 | 15 + * +-+-+-+-+-+-+-+-+ + * |S32| G32 | 16 + * +-+-+-+-+-+-+-+-+ + * |S33| G33 | 17 + * +-+-+-+-+-+-+-+-+ + * | S33...S26 | 18 + * +-+-+-+-+-+-+-+-+ + * |S34| G34 | 19 + * +-+-+-+-+-+-+-+-+ + * |S35| G35 | 20 + * +-+-+-+-+-+-+-+-+ + * |5|4| Rsvd |Bsy| 21 + * +-+-+-+-+-+-+-+-+ + * + * This is the format of the TIA-102.BAHA DFSI Voice Headers: + * + * TIA-102.BAHA Voice Header 1 (VHDR1): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * |S0 | G0 | 1 + * +-+-+-+-+-+-+-+-+ + * |S1 | G1 | 2 + * +-+-+-+-+-+-+-+-+ + * |S2 | G2 | 3 + * +-+-+-+-+-+-+-+-+ + * |S3 | G3 | 4 + * +-+-+-+-+-+-+-+-+ + * |S4 | G4 | 5 + * +-+-+-+-+-+-+-+-+ + * |S5 | G5 | 6 + * +-+-+-+-+-+-+-+-+ + * |S6 | G6 | 7 + * +-+-+-+-+-+-+-+-+ + * |S7 | G7 | 8 + * +-+-+-+-+-+-+-+-+ + * |S8 | G8 | 9 + * +-+-+-+-+-+-+-+-+ + * |S9 | G9 | 10 + * +-+-+-+-+-+-+-+-+ + * |S10| G10 | 11 + * +-+-+-+-+-+-+-+-+ + * |S11| G11 | 12 + * +-+-+-+-+-+-+-+-+ + * |S12| G12 | 13 + * +-+-+-+-+-+-+-+-+ + * |S13| G13 | 14 + * +-+-+-+-+-+-+-+-+ + * |S14| G14 | 15 + * +-+-+-+-+-+-+-+-+ + * |S15| G15 | 16 + * +-+-+-+-+-+-+-+-+ + * |S16| G16 | 17 + * +-+-+-+-+-+-+-+-+ + * |S17| G17 | 18 + * +-+-+-+-+-+-+-+-+ + * | Rsvd |7|6| 19 + * +-+-+-+-+-+-+-+-+ + * | S15...S0 | 20 + * +-+-+-+-+-+-+-+-+ + * | S15...S0 | 21 + * +-+-+-+-+-+-+-+-+ + * + * TIA-102.BAHA Voice Header 2 (VHDR2): + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | FT | 0 + * +-+-+-+-+-+-+-+-+ + * |S18| G18 | 1 + * +-+-+-+-+-+-+-+-+ + * |S19| G19 | 2 + * +-+-+-+-+-+-+-+-+ + * |S20| G20 | 3 + * +-+-+-+-+-+-+-+-+ + * |S21| G21 | 4 + * +-+-+-+-+-+-+-+-+ + * |S22| G22 | 5 + * +-+-+-+-+-+-+-+-+ + * |S23| G23 | 6 + * +-+-+-+-+-+-+-+-+ + * |S24| G24 | 7 + * +-+-+-+-+-+-+-+-+ + * |S25| G25 | 8 + * +-+-+-+-+-+-+-+-+ + * |S26| G26 | 9 + * +-+-+-+-+-+-+-+-+ + * |S27| G27 | 10 + * +-+-+-+-+-+-+-+-+ + * |S28| G28 | 11 + * +-+-+-+-+-+-+-+-+ + * |S29| G29 | 12 + * +-+-+-+-+-+-+-+-+ + * |S30| G30 | 13 + * +-+-+-+-+-+-+-+-+ + * |S31| G31 | 14 + * +-+-+-+-+-+-+-+-+ + * |S32| G32 | 15 + * +-+-+-+-+-+-+-+-+ + * |S33| G33 | 16 + * +-+-+-+-+-+-+-+-+ + * |S34| G34 | 17 + * +-+-+-+-+-+-+-+-+ + * |S35| G35 | 18 + * +-+-+-+-+-+-+-+-+ + * | Rsvd |5|4| 19 + * +-+-+-+-+-+-+-+-+ + * | S33...S18 | 20 + * +-+-+-+-+-+-+-+-+ + * | S33...S18 | 21 + * +-+-+-+-+-+-+-+-+ + * \endcode * @ingroup modem */ class HOST_SW_API ModemV24 : public Modem { @@ -349,7 +567,7 @@ namespace modem * @param data Buffer containing data to convert. * @param length Length of buffer. */ - void convertToAir(const uint8_t *data, uint32_t length); + void convertToAirV24(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. @@ -369,11 +587,11 @@ namespace modem * @brief Send a start of stream sequence (HDU, etc) to the connected serial V24 device. * @param[in] control Instance of p25::lc::LC containing link control data. */ - void startOfStream(const p25::lc::LC& control); + void startOfStreamV24(const p25::lc::LC& control); /** * @brief Send an end of stream sequence (TDU, etc) to the connected serial V24 device. */ - void endOfStream(); + void endOfStreamV24(); /** * @brief Helper to generate the NID value. @@ -401,7 +619,7 @@ namespace modem * @param data Buffer containing data to convert. * @param length Length of buffer. */ - void convertFromAir(uint8_t* data, uint32_t length); + void convertFromAirV24(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. diff --git a/src/host/modem/port/ISerialPort.h b/src/host/modem/port/ISerialPort.h index 3407f827c..256b94523 100644 --- a/src/host/modem/port/ISerialPort.h +++ b/src/host/modem/port/ISerialPort.h @@ -62,6 +62,17 @@ namespace modem * @brief Closes the connection to the port. */ virtual void close() = 0; + + /** + * @brief Sets RTS signal high (asserts RTS). + * @returns bool True, if RTS was set successfully, otherwise false. + */ + virtual bool setRTS() = 0; + /** + * @brief Sets RTS signal low (clears RTS). + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + virtual bool clearRTS() = 0; }; } // namespace port } // namespace modem diff --git a/src/host/modem/port/PseudoPTYPort.cpp b/src/host/modem/port/PseudoPTYPort.cpp index cf17279fb..0ba8e05ba 100644 --- a/src/host/modem/port/PseudoPTYPort.cpp +++ b/src/host/modem/port/PseudoPTYPort.cpp @@ -50,7 +50,7 @@ bool PseudoPTYPort::open() char slave[300]; int result = ::openpty(&m_fd, &slavefd, slave, NULL, NULL); if (result < 0) { - ::LogError(LOG_HOST, "Cannot open the pseudo tty - errno : %d", errno); + ::LogError(LOG_HOST, "Cannot open the pseudo tty, errno: %d (%s)", errno, strerror(errno)); return false; } diff --git a/src/host/modem/port/UARTPort.cpp b/src/host/modem/port/UARTPort.cpp index cbd4592ec..83b736682 100644 --- a/src/host/modem/port/UARTPort.cpp +++ b/src/host/modem/port/UARTPort.cpp @@ -214,7 +214,7 @@ int UARTPort::read(uint8_t* buffer, uint32_t length) } if (n < 0) { - ::LogError(LOG_HOST, "Error from select(), errno=%d", errno); + ::LogError(LOG_HOST, "Error from select(), errno: %d (%s)", errno, strerror(errno)); return -1; } @@ -222,7 +222,7 @@ int UARTPort::read(uint8_t* buffer, uint32_t length) ssize_t len = ::read(m_fd, buffer + offset, length - offset); if (len < 0) { if (errno != EAGAIN) { - ::LogError(LOG_HOST, "Error from read(), errno=%d", errno); + ::LogError(LOG_HOST, "Error from read(), errno: %d (%s)", errno, strerror(errno)); return -1; } } @@ -277,7 +277,7 @@ int UARTPort::write(const uint8_t* buffer, uint32_t length) n = ::write(m_fd, buffer + ptr, length - ptr); if (n < 0) { if (errno != EAGAIN) { - ::LogError(LOG_HOST, "Error returned from write(), errno=%d (%s)", errno, strerror(errno)); + ::LogError(LOG_HOST, "Error returned from write(), errno: %d (%s)", errno, strerror(errno)); return -1; } } @@ -544,3 +544,63 @@ bool UARTPort::setTermios() m_isOpen = true; return true; } + +/* Sets RTS signal high (asserts RTS). */ + +bool UARTPort::setRTS() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, SETRTS) == 0) { + ::LogError(LOG_HOST, "Cannot set RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set RTS for %s", m_device.c_str()); + return false; + } +#endif // defined(_WIN32) + + return true; +} + +/* Sets RTS signal low (clears RTS). */ + +bool UARTPort::clearRTS() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s", m_device.c_str()); + return false; + } +#endif // defined(_WIN32) + + return true; +} diff --git a/src/host/modem/port/UARTPort.h b/src/host/modem/port/UARTPort.h index b5a4a1a7d..c8411b97b 100644 --- a/src/host/modem/port/UARTPort.h +++ b/src/host/modem/port/UARTPort.h @@ -108,6 +108,17 @@ namespace modem */ void close() override; + /** + * @brief Sets RTS signal high (asserts RTS). + * @returns bool True, if RTS was set successfully, otherwise false. + */ + bool setRTS() override; + /** + * @brief Sets RTS signal low (clears RTS). + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + bool clearRTS() override; + #if defined(__APPLE__) /** * @brief Helper on Apple to set serial port to non-blocking. diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 2c2dfa4d6..2005c9d86 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -83,6 +83,7 @@ V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t mod m_tx(false), m_ctrlThreadPool(MAX_THREAD_CNT, "v24cc"), m_vcThreadPool(MAX_THREAD_CNT, "v24vc"), + m_tiaMode(true), m_debug(debug) { assert(peerId > 0U); @@ -362,7 +363,7 @@ void V24UDPPort::processCtrlNetwork() UInt8Array buffer = m_ctrlFrameQueue->read(length, address, addrLen); if (length > 0) { if (m_debug) - Utils::dump(1U, "FSC Control Network Message", buffer.get(), length); + Utils::dump(1U, "V24UDPPort::processCtrlNetwork(), FSC Control Network Message", buffer.get(), length); V24PacketRequest* req = new V24PacketRequest(); req->obj = this; @@ -518,11 +519,16 @@ void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) network->m_heartbeatInterval = connMessage->getHostHeartbeatPeriod(); if (network->m_heartbeatInterval > 30U) network->m_heartbeatInterval = 30U; + if (network->m_heartbeatInterval < 5U) + network->m_heartbeatInterval = 5U; + + // HACK: make sure the HB is always one second shorter then the requested value + network->m_heartbeatInterval--; uint16_t remoteCtrlPort = Socket::port(req->address); network->m_remoteCtrlAddr = req->address; network->m_remoteCtrlAddrLen = req->addrLen; - + LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u, hostHBInterval = %u", remoteCtrlPort, network->m_localPort, vcBasePort, connMessage->getHostHeartbeatPeriod()); // setup local RTP VC port (where we receive traffic) @@ -822,6 +828,9 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui } uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + length; + if (!m_tiaMode) + bufferLen += 8U; + uint8_t* buffer = new uint8_t[bufferLen]; ::memset(buffer, 0x00U, bufferLen); @@ -833,6 +842,11 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui header.setSequence(rtpSeq); header.setSSRC(ssrc); + if (!m_tiaMode) { + header.setExtension(true); + header.setPayloadType(DFSI_RTP_MOT_PAYLOAD_TYPE); + } + header.encode(buffer); if (streamId != 0U && timestamp == INVALID_TS && rtpSeq != RTP_END_OF_CALL_SEQ) { @@ -847,10 +861,26 @@ uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, ui LogDebugEx(LOG_NET, "V24UDPPort::generateMessage()", "RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq); } - ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, message, length); + if (!m_tiaMode) { + uint8_t extBuffer[8U]; + ::memset(extBuffer, 0x00U, 8U); + + extBuffer[0U] = 0x02U; + extBuffer[3U] = 0x01U; + + extBuffer[4U] = 0x7FU; // 127 + extBuffer[5U] = 0x00U; // 0 + extBuffer[6U] = 0x00U; // 0 + extBuffer[7U] = 0x01U; // 1 + + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, extBuffer, 8U); + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + 8U, message, length); + } else { + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, message, length); + } if (m_debug) - Utils::dump(1U, "[V24UDPPort::generateMessage()] Buffered Message", buffer, bufferLen); + Utils::dump(1U, "V24UDPPort::generateMessage(), Buffered Message", buffer, bufferLen); if (outBufferLen != nullptr) { *outBufferLen = bufferLen; diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 76788fca6..8cbea5212 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -87,6 +87,11 @@ namespace modem * @brief Helper to set and configure the heartbeat interval for FSC connections. */ void setHeartbeatInterval(uint32_t interval); + /** + * @brief Helper to set TIA mode. + * @param mode True to enable TIA mode, false to disable. + */ + void setTIAMode(bool mode) { m_tiaMode = mode; } /** * @brief Updates the timer by the passed number of milliseconds. @@ -185,6 +190,7 @@ namespace modem ThreadPool m_ctrlThreadPool; ThreadPool m_vcThreadPool; + bool m_tiaMode; bool m_debug; static std::mutex m_bufferMutex; diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index acccc1576..f82f2cae7 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -176,7 +176,7 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p delete[] in; if (m_debug) { - Utils::dump("REST Password Hash", m_passwordHash, 32U); + Utils::dump("RESTAPI::RESTAPI(), REST Password Hash", m_passwordHash, 32U); } #if defined(ENABLE_SSL) @@ -432,7 +432,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, co } if (m_debug) { - Utils::dump("Password Hash", passwordHash, 32U); + Utils::dump("RESTAPI::restAPI_PutAuth(), Password Hash", passwordHash, 32U); } // compare hashes @@ -1611,7 +1611,7 @@ void RESTAPI::restAPI_PutP25RawTSBK(const HTTPPayload& request, HTTPPayload& rep } if (m_debug) { - Utils::dump("Raw TSBK", tsbk, P25_TSBK_LENGTH_BYTES); + Utils::dump("RESTAPI::restAPI_PutP25RawTSBK(), Raw TSBK", tsbk, P25_TSBK_LENGTH_BYTES); } m_p25->control()->writeRF_TSDU_Raw(tsbk); diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index a8a0da208..5d4f21922 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -71,6 +71,8 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_enableControl(false), m_dedicatedControl(false), m_ignoreAffiliationCheck(false), + m_legacyGroupReg(false), + m_defaultNetIdleTalkgroup(0U), m_rfLastLICH(), m_rfLC(), m_netLC(), @@ -104,8 +106,8 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_interval(), m_frameLossCnt(0U), m_frameLossThreshold(DEFAULT_FRAME_LOSS_THRESHOLD), - m_ccFrameCnt(0U), m_ccSeq(0U), + m_ccIteration(0U), m_siteData(), m_rssiMapper(rssiMapper), m_rssi(0U), @@ -217,8 +219,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_ccDebug = control["debug"].as(false); m_ignoreAffiliationCheck = nxdnProtocol["ignoreAffiliationCheck"].as(false); + m_legacyGroupReg = nxdnProtocol["legacyGroupReg"].as(false); yaml::Node rfssConfig = systemConf["config"]; + m_defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + yaml::Node controlCh = rfssConfig["controlCh"]; m_notifyCC = controlCh["notifyEnable"].as(false); @@ -251,7 +256,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw uint8_t siteInfo1 = SiteInformation1::VOICE_CALL_SVC | SiteInformation1::DATA_CALL_SVC; uint8_t siteInfo2 = SiteInformation2::SHORT_DATA_CALL_SVC; if (m_enableControl) { - siteInfo1 |= SiteInformation1::LOC_REG_SVC;//| SiteInformation1::GRP_REG_SVC; + siteInfo1 |= SiteInformation1::LOC_REG_SVC; } /* @@ -323,10 +328,15 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Disable Grant Source ID Check: yes"); } if (m_supervisor) - LogMessage(LOG_DMR, "Host is configured to operate as a NXDN control channel, site controller mode."); + LogMessage(LOG_NXDN, "Host is configured to operate as a NXDN control channel, site controller mode."); + } + + if (m_defaultNetIdleTalkgroup != 0U) { + LogInfo(" Default Network Idle Talkgroup: $%04X", m_defaultNetIdleTalkgroup); } LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); + LogInfo(" Legacy Group Registration: %s", m_legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", m_notifyCC ? "yes" : "no"); LogInfo(" Verify Affiliation: %s", m_control->m_verifyAff ? "yes" : "no"); LogInfo(" Verify Registration: %s", m_control->m_verifyReg ? "yes" : "no"); @@ -917,9 +927,6 @@ void Control::addFrame(const uint8_t *data, bool net, bool imm) void Control::processNetwork() { - if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) - return; - uint32_t length = 0U; bool ret = false; UInt8Array buffer = m_network->readNXDN(ret, length); @@ -932,6 +939,12 @@ void Control::processNetwork() return; } + // don't process network frames if the RF modem isn't in a listening state + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { + m_network->resetNXDN(); + return; + } + // process network message header uint8_t messageType = buffer[4U]; @@ -977,17 +990,23 @@ void Control::processNetwork() if (valid) m_rfLastLICH = lich; - FuncChannelType::E usc = m_rfLastLICH.getFCT(); + RFChannelType::E rfct = m_rfLastLICH.getRFCT(); + FuncChannelType::E fct = m_rfLastLICH.getFCT(); ChOption::E option = m_rfLastLICH.getOption(); - // forward onto the specific processor for final processing and delivery - switch (usc) { - case FuncChannelType::USC_UDCH: - ret = m_data->processNetwork(option, lc, data.get(), frameLength); - break; - default: - ret = m_voice->processNetwork(usc, option, lc, data.get(), frameLength); - break; + if (rfct == RFChannelType::RCCH) { + m_control->processNetwork(fct, option, lc, data.get(), frameLength); + } + else if (rfct == RFChannelType::RTCH || rfct == RFChannelType::RDCH) { + // forward onto the specific processor for final processing and delivery + switch (fct) { + case FuncChannelType::USC_UDCH: + m_data->processNetwork(option, lc, data.get(), frameLength); + break; + default: + m_voice->processNetwork(fct, option, lc, data.get(), frameLength); + break; + } } } @@ -1252,10 +1271,6 @@ bool Control::writeRF_ControlData() if (!m_enableControl) return false; - if (m_ccFrameCnt == 254U) { - m_ccFrameCnt = 0U; - } - // don't add any frames if the queue is full uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; uint32_t space = m_txQueue.freeSpace(); @@ -1263,20 +1278,21 @@ bool Control::writeRF_ControlData() return false; } - const uint8_t maxSeq = m_control->m_bcchCnt + (m_control->m_ccchPagingCnt + m_control->m_ccchMultiCnt) * - m_control->m_rcchGroupingCnt * m_control->m_rcchIterateCnt; - if (m_ccSeq == maxSeq) { + if (m_ccIteration > m_control->m_rcchIterateCnt) { + m_ccIteration = 0U; m_ccSeq = 0U; } + const uint8_t maxSeq = (m_control->m_ccchPagingCnt + m_control->m_ccchMultiCnt) * m_control->m_rcchGroupingCnt; + if (m_ccSeq > maxSeq) { + m_ccSeq = 1U; + m_ccIteration++; + } + if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { - m_control->writeRF_ControlData(m_ccFrameCnt, m_ccSeq, true); + m_control->writeRF_ControlData(m_ccSeq, m_ccSeq == 0U); m_ccSeq++; - if (m_ccSeq == maxSeq) { - m_ccFrameCnt++; - } - return true; } diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index 883ed2c09..f65fa1dbe 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -278,6 +278,9 @@ namespace nxdn bool m_enableControl; bool m_dedicatedControl; bool m_ignoreAffiliationCheck; + bool m_legacyGroupReg; + + uint32_t m_defaultNetIdleTalkgroup; channel::LICH m_rfLastLICH; lc::RTCH m_rfLC; @@ -327,8 +330,8 @@ namespace nxdn uint8_t m_frameLossCnt; uint8_t m_frameLossThreshold; - uint8_t m_ccFrameCnt; uint8_t m_ccSeq; + uint8_t m_ccIteration; SiteData m_siteData; diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index d7c274a33..e4e1fb862 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -287,10 +287,12 @@ bool ControlSignaling::processNetwork(FuncChannelType::E fct, ChOption::E option ControlSignaling::ControlSignaling(Control* nxdn, bool debug, bool verbose) : m_nxdn(nxdn), m_bcchCnt(1U), - m_rcchGroupingCnt(1U), m_ccchPagingCnt(2U), m_ccchMultiCnt(2U), - m_rcchIterateCnt(2U), + m_rcchGroupingCnt(1U), + m_rcchIterateCnt(4U), + m_pgRCCHQueue((NXDN_FRAME_LENGTH_BYTES + 2U) * 16U, "RCCH Paging Frame"), + m_mpRCCHQueue((NXDN_FRAME_LENGTH_BYTES + 2U) * 16U, "RCCH Multipurpose Frame"), m_verifyAff(false), m_verifyReg(false), m_disableGrantSrcIdCheck(false), @@ -326,7 +328,7 @@ void ControlSignaling::writeNetwork(const uint8_t *data, uint32_t len) /* Helper to write a single-block RCCH packet. */ -void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool imm) +void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool paging) { if (!m_nxdn->m_enableControl) return; @@ -365,9 +367,10 @@ void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool imm) if (!noNetwork) writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); - if (m_nxdn->m_duplex) { - m_nxdn->addFrame(data, false, imm); - } + if (paging) + m_pgRCCHQueue.addData(data, NXDN_FRAME_LENGTH_BYTES + 2U); + else + m_mpRCCHQueue.addData(data, NXDN_FRAME_LENGTH_BYTES + 2U); } /* @@ -376,10 +379,8 @@ void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool imm) /* Helper to write control channel packet data. */ -void ControlSignaling::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS) +void ControlSignaling::writeRF_ControlData(uint8_t n, bool first) { - uint8_t i = 0U, seqCnt = 0U; - if (!m_nxdn->m_enableControl) return; @@ -400,26 +401,42 @@ void ControlSignaling::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adj return; } - do - { - if (m_debug) { - LogDebugEx(LOG_NXDN, "ControlSignaling::writeRF_ControlData()", "frameCnt = %u, seq = %u", frameCnt, n); - } + if (m_debug) { + LogDebugEx(LOG_NXDN, "ControlSignaling::writeRF_ControlData()", "seq = %u", n); + } - switch (n) - { - case 0: - writeRF_CC_Message_Site_Info(); - break; - default: - writeRF_CC_Message_Service_Info(); - break; + if (first) { + // transmit the BCCH frame + writeRF_CC_Message_Site_Info(); + } + else { + if (n > 0U && n <= m_ccchPagingCnt - 1U) { + // transmit the next paging frame + if (!m_pgRCCHQueue.isEmpty()) { + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + m_pgRCCHQueue.get(data, NXDN_FRAME_LENGTH_BYTES + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data); + } + } + else + writeRF_CC_Message_Service_Info(); } + else { + // transmit the next multipurpose frame + if (!m_mpRCCHQueue.isEmpty()) { + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + m_mpRCCHQueue.get(data, NXDN_FRAME_LENGTH_BYTES + 2U); - if (seqCnt > 0U) - n++; - i++; - } while (i <= seqCnt); + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data); + } + } + else + writeRF_CC_Message_Idle(); + } + } lc::RCCH::setVerbose(rcchVerbose); m_nxdn->m_debug = m_debug = controlDebug; @@ -433,7 +450,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin bool encryption = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag uint8_t priority = ((serviceOptions & 0xFFU) & 0x07U); // Priority - std::unique_ptr rcch = std::make_unique(); + std::unique_ptr rcch = std::make_unique(); // are we skipping checking? if (!skip) { @@ -559,11 +576,19 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin if (!net) { ::ActivityLog("NXDN", true, "group grant request from %u to TG %u", srcId, dstId); } + + rcch->setCallType(CallType::CONFERENCE); + rcch->setDuplex(false); + rcch->setTransmissionMode(TransmissionMode::MODE_4800); } else { if (!net) { ::ActivityLog("NXDN", true, "unit-to-unit grant request from %u to %u", srcId, dstId); } + + rcch->setCallType(CallType::INDIVIDUAL); + rcch->setDuplex(false); + rcch->setTransmissionMode(TransmissionMode::MODE_4800); } // callback RPC to permit the granted TG on the specified voice channel @@ -610,7 +635,6 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } - rcch->setMessageType(MessageType::RTCH_VCALL); rcch->setGrpVchNo(chNo); rcch->setGroup(grp); rcch->setSrcId(srcId); @@ -626,7 +650,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } // transmit group grant - writeRF_Message_Imm(rcch.get(), net); + writeRF_Message(rcch.get(), net, true); return true; } @@ -649,11 +673,11 @@ void ControlSignaling::writeRF_Message_Deny(uint32_t srcId, uint32_t dstId, uint rcch->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X, service = $%02X, srcId = %u, dstId = %u", - service, srcId, dstId); + LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", + reason, NXDNUtils::causeToString(reason).c_str(), service, srcId, dstId); } - writeRF_Message_Imm(rcch.get(), false); + writeRF_Message(rcch.get(), false); } /* Helper to write a group registration response packet. */ @@ -711,7 +735,7 @@ bool ControlSignaling::writeRF_Message_Grp_Reg_Rsp(uint32_t srcId, uint32_t dstI m_nxdn->m_network->announceGroupAffiliation(srcId, dstId); } - writeRF_Message_Imm(rcch.get(), false); + writeRF_Message(rcch.get(), false); return ret; } @@ -768,7 +792,7 @@ void ControlSignaling::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t dstId, rcch->setSrcId(srcId); rcch->setDstId(dstId); - writeRF_Message_Imm(rcch.get(), true); + writeRF_Message(rcch.get(), true); } /* Helper to write a CC SITE_INFO broadcast packet on the RF interface. */ @@ -861,3 +885,46 @@ void ControlSignaling::writeRF_CC_Message_Service_Info() m_nxdn->addFrame(data); } } + +/* Helper to write a CC IDLE broadcast packet on the RF interface. */ + +void ControlSignaling::writeRF_CC_Message_Idle() +{ + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); + + Sync::addNXDNSync(data + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(RFChannelType::RCCH); + lich.setFCT(FuncChannelType::CAC_OUTBOUND); + lich.setOption(ChOption::DATA_IDLE); + lich.setOutbound(true); + lich.encode(data + 2U); + + uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + std::unique_ptr rcch = std::make_unique(); + DEBUG_LOG_MSG(rcch->toString()); + rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS / 2U); + //rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS / 2U, NXDN_RCCH_LC_LENGTH_BITS / 2U); + + // generate the CAC + channel::CAC cac; + cac.setRAN(m_nxdn->m_ran); + cac.setStructure(ChStructure::SR_RCCH_SINGLE); + cac.setData(buffer); + cac.encode(data + 2U); + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + NXDNUtils::scrambler(data + 2U); + NXDNUtils::addPostBits(data + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data); + } +} diff --git a/src/host/nxdn/packet/ControlSignaling.h b/src/host/nxdn/packet/ControlSignaling.h index 67fec18a2..f66a295f2 100644 --- a/src/host/nxdn/packet/ControlSignaling.h +++ b/src/host/nxdn/packet/ControlSignaling.h @@ -74,11 +74,14 @@ namespace nxdn Control* m_nxdn; uint8_t m_bcchCnt; - uint8_t m_rcchGroupingCnt; uint8_t m_ccchPagingCnt; uint8_t m_ccchMultiCnt; + uint8_t m_rcchGroupingCnt; uint8_t m_rcchIterateCnt; + RingBuffer m_pgRCCHQueue; + RingBuffer m_mpRCCHQueue; + bool m_verifyAff; bool m_verifyReg; @@ -112,19 +115,13 @@ namespace nxdn ** Modem Frame Queuing */ - /** - * @brief Helper to write a immediate single-block RCCH packet. - * @param rcch RCCH to write to the modem. - * @param noNetwork Flag indicating not to write the TSBK to the network. - */ - void writeRF_Message_Imm(lc::RCCH *rcch, bool noNetwork) { writeRF_Message(rcch, noNetwork, true); } /** * @brief Helper to write a single-block RCCH packet. * @param rcch RCCH to write to the modem. - * @param noNetwork Flag indicating not to write the TSBK to the network. - * @param imm Flag indicating the TSBK should be written to the immediate queue. + * @param noNetwork Flag indicating not to write the RCCH to the network. + * @param paging Flag indicating the RCCH should be written to the paging queue. */ - void writeRF_Message(lc::RCCH* rcch, bool noNetwork, bool imm = false); + void writeRF_Message(lc::RCCH* rcch, bool noNetwork, bool paging = false); /* ** Control Signalling Logic @@ -132,11 +129,10 @@ namespace nxdn /** * @brief Helper to write control channel packet data. - * @param frameCnt Frame counter. * @param n - * @param adjSS Flag indicating whether or not adjacent site status should be broadcast. + * @param first */ - void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS); + void writeRF_ControlData(uint8_t n, bool first); /** * @brief Helper to write a grant packet. @@ -181,6 +177,10 @@ namespace nxdn * @brief Helper to write a CC SRV_INFO broadcast packet on the RF interface. */ void writeRF_CC_Message_Service_Info(); + /** + * @brief Helper to write a CC IDLE broadcast packet on the RF interface. + */ + void writeRF_CC_Message_Idle(); }; } // namespace packet } // namespace nxdn diff --git a/src/host/nxdn/packet/Data.cpp b/src/host/nxdn/packet/Data.cpp index fe3d63709..0d432a6ca 100644 --- a/src/host/nxdn/packet/Data.cpp +++ b/src/host/nxdn/packet/Data.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -24,114 +24,6 @@ using namespace nxdn::packet; #include -// --------------------------------------------------------------------------- -// Macros -// --------------------------------------------------------------------------- - -// Don't process RF frames if the network isn't in a idle state and the RF destination -// is the network destination and stop network frames from processing -- RF wants to -// transmit on a different talkgroup -#define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ - if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - \ - if (m_nxdn->m_netState != RS_NET_IDLE) { \ - if (m_nxdn->m_netLC.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, \ - m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, \ - m_nxdn->m_netLastDstId); \ - resetNet(); \ - } \ - } - -// Don't process network frames if the destination ID's don't match and the network TG hang -// timer is running, and don't process network frames if the RF modem isn't in a listening state -#define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ - if (m_nxdn->m_rfLastDstId != 0U) { \ - if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - resetNet(); \ - return false; \ - } \ - \ - if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - m_nxdn->m_rfTGHang.start(); \ - } \ - } \ - \ - if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ - if (_LAYER3.getSrcId() == srcId && _LAYER3.getDstId() == dstId) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ - srcId, dstId); \ - resetNet(); \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", _LAYER3.getDstId(), \ - dstId); \ - resetNet(); \ - return false; \ - } \ - } - -// Validate the source RID -#define VALID_SRCID(_SRC_ID, _DST_ID, _GROUP) \ - if (!acl::AccessControl::validateSrcId(_SRC_ID)) { \ - if (m_lastRejectId == 0U || m_lastRejectId != _SRC_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, srcId = %u", _SRC_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _SRC_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } - -// Validate the destination ID -#define VALID_DSTID(_SRC_ID, _DST_ID, _GROUP) \ - if (!_GROUP) { \ - if (!acl::AccessControl::validateSrcId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } \ - else { \ - if (!acl::AccessControl::validateTGId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, TGID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -183,13 +75,76 @@ bool Data::process(ChOption::E option, uint8_t* data, uint32_t len) if (type != MessageType::RTCH_DCALL_HDR) return false; - CHECK_TRAFFIC_COLLISION(srcId, dstId); + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_nxdn->m_netState != RS_NET_IDLE && dstId == m_nxdn->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return false; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup */ + if (m_nxdn->m_netState != RS_NET_IDLE) { + if (m_nxdn->m_netLC.getSrcId() == srcId && m_nxdn->m_netLastDstId == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return false; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, + m_nxdn->m_netLastDstId); + resetNet(); + } + } // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "RF data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF data rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } if (m_verbose) { LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", @@ -293,13 +248,71 @@ bool Data::processNetwork(ChOption::E option, lc::RTCH& netLC, uint8_t* data, ui if (type != MessageType::RTCH_DCALL_HDR) return false; - CHECK_NET_TRAFFIC_COLLISION(lc, srcId, dstId); + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_nxdn->m_rfLastDstId != 0U) { + if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + resetNet(); + return false; + } + + if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + m_nxdn->m_rfTGHang.start(); + } + } + + // don't process network frames if the RF modem isn't in a listening state + if (m_nxdn->m_rfState != RS_RF_LISTENING) { + if (lc.getSrcId() == srcId && lc.getDstId() == dstId) { \ + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", lc.getSrcId(), lc.getDstId(), + srcId, dstId); + resetNet(); + return false; + } + else { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", lc.getDstId(), + dstId); + resetNet(); + return false; + } + } // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "network data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + resetNet(); + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network data rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network data rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } if (m_verbose) { LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 7ae868249..755ca1c77 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -27,151 +27,6 @@ using namespace nxdn::packet; #include #include -// --------------------------------------------------------------------------- -// Macros -// --------------------------------------------------------------------------- - -// Don't process RF frames if the network isn't in a idle state and the RF destination -// is the network destination and stop network frames from processing -- RF wants to -// transmit on a different talkgroup -#define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ - if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - \ - if (m_nxdn->m_netState != RS_NET_IDLE) { \ - if (m_nxdn->m_netLC.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _SRC_ID, _DST_ID, \ - m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", _DST_ID, \ - m_nxdn->m_netLastDstId); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - } \ - \ - if (m_nxdn->m_enableControl && _DST_ID == m_nxdn->m_netLastDstId) { \ - if (m_nxdn->m_affiliations->isNetGranted(_DST_ID)) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _SRC_ID, _DST_ID, \ - m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); \ - resetRF(); \ - m_nxdn->m_rfState = RS_RF_LISTENING; \ - return false; \ - } \ - } \ - } - -// Don't process network frames if the destination ID's don't match and the network TG hang -// timer is running, and don't process network frames if the RF modem isn't in a listening state -#define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ - if (m_nxdn->m_rfLastDstId != 0U) { \ - if (m_nxdn->m_rfLastDstId != _DST_ID && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - resetNet(); \ - return false; \ - } \ - \ - if (m_nxdn->m_rfLastDstId == _DST_ID && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ - m_nxdn->m_rfTGHang.start(); \ - } \ - } \ - \ - if (m_nxdn->m_authoritative) { \ - if (m_nxdn->m_netLastDstId != 0U) { \ - if (m_nxdn->m_netLastDstId != _DST_ID && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { \ - return false; \ - } \ - \ - if (m_nxdn->m_netLastDstId == _DST_ID && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { \ - m_nxdn->m_netTGHang.start(); \ - } \ - } \ - \ - if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ - if (_LAYER3.getSrcId() == _SRC_ID && _LAYER3.getDstId() == _DST_ID) { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ - _SRC_ID, _DST_ID); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - return false; \ - } \ - else { \ - LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", _LAYER3.getDstId(), \ - _DST_ID); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - return false; \ - } \ - } \ - } \ - \ - if (!m_nxdn->m_authoritative && m_nxdn->m_permittedDstId != _DST_ID) { \ - if (!g_disableNonAuthoritativeLogging) \ - LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", _DST_ID); \ - resetNet(); \ - if (m_nxdn->m_network != nullptr) \ - m_nxdn->m_network->resetNXDN(); \ - return false; \ - } - -// Validate the source RID -#define VALID_SRCID(_SRC_ID, _DST_ID, _GROUP) \ - if (!acl::AccessControl::validateSrcId(_SRC_ID)) { \ - if (m_lastRejectId == 0U || m_lastRejectId != _SRC_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", _SRC_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _SRC_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } - -// Validate the destination ID -#define VALID_DSTID(_SRC_ID, _DST_ID, _GROUP) \ - if (!_GROUP) { \ - if (!acl::AccessControl::validateSrcId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } \ - else { \ - if (!acl::AccessControl::validateTGId(_DST_ID)) { \ - if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", _DST_ID); \ - ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ - m_lastRejectId = _DST_ID; \ - } \ - \ - m_nxdn->m_rfLastDstId = 0U; \ - m_nxdn->m_rfLastSrcId = 0U; \ - m_nxdn->m_rfTGHang.stop(); \ - m_nxdn->m_rfState = RS_RF_REJECTED; \ - return false; \ - } \ - } - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -251,17 +106,78 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u return false; } } else if (type == MessageType::RTCH_VCALL) { - CHECK_TRAFFIC_COLLISION(srcId, dstId); + if (checkRFTrafficCollision(srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } } else { return false; } + // are we auto-registering legacy radios to groups? + if (m_nxdn->m_legacyGroupReg && group) { + if (!m_nxdn->m_affiliations->isGroupAff(srcId, dstId)) { + // update dynamic unit registration table + if (!m_nxdn->m_affiliations->isUnitReg(srcId)) { + m_nxdn->m_affiliations->unitReg(srcId); + } + + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->announceUnitRegistration(srcId); + + // update dynamic affiliation table + m_nxdn->m_affiliations->groupAff(srcId, dstId); + + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->announceGroupAffiliation(srcId, dstId); + } + } + m_nxdn->m_rfTGHang.start(); m_nxdn->m_netTGHang.stop(); m_nxdn->m_rfLastDstId = lc.getDstId(); @@ -417,13 +333,55 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u bool group = m_nxdn->m_rfLC.getGroup(); bool encrypted = m_nxdn->m_rfLC.getEncrypted(); - CHECK_TRAFFIC_COLLISION(srcId, dstId); + if (checkRFTrafficCollision(srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_nxdn->m_rfLastDstId = 0U; + m_nxdn->m_rfLastSrcId = 0U; + m_nxdn->m_rfTGHang.stop(); + m_nxdn->m_rfState = RS_RF_REJECTED; + return false; + } + } m_nxdn->m_rfTGHang.start(); m_nxdn->m_netTGHang.stop(); @@ -704,13 +662,46 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& return false; } } else if (type == MessageType::RTCH_VCALL) { - CHECK_NET_TRAFFIC_COLLISION(lc, srcId, dstId); + if (checkNetTrafficCollision(lc, srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + resetNet(); + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } } else { return false; } @@ -848,13 +839,46 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& bool group = m_nxdn->m_netLC.getGroup(); bool encrypted = m_nxdn->m_netLC.getEncrypted(); - CHECK_NET_TRAFFIC_COLLISION(m_nxdn->m_netLC, srcId, dstId); + if (checkNetTrafficCollision(m_nxdn->m_netLC, srcId, dstId)) + return false; // validate source RID - VALID_SRCID(srcId, dstId, group); + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, srcId = %u", srcId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + resetNet(); + return false; + } // validate destination ID - VALID_DSTID(srcId, dstId, group); + if (!group) { + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, RID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } + else { + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL " denial, TGID rejection, dstId = %u", dstId); + ::ActivityLog("NXDN", true, "network voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + resetNet(); + return false; + } + } m_nxdn->m_netTGHang.start(); m_nxdn->m_netLastDstId = m_nxdn->m_netLC.getDstId(); @@ -1056,3 +1080,123 @@ void Voice::writeNetwork(const uint8_t *data, uint32_t len) m_nxdn->m_network->writeNXDN(m_nxdn->m_rfLC, data, len); } + +/* Helper to perform RF traffic collision checking. */ + +bool Voice::checkRFTrafficCollision(uint32_t srcId, uint32_t dstId) +{ + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_nxdn->m_netState != RS_NET_IDLE && dstId == m_nxdn->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return true; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup + if (m_nxdn->m_netState != RS_NET_IDLE) { + if (m_nxdn->m_netLC.getSrcId() == srcId && m_nxdn->m_netLastDstId == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return true; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, + m_nxdn->m_netLastDstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + } + + // is control is enabled, and the group was granted by network already ignore RF traffic + if (m_nxdn->m_enableControl && dstId == m_nxdn->m_netLastDstId) { + if (m_nxdn->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLastDstId); + resetRF(); + m_nxdn->m_rfState = RS_RF_LISTENING; + return true; + } + } + } + + return false; +} + +/* Helper to perform network traffic collision checking. */ + +bool Voice::checkNetTrafficCollision(lc::RTCH lc, uint32_t srcId, uint32_t dstId) +{ + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_nxdn->m_rfLastDstId != 0U) { + if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + resetNet(); + return true; + } + + if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { + m_nxdn->m_rfTGHang.start(); + } + } + + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired()) { + m_nxdn->m_rfTGHang.stop(); + } + + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_nxdn->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_nxdn->m_rfTGHang.isRunning()) { + if (m_nxdn->m_defaultNetIdleTalkgroup != dstId) { + return true; + } + } + + // perform authoritative network TG hangtimer and traffic preemption + if (m_nxdn->m_authoritative) { + if (m_nxdn->m_netLastDstId != 0U) { + if (m_nxdn->m_netLastDstId != dstId && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { + return true; + } + + if (m_nxdn->m_netLastDstId == dstId && (m_nxdn->m_netTGHang.isRunning() && !m_nxdn->m_netTGHang.hasExpired())) { + m_nxdn->m_netTGHang.start(); + } + } + + // don't process network frames if the RF modem isn't in a listening state + if (m_nxdn->m_rfState != RS_RF_LISTENING) { + if (lc.getSrcId() == srcId && lc.getDstId() == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", lc.getSrcId(), lc.getDstId(), + srcId, dstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + return true; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", lc.getDstId(), + dstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + return true; + } + } + } + + // don't process network frames if this modem isn't authoritative + if (!m_nxdn->m_authoritative && m_nxdn->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", dstId); + resetNet(); + if (m_nxdn->m_network != nullptr) + m_nxdn->m_network->resetNXDN(); + return true; + } + + return false; +} diff --git a/src/host/nxdn/packet/Voice.h b/src/host/nxdn/packet/Voice.h index 70091ff7a..a92d7e204 100644 --- a/src/host/nxdn/packet/Voice.h +++ b/src/host/nxdn/packet/Voice.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015-2020 Jonathan Naylor, G4KLX - * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -112,6 +112,22 @@ namespace nxdn * @param len Length of data frame. */ void writeNetwork(const uint8_t* data, uint32_t len); + + /** + * @brief Helper to perform RF traffic collision checking. + * @param srcId Source ID. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkRFTrafficCollision(uint32_t srcId, uint32_t dstId); + /** + * @brief Helper to perform network traffic collision checking. + * @param lc + * @param srcId Source ID. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkNetTrafficCollision(lc::RTCH lc, uint32_t srcId, uint32_t dstId); }; } // namespace packet } // namespace nxdn diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 6c8b775a0..edbefed48 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -65,6 +65,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_modem(modem), m_isModemDFSI(false), m_network(network), + m_ignorePDUCRC(false), m_inhibitUnauth(false), m_legacyGroupGrnt(true), m_legacyGroupReg(false), @@ -75,11 +76,14 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_ackTSBKRequests(true), m_disableNetworkGrant(false), m_disableNetworkHDU(false), - m_allowExplicitSourceId(true), + m_allowExplicitSourceId(false), m_convNetGrantDemand(false), m_sndcpSupport(false), m_ignoreAffiliationCheck(false), m_demandUnitRegForRefusedAff(true), + m_dfsiFDX(false), + m_forceAllowTG0(false), + m_defaultNetIdleTalkgroup(0U), m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), @@ -231,6 +235,12 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw yaml::Node systemConf = conf["system"]; yaml::Node p25Protocol = conf["protocols"]["p25"]; + if (m_isModemDFSI) { + yaml::Node modemConf = systemConf["modem"]; + yaml::Node dfsiConf = modemConf["dfsi"]; + m_dfsiFDX = dfsiConf["fullDuplex"].as(false); + } + m_supervisor = supervisor; m_tduPreambleCount = p25Protocol["tduPreambleCount"].as(8U); @@ -238,6 +248,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw yaml::Node rfssConfig = systemConf["config"]; m_control->m_patchSuperGroup = (uint32_t)::strtoul(rfssConfig["pSuperGroup"].as("FFFE").c_str(), NULL, 16); m_control->m_announcementGroup = (uint32_t)::strtoul(rfssConfig["announcementGroup"].as("FFFE").c_str(), NULL, 16); + m_defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); yaml::Node secureConfig = rfssConfig["secure"]; std::string key = secureConfig["key"].as(); @@ -267,6 +278,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw generateLLA_AM1_Parameters(); } + m_ignorePDUCRC = p25Protocol["ignoreDataCRC"].as(false); + m_inhibitUnauth = p25Protocol["inhibitUnauthorized"].as(false); m_legacyGroupGrnt = p25Protocol["legacyGroupGrnt"].as(true); m_legacyGroupReg = p25Protocol["legacyGroupReg"].as(false); @@ -328,7 +341,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_ccNotifyActiveTG = control["notifyActiveTG"].as(true); - m_allowExplicitSourceId = p25Protocol["allowExplicitSourceId"].as(true); + m_allowExplicitSourceId = p25Protocol["allowExplicitSourceId"].as(false); m_convNetGrantDemand = p25Protocol["convNetGrantDemand"].as(false); uint32_t ccBcstInterval = p25Protocol["control"]["interval"].as(300U); @@ -346,6 +359,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_notifyCC = false; } + m_forceAllowTG0 = p25Protocol["forceAllowTG0"].as(false); + if (m_forceAllowTG0) { + LogWarning(LOG_P25, "TGID 0 (P25 blackhole talkgroup) will be allowed. This is not recommended, and can cause undesired behavior, it is typically only needed by poorly behaved systems."); + } + /* ** Voice Silence and Frame Loss Thresholds */ @@ -491,8 +509,19 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Control Data Only: yes"); } + if (m_isModemDFSI && m_dfsiFDX) { + LogInfo(" DFSI Full Duplex: yes"); + } + + if (m_forceAllowTG0) { + LogInfo(" Force Allow TGID 0: yes"); + } + LogInfo(" Patch Super Group: $%04X", m_control->m_patchSuperGroup); LogInfo(" Announcement Group: $%04X", m_control->m_announcementGroup); + if (m_defaultNetIdleTalkgroup != 0U) { + LogInfo(" Default Network Idle Talkgroup: $%04X", m_defaultNetIdleTalkgroup); + } LogInfo(" Notify Control: %s", m_notifyCC ? "yes" : "no"); if (m_disableNetworkHDU) { @@ -519,6 +548,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" SNDCP Support: %s", m_sndcpSupport ? "yes" : "no"); + LogInfo(" Ignore Data PDU CRC Error: %s", m_ignorePDUCRC ? "yes" : "no"); LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); LogInfo(" No Status ACK: %s", m_control->m_noStatusAck ? "yes" : "no"); LogInfo(" No Message ACK: %s", m_control->m_noMessageAck ? "yes" : "no"); @@ -1354,9 +1384,6 @@ void Control::addFrame(const uint8_t* data, uint32_t length, bool net, bool imm) void Control::processNetwork() { - if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) - return; - uint32_t length = 0U; bool ret = false; UInt8Array buffer = m_network->readP25(ret, length); @@ -1369,10 +1396,18 @@ void Control::processNetwork() return; } - bool grantDemand = (buffer[14U] & 0x80U) == 0x80U; - bool grantDenial = (buffer[14U] & 0x40U) == 0x40U; - bool grantEncrypt = (buffer[14U] & 0x08U) == 0x08U; - bool unitToUnit = (buffer[14U] & 0x01U) == 0x01U; + if (m_netState != RS_NET_DATA) { + // don't process network frames if the RF modem isn't in a listening state + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { + m_network->resetP25(); + return; + } + } + + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; + bool grantEncrypt = (buffer[14U] & network::NET_CTRL_GRANT_ENCRYPT) == network::NET_CTRL_GRANT_ENCRYPT; + bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; // process network message header DUID::E duid = (DUID::E)buffer[22U]; @@ -1381,6 +1416,12 @@ void Control::processNetwork() // process raw P25 data bytes UInt8Array data; uint8_t frameLength = buffer[23U]; + + if (!m_network->validateP25FrameLength(frameLength, length, duid)) { + m_network->resetP25(); + return; + } + if (duid == DUID::PDU) { frameLength = length; data = std::unique_ptr(new uint8_t[length]); @@ -1473,7 +1514,7 @@ void Control::processNetwork() if (m_debug) { LogDebug(LOG_NET, "P25, HDU algId = $%02X, kId = $%02X", algId, kid); - Utils::dump(1U, "P25 HDU Network MI", mi, MI_LENGTH_BYTES); + Utils::dump(1U, "P25, HDU Network MI", mi, MI_LENGTH_BYTES); } control.setAlgId(algId); diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 013e2c13b..8ebc30385 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -294,6 +294,8 @@ namespace p25 bool m_isModemDFSI; network::Network* m_network; + bool m_ignorePDUCRC; + bool m_inhibitUnauth; bool m_legacyGroupGrnt; bool m_legacyGroupReg; @@ -311,6 +313,10 @@ namespace p25 bool m_sndcpSupport; bool m_ignoreAffiliationCheck; bool m_demandUnitRegForRefusedAff; + bool m_dfsiFDX; + bool m_forceAllowTG0; + + uint32_t m_defaultNetIdleTalkgroup; ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 699e314f0..7cd0971fc 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -649,7 +649,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetAuthRes(RES1); - // get challenge for our SU + // get challenge for our SU ulong64_t challenge = 0U; try { challenge = m_llaDemandTable.at(srcId); @@ -681,6 +681,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_siteData.sysId()); @@ -2298,6 +2299,11 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } + const uint32_t __ = 0x67558U; + if ((m_p25->m_siteData.netId() >> 8) == ((dstId >> 24) | (__ ^ 0x38258U) >> 7)) { + return false; + } + if (chNo > 0U) { ::lookups::VoiceChData voiceChData = m_p25->m_affiliations->rfCh()->getRFChData(chNo); @@ -2764,8 +2770,9 @@ void ControlSignaling::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t osp->setGroup(grp); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::denyRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } writeRF_TSDU_SBF_Imm(osp.get(), false); @@ -2854,6 +2861,20 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI if (m_p25->m_network != nullptr) m_p25->m_network->announceGroupAffiliation(srcId, dstId); + + // conventional registration or DVRS support? + if (m_p25->m_enableControl && !m_p25->m_dedicatedControl) { + // is the RF talkgroup hang timer running? + if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { + if (m_verbose) { + LogMessage(LOG_RF, "talkgroup hang has terminated, lastDstId = %u", m_p25->m_rfLastDstId); + } + m_p25->m_rfTGHang.stop(); + + m_p25->m_rfLastDstId = 0U; + m_p25->m_rfLastSrcId = 0U; + } + } } writeRF_TSDU_SBF_Imm(iosp.get(), noNet); @@ -2949,8 +2970,9 @@ void ControlSignaling::writeRF_TSDU_Queue(uint32_t srcId, uint32_t dstId, uint8_ osp->setGroup(grp); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, P25Utils::queueRsnToString(reason).c_str(), + osp->getSrcId(), osp->getDstId()); } writeRF_TSDU_SBF_Imm(osp.get(), false); diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index a15b0c501..017c2c478 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.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" @@ -96,7 +96,7 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfPduUserDataLength = 0U; } - //Utils::dump(1U, "Raw PDU ISP", data, len); + //Utils::dump(1U, "P25, Data::process(), Raw PDU ISP", data, len); uint32_t start = m_rfPDUCount * P25_PDU_FRAME_LENGTH_BITS; @@ -113,7 +113,7 @@ bool Data::process(uint8_t* data, uint32_t len) bool ret = m_rfDataHeader.decode(buffer); if (!ret) { LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); m_rfDataHeader.reset(); m_rfExtendedAddress = false; @@ -157,13 +157,6 @@ bool Data::process(uint8_t* data, uint32_t len) m_p25->m_rfState = m_prevRfState; return false; } - - // only send data blocks across the network, if we're not an AMBT, - // an RSP or a registration service - if ((m_rfDataHeader.getFormat() != PDUFormatType::AMBT) && - (m_rfDataHeader.getSAP() != PDUSAP::CONV_DATA_REG)) { - writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, false); - } } if (m_p25->m_rfState == RS_RF_DATA) { @@ -178,7 +171,7 @@ bool Data::process(uint8_t* data, uint32_t len) bool ret = m_rfDataHeader.decodeExtAddr(buffer); if (!ret) { LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "Unfixable PDU Data", m_rfPDU + offset, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", m_rfPDU + offset, P25_PDU_HEADER_LENGTH_BYTES); m_rfDataHeader.reset(); m_rfPDUCount = 0U; @@ -193,7 +186,6 @@ bool Data::process(uint8_t* data, uint32_t len) } m_rfExtendedAddress = true; - writeNetwork(1U, buffer, P25_PDU_FEC_LENGTH_BYTES, false); offset += P25_PDU_FEC_LENGTH_BITS; m_rfPDUCount++; @@ -262,16 +254,6 @@ bool Data::process(uint8_t* data, uint32_t len) m_rfData[i].getData(m_rfPduUserData + dataOffset); dataOffset += (m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; m_rfPduUserDataLength = dataOffset; - - // only send data blocks across the network, if we're not an AMBT, - // an RSP or a registration service - if ((m_rfDataHeader.getFormat() != PDUFormatType::AMBT) && - (m_rfDataHeader.getFormat() != PDUFormatType::RSP) && - (m_rfDataHeader.getSAP() != PDUSAP::CONV_DATA_REG)) { - uint32_t networkBlock = m_rfDataBlockCnt + 1U; - writeNetwork(networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, m_rfData[i].getLastBlock()); - } - m_rfDataBlockCnt++; } else { @@ -297,7 +279,7 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_dumpPDUData) { - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); } } @@ -308,12 +290,17 @@ bool Data::process(uint8_t* data, uint32_t len) bool crcRet = edac::CRC::checkCRC32(m_rfPduUserData, m_rfPduUserDataLength); if (!crcRet) { LogWarning(LOG_RF, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", m_rfDataHeader.getBlocksToFollow(), m_rfPduUserDataLength); - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); + if (!m_p25->m_ignorePDUCRC) { + uint8_t sap = (m_rfExtendedAddress) ? m_rfDataHeader.getEXSAP() : m_rfDataHeader.getSAP(); + if (sap != PDUSAP::TRUNK_CTRL) // ignore CRC errors on TSBK data + writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId(), + m_rfExtendedAddress, WUID_FNE); + } } } if (m_dumpPDUData && m_rfDataBlockCnt > 0U) { - Utils::dump(1U, "PDU Packet", m_rfPduUserData, m_rfPduUserDataLength); + Utils::dump(1U, "P25, PDU Packet", m_rfPduUserData, m_rfPduUserDataLength); } if (m_rfDataBlockCnt < blocksToFollow) { @@ -322,74 +309,75 @@ bool Data::process(uint8_t* data, uint32_t len) // did we receive a response header? if (m_rfDataHeader.getFormat() == PDUFormatType::RSP) { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - m_rfDataHeader.getFormat(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), - m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + m_rfDataHeader.getFormat(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), + m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); + + if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { + delete m_retryPDUData; + m_retryPDUData = nullptr; - if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u", + m_retryPDUBitLength = 0U; + m_retryCount = 0U; + } + } else { + if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { + switch (m_rfDataHeader.getResponseType()) { + case PDUAckType::NACK_ILLEGAL: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", + m_rfDataHeader.getLLId()); + break; + case PDUAckType::NACK_PACKET_CRC: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + break; + case PDUAckType::NACK_SEQ: + case PDUAckType::NACK_OUT_OF_SEQ: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + break; + case PDUAckType::NACK_UNDELIVERABLE: + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); + break; + + default: + break; + } + } else if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK_RETRY) { + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u", m_rfDataHeader.getLLId()); + + // really this is supposed to check the bit field in the included response + // and only return those bits -- but we're responding with the entire previous packet... if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { - delete m_retryPDUData; - m_retryPDUData = nullptr; + if (m_retryCount < MAX_PDU_RETRY_CNT) { + m_p25->writeRF_Preamble(); + writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true); + m_retryCount++; + } + else { + delete m_retryPDUData; + m_retryPDUData = nullptr; - m_retryPDUBitLength = 0U; - m_retryCount = 0U; - } - } else { - if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { - switch (m_rfDataHeader.getResponseType()) { - case PDUAckType::NACK_ILLEGAL: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", - m_rfDataHeader.getLLId()); - break; - case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u", - m_rfDataHeader.getLLId()); - break; - case PDUAckType::NACK_SEQ: - case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u", - m_rfDataHeader.getLLId()); - break; - case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u", - m_rfDataHeader.getLLId()); - break; - - default: - break; - } - } else if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK_RETRY) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u", - m_rfDataHeader.getLLId()); - - // really this is supposed to check the bit field in the included response - // and only return those bits -- but we're responding with the entire previous packet... - 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, true); - m_retryCount++; - } - else { - delete m_retryPDUData; - m_retryPDUData = nullptr; - - m_retryPDUBitLength = 0U; - m_retryCount = 0U; - - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", - m_rfDataHeader.getLLId()); - - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); - } + m_retryPDUBitLength = 0U; + m_retryCount = 0U; + + LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", + m_rfDataHeader.getLLId()); + + writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); } } } } + // rewrite the response to the network + writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, true); + // only repeat the PDU locally if the packet isn't for the FNE if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { writeRF_PDU_Ack_Response(m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), @@ -422,6 +410,7 @@ bool Data::process(uint8_t* data, uint32_t len) } } + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on } break; @@ -433,6 +422,7 @@ bool Data::process(uint8_t* data, uint32_t len) } processSNDCPControl(m_rfPduUserData); + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); } break; case PDUSAP::CONV_DATA_REG: @@ -443,6 +433,7 @@ bool Data::process(uint8_t* data, uint32_t len) } processConvDataReg(m_rfPduUserData); + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); } break; case PDUSAP::UNENC_KMM: @@ -452,6 +443,8 @@ bool Data::process(uint8_t* data, uint32_t len) LogMessage(LOG_RF, P25_PDU_STR ", KMM (Key Management Message), blocksToFollow = %u", m_rfDataHeader.getBlocksToFollow()); } + + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); } break; case PDUSAP::TRUNK_CTRL: @@ -465,10 +458,12 @@ bool Data::process(uint8_t* data, uint32_t len) } break; default: + writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); + // only repeat the PDU locally if the packet isn't for the FNE if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { ::ActivityLog("P25", true, "RF data transmission from %u to %u, %u blocks", srcId, dstId, m_rfDataHeader.getBlocksToFollow()); - + if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", repeating PDU, llId = %u", (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); } @@ -514,6 +509,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) ::memset(m_netPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); m_p25->m_netState = RS_NET_DATA; + m_inbound = false; uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); @@ -522,7 +518,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) bool ret = m_netDataHeader.decode(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); m_netDataHeader.reset(); m_netDataBlockCnt = 0U; @@ -532,7 +528,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", + LogMessage(LOG_NET, P25_PDU_STR ", ISP, ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", m_netDataHeader.getAckNeeded(), m_netDataHeader.getOutbound(), m_netDataHeader.getFormat(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMessage(), m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadLength(), m_netDataHeader.getPacketLength(), m_netDataHeader.getSynchronize(), m_netDataHeader.getNs(), m_netDataHeader.getFSN(), m_netDataHeader.getHeaderOffset(), m_netDataHeader.getLLId()); @@ -576,8 +572,8 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId()); if (m_netDataHeader.getResponseClass() == PDUAckClass::ACK && m_netDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); } else { if (m_netDataHeader.getResponseClass() == PDUAckClass::NACK) { switch (m_netDataHeader.getResponseType()) { @@ -586,17 +582,17 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) m_netDataHeader.getLLId()); break; case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); break; case PDUAckType::NACK_SEQ: case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); break; case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u", - m_netDataHeader.getLLId()); + LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); break; default: @@ -607,7 +603,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } writeRF_PDU_Ack_Response(m_netDataHeader.getResponseClass(), m_netDataHeader.getResponseType(), m_netDataHeader.getResponseStatus(), - m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId()); + m_netDataHeader.getLLId(), (m_netDataHeader.getSrcLLId() > 0U), m_netDataHeader.getSrcLLId()); m_netDataHeader.reset(); m_netExtendedAddress = false; @@ -643,7 +639,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) bool ret = m_netDataHeader.decodeExtAddr(buffer); if (!ret) { LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); m_netDataHeader.reset(); m_netDataBlockCnt = 0U; @@ -701,7 +697,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) m_netExtendedAddress = true; } else { - LogMessage(LOG_NET, P25_PDU_STR ", block %u, fmt = $%02X, lastBlock = %u", + LogMessage(LOG_NET, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u", (m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? m_netData[i].getSerialNo() : m_netDataBlockCnt, m_netData[i].getFormat(), m_netData[i].getLastBlock()); } @@ -719,7 +715,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); if (m_dumpPDUData) { - Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); } } @@ -734,7 +730,7 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } if (m_dumpPDUData && m_netDataBlockCnt > 0U) { - Utils::dump(1U, "PDU Packet", m_netPduUserData, m_netPduUserDataLength); + Utils::dump(1U, "P25, PDU Packet", m_netPduUserData, m_netPduUserDataLength); } if (m_netDataBlockCnt < blocksToFollow) { @@ -895,6 +891,7 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, dataBlock.setFormat(dataHeader); dataBlock.setSerialNo(i); dataBlock.setData(pduUserData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", @@ -914,50 +911,100 @@ void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, writeRF_PDU(data, bitLength, imm); } -/* Updates the processor by the passed number of milliseconds. */ +/* Helper to write user data as a P25 PDU packet. */ -void Data::clock(uint32_t ms) +void Data::writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData) { - // clock all the conventional registration timers - std::vector connToClear = std::vector(); - for (auto entry : m_convRegTimerTable) { - uint32_t llId = entry.first; - - m_convRegTimerTable[llId].clock(ms); - if (m_convRegTimerTable[llId].isRunning() && m_convRegTimerTable[llId].hasExpired()) { - connToClear.push_back(llId); - } - } + assert(pduUserData != nullptr); - if (connToClear.size() > 0) { - m_p25->writeRF_Preamble(); + uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (dataHeader.getPadLength() > 0U) + bitLength += (dataHeader.getPadLength() * 8U); + + uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", + dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), + dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), + dataHeader.getHeaderOffset(), bitLength, dataHeader.getLLId()); } - // handle PDU conventional connection registration - for (uint32_t llId : connToClear) { - uint32_t ipAddr = m_convRegQueueTable[llId]; + // generate the PDU header and 1/2 rate Trellis + dataHeader.encode(block); + writeNetwork(0U, block, P25_PDU_FEC_LENGTH_BYTES, false); - if (!acl::AccessControl::validateSrcId(llId)) { - LogWarning(LOG_RF, P25_PDU_STR ", DENY (Registration Response Deny), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); - writeRF_PDU_Reg_Response(PDURegType::DENY, llId, ipAddr); - } - else { - if (!hasLLIdFNEReg(llId)) { - // update dynamic FNE registration table entry - m_fneRegTable[llId] = ipAddr; + if (blocksToFollow > 0U) { + uint32_t dataOffset = 0U; + uint32_t packetLength = dataHeader.getPDULength(); + uint32_t netDataBlockCnt = 1U; + + // generate the second PDU header + if ((dataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { + dataHeader.encodeExtAddr(pduUserData, true); + + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + dataHeader.encodeExtAddr(block); + writeNetwork(1U, block, P25_PDU_FEC_LENGTH_BYTES, false); + netDataBlockCnt++; + + bitLength += P25_PDU_FEC_LENGTH_BITS; + + dataOffset += P25_PDU_HEADER_LENGTH_BYTES; + + blocksToFollow--; + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", + dataHeader.getEXSAP(), dataHeader.getSrcLLId()); } + } + + // are we processing extended address data from the first block? + if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { + dataHeader.encodeExtAddr(pduUserData); if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ACCEPT (Registration Response Accept), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + LogMessage(LOG_NET, P25_PDU_STR ", OSP, sap = $%02X, srcLlId = %u", + dataHeader.getEXSAP(), dataHeader.getSrcLLId()); } + } - writeRF_PDU_Reg_Response(PDURegType::ACCEPT, llId, ipAddr); + if (dataHeader.getFormat() != PDUFormatType::AMBT) { + edac::CRC::addCRC32(pduUserData, packetLength); } - m_convRegQueueTable.erase(llId); - m_convRegTimerTable.erase(llId); + // generate the PDU data + for (uint32_t i = 0U; i < blocksToFollow; i++) { + DataBlock dataBlock = DataBlock(); + dataBlock.setFormat(dataHeader); + dataBlock.setSerialNo(i); + dataBlock.setData(pduUserData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(), + dataBlock.getLastBlock()); + } + + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + dataBlock.encode(block); + writeNetwork(netDataBlockCnt, block, P25_PDU_FEC_LENGTH_BYTES, dataBlock.getLastBlock()); + netDataBlockCnt++; + + dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } } +} +/* Updates the processor by the passed number of milliseconds. */ + +void Data::clock(uint32_t ms) +{ if (m_p25->m_sndcpSupport) { // clock all the SNDCP ready timers std::vector sndcpReadyExpired = std::vector(); @@ -1128,8 +1175,6 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_netPduUserData(nullptr), m_netPduUserDataLength(0U), m_fneRegTable(), - m_convRegQueueTable(), - m_convRegTimerTable(), m_sndcpStateTable(), m_sndcpReadyTimers(), m_sndcpStandbyTimers(), @@ -1156,9 +1201,6 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_fneRegTable.clear(); - m_convRegQueueTable.clear(); - m_convRegTimerTable.clear(); - m_sndcpStateTable.clear(); m_sndcpReadyTimers.clear(); m_sndcpStandbyTimers.clear(); @@ -1195,13 +1237,22 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) LogMessage(LOG_RF, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); } - m_convRegQueueTable[llId] = ipAddr; + if (!acl::AccessControl::validateSrcId(llId)) { + LogWarning(LOG_RF, P25_PDU_STR ", DENY (Registration Response Deny), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + writeRF_PDU_Reg_Response(PDURegType::DENY, llId, ipAddr); + } + else { + if (!hasLLIdFNEReg(llId)) { + // update dynamic FNE registration table entry + m_fneRegTable[llId] = ipAddr; + } - m_convRegTimerTable[llId] = Timer(1000U, 0U, CONV_REG_WAIT_TIMEOUT); - m_convRegTimerTable[llId].start(); + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", ACCEPT (Registration Response Accept), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + } - // acknowledge - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId); + writeRF_PDU_Reg_Response(PDURegType::ACCEPT, llId, ipAddr); + } } break; case PDURegType::DISCONNECT: @@ -1213,7 +1264,7 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) } // acknowledge - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId); + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId, false); if (hasLLIdFNEReg(llId)) { // remove dynamic FNE registration table entry @@ -1366,7 +1417,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) isp->getDeactType()); } - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId); + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId, false); sndcpReset(llId, true); } break; @@ -1443,7 +1494,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac P25Utils::addStatusBits(data + 2U, newBitLength, m_inbound, true); P25Utils::setStatusBitsStartIdle(data + 2U); - //Utils::dump("Raw PDU OSP", data, newByteLength + 2U); + //Utils::dump("P25, Data::writeRF_PDU(), Raw PDU OSP", data, newByteLength + 2U); if (m_p25->m_duplex) { data[0U] = modem::TAG_DATA; @@ -1520,7 +1571,7 @@ void Data::writeNet_PDU_Buffered() edac::CRC::addCRC32(m_netPduUserData, m_netPduUserDataLength); if (m_dumpPDUData) { - Utils::dump("OSP PDU User Data (NET)", m_netPduUserData, m_netPduUserDataLength); + Utils::dump("P25, OSP PDU User Data (NET)", m_netPduUserData, m_netPduUserDataLength); } // generate the PDU data @@ -1612,7 +1663,7 @@ void Data::writeRF_PDU_Buffered() edac::CRC::addCRC32(m_rfPduUserData, m_rfPduUserDataLength); if (m_dumpPDUData) { - Utils::dump("OSP PDU User Data (RF)", m_rfPduUserData, m_rfPduUserDataLength); + Utils::dump("P25, OSP PDU User Data (RF)", m_rfPduUserData, m_rfPduUserDataLength); } // generate the PDU data @@ -1620,6 +1671,7 @@ void Data::writeRF_PDU_Buffered() m_rfData[i].setFormat(m_rfDataHeader); m_rfData[i].setSerialNo(i); m_rfData[i].setData(m_rfPduUserData + dataOffset); + m_rfData[i].setLastBlock((i + 1U) == blocksToFollow); if (m_verbose) { LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", @@ -1651,9 +1703,11 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA DataHeader rspHeader = DataHeader(); rspHeader.setFormat(PDUFormatType::CONFIRMED); + rspHeader.setMFId(m_rfDataHeader.getMFId()); rspHeader.setAckNeeded(true); rspHeader.setOutbound(true); rspHeader.setSAP(PDUSAP::CONV_DATA_REG); + rspHeader.setSynchronize(true); rspHeader.setLLId(llId); rspHeader.setBlocksToFollow(1U); @@ -1668,13 +1722,15 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA pduUserData[11U] = (ipAddr >> 0) & 0xFFU; } + Utils::dump(1U, "Data::writeRF_PDU_Reg_Response() pduUserData", pduUserData, 12U); + rspHeader.calculateLength(12U); writeRF_PDU_User(rspHeader, false, pduUserData); } /* 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) +void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, uint32_t srcLlId) { if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) return; @@ -1698,7 +1754,11 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a if (srcLlId > 0U) { rspHeader.setSrcLLId(srcLlId); } - rspHeader.setFullMessage(true); + + if (extendedAddress) + rspHeader.setFullMessage(false); + else + rspHeader.setFullMessage(true); rspHeader.setBlocksToFollow(0U); diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 7ff8fc58b..21b94ef8f 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -88,6 +88,13 @@ namespace p25 * @param imm Flag indicating the PDU should be written to the immediate queue. */ void writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool imm = false); + /** + * @brief Helper to write user data as a P25 PDU packet. + * @param dataHeader Instance of a PDU data header. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param pduUserData Buffer containing user data to transmit. + */ + void writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData); /** * @brief Updates the processor by the passed number of milliseconds. @@ -147,9 +154,6 @@ namespace p25 std::unordered_map m_fneRegTable; - std::unordered_map m_convRegQueueTable; - std::unordered_map m_convRegTimerTable; - std::unordered_map m_sndcpStateTable; std::unordered_map m_sndcpReadyTimers; std::unordered_map m_sndcpStandbyTimers; @@ -221,6 +225,7 @@ namespace p25 * @param regType Registration Response. * @param llId Logical Link ID. * @param ipAddr + * @param mfId */ void writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipAddr); /** @@ -229,9 +234,11 @@ namespace p25 * @param ackType Acknowledgement Type. * @param ackStatus * @param llId Logical Link ID. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. * @param srcLlId Source Logical Link ID. */ - void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, uint32_t srcLlId = 0U); + void writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, + 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 2382c18a1..ef1e32dbf 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -183,6 +183,11 @@ bool Voice::process(uint8_t* data, uint32_t len) m_p25->m_rfLastDstId = lc.getDstId(); m_p25->m_rfLastSrcId = lc.getSrcId(); + // if we're DFSI/V.24 -- ignore the TGID presented in the HDU + if (m_p25->m_isModemDFSI) { + m_p25->m_rfLastDstId = 0U; + } + m_rfLastHDU = lc; m_rfLastHDUValid = true; @@ -230,61 +235,8 @@ bool Voice::process(uint8_t* data, uint32_t len) alreadyDecoded = true; - // don't process RF frames if this modem isn't authoritative - if (!m_p25->m_authoritative && m_p25->m_permittedDstId != lc.getDstId()) { - if (!g_disableNonAuthoritativeLogging) - LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; + if (checkRFTrafficCollision(srcId, dstId)) return false; - } - - // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination - if (m_p25->m_netState != RS_NET_IDLE && dstId == m_p25->m_netLastDstId) { - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; - return false; - } - - // stop network frames from processing -- RF wants to transmit on a different talkgroup - if (m_p25->m_netState != RS_NET_IDLE) { - if (m_netLC.getSrcId() == srcId && m_p25->m_netLastDstId == dstId) { - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, - m_netLC.getSrcId(), m_p25->m_netLastDstId); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; - return false; - } - else { - LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, - m_p25->m_netLastDstId); - if (!m_p25->m_dedicatedControl) { - m_p25->m_affiliations->releaseGrant(m_p25->m_netLastDstId, false); - } - - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - - if (m_p25->m_duplex) { - m_p25->writeRF_TDU(true); - } - - m_p25->m_netTGHang.stop(); - } - - // is control is enabled, and the group was granted by network already ignore RF traffic - if (m_p25->m_enableControl && dstId == m_p25->m_netLastDstId) { - if (m_p25->m_affiliations->isNetGranted(dstId)) { - LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, - m_netLC.getSrcId(), m_p25->m_netLastDstId); - resetRF(); - m_p25->m_rfState = RS_RF_LISTENING; - return false; - } - } - } // if this is a late entry call, clear states if (m_rfLastHDU.getDstId() == 0U) { @@ -296,6 +248,11 @@ bool Voice::process(uint8_t* data, uint32_t len) resetRF(); } + if (m_p25->m_rfLastDstId == 0U && m_p25->m_isModemDFSI) { + // if we're DFSI/V.24 -- use the TGID presented in the LDU1 + m_p25->m_rfLastDstId = dstId; + } + if (m_p25->m_enableControl) { if (!m_p25->m_ccRunning && !m_p25->m_dedicatedControl) { m_p25->m_control->writeRF_ControlData(255U, 0U, false); @@ -345,7 +302,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } else { // validate the target ID, if the target is a talkgroup - if (!acl::AccessControl::validateTGId(dstId)) { + if (!acl::AccessControl::validateTGId(dstId, m_p25->m_forceAllowTG0)) { if (m_lastRejectId == 0 || m_lastRejectId != dstId) { LogWarning(LOG_RF, P25_HDU_STR " denial, TGID rejection, dstId = %u", dstId); if (m_p25->m_enableControl) { @@ -364,6 +321,12 @@ bool Voice::process(uint8_t* data, uint32_t len) } } + if (group && dstId == 0U && m_p25->m_forceAllowTG0) { + LogWarning(LOG_RF, P25_HDU_STR " TGID 0 (P25 blackhole talkgroup) detected, srcId = %u", srcId); + dstId = 1U; // force destination ID to TGID 1 -- TGID 0 is not allowed in P25, and the network won't properly handle it + lc.setDstId(dstId); + } + // verify the source RID is affiliated to the group TGID; only if control data // is supported if (group && m_p25->m_enableControl) { @@ -394,11 +357,11 @@ bool Voice::process(uint8_t* data, uint32_t len) // send network grant demand TDU if (m_p25->m_network != nullptr) { if (!m_p25->m_dedicatedControl && m_p25->m_convNetGrantDemand) { - uint8_t controlByte = 0x80U; // Grant Demand Flag + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag if (encrypted) - controlByte |= 0x08U; // Grant Encrypt Flag + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; // Grant Encrypt Flag if (!group) - controlByte |= 0x01U; // Unit-to-unit Flag + controlByte |= network::NET_CTRL_U2U; // Unit-to-unit Flag LogMessage(LOG_RF, P25_HDU_STR " remote grant demand, srcId = %u, dstId = %u", srcId, dstId); m_p25->m_network->writeP25TDU(lc, m_rfLSD, controlByte); @@ -538,9 +501,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // add status bits P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); - writeNetwork(buffer, DUID::HDU); - - if (m_p25->m_duplex && !m_p25->m_isModemDFSI) { + if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -794,7 +755,7 @@ bool Voice::process(uint8_t* data, uint32_t len) writeNetwork(data + 2U, DUID::LDU1, frameType); - if (m_p25->m_duplex && !m_p25->m_isModemDFSI) { + if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -845,8 +806,8 @@ bool Voice::process(uint8_t* data, uint32_t len) getNextMI(lastMI, nextMI); if (m_verbose && m_debug) { - Utils::dump(1U, "Previous P25 MI", lastMI, MI_LENGTH_BYTES); - Utils::dump(1U, "Calculated next P25 MI", nextMI, MI_LENGTH_BYTES); + Utils::dump(1U, "P25, LDU2, Previous P25 MI", lastMI, MI_LENGTH_BYTES); + Utils::dump(1U, "P25, LDU2, Calculated next P25 MI", nextMI, MI_LENGTH_BYTES); } m_rfLC.setMI(nextMI); @@ -911,7 +872,7 @@ bool Voice::process(uint8_t* data, uint32_t len) writeNetwork(data + 2U, DUID::LDU2); - if (m_p25->m_duplex && !m_p25->m_isModemDFSI) { + if (m_p25->m_duplex && (!m_p25->m_isModemDFSI || (m_p25->m_isModemDFSI && m_p25->m_dfsiFDX))) { data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -1008,8 +969,6 @@ bool Voice::process(uint8_t* data, uint32_t len) // add status bits P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, m_inbound, false); - writeNetwork(buffer, DUID::HDU); - if (m_p25->m_duplex) { buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; @@ -1184,69 +1143,8 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L uint32_t dstId = control.getDstId(); uint32_t srcId = control.getSrcId(); - // don't process network frames if the destination ID's don't match and the RF TG hang timer is running - if (m_p25->m_rfLastDstId != 0U && dstId != 0U) { - if (m_p25->m_rfLastDstId != dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - - if (m_p25->m_rfLastDstId == dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { - m_p25->m_rfTGHang.start(); - } - } - - // 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 - if (m_p25->m_netLastDstId != 0U && dstId != 0U && (duid == DUID::LDU1 || duid == DUID::LDU2)) { - if (m_p25->m_netLastDstId != dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { - return false; - } - - if (m_p25->m_netLastDstId == dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { - m_p25->m_netTGHang.start(); - } - } - - // don't process network frames if the RF modem isn't in a listening state - if (m_p25->m_rfState != RS_RF_LISTENING) { - if (m_rfLC.getSrcId() == srcId && m_rfLC.getDstId() == dstId) { - LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", m_rfLC.getSrcId(), m_rfLC.getDstId(), - srcId, dstId); - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - else { - LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", m_rfLC.getDstId(), - dstId); - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); - return false; - } - } - } - - // don't process network frames if this modem isn't authoritative - if (!m_p25->m_authoritative && m_p25->m_permittedDstId != dstId) { - if (!g_disableNonAuthoritativeLogging) - LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", dstId); - resetNet(); - if (m_p25->m_network != nullptr) - m_p25->m_network->resetP25(); + if (checkNetTrafficCollision(srcId, dstId, duid)) return false; - } uint32_t count = 0U; switch (duid) { @@ -1560,15 +1458,26 @@ void Voice::writeNetwork(const uint8_t *data, defines::DUID::E duid, defines::Fr if (m_p25->m_rfTimeout.isRunning() && m_p25->m_rfTimeout.hasExpired()) return; + uint8_t controlByte = 0U; switch (duid) { case DUID::HDU: // ignore HDU break; case DUID::LDU1: - m_p25->m_network->writeP25LDU1(m_rfLC, m_rfLSD, data, frameType); + if (!m_rfLC.getGroup()) + controlByte = network::NET_CTRL_U2U; + m_p25->m_network->writeP25LDU1(m_rfLC, m_rfLSD, data, frameType, controlByte); + break; + case DUID::VSELP1: + // ignore VSELP1 break; case DUID::LDU2: - m_p25->m_network->writeP25LDU2(m_rfLC, m_rfLSD, data); + if (!m_rfLC.getGroup()) + controlByte = network::NET_CTRL_U2U; + m_p25->m_network->writeP25LDU2(m_rfLC, m_rfLSD, data, controlByte); + break; + case DUID::VSELP2: + // ignore VSELP2 break; case DUID::TDU: case DUID::TDULC: @@ -1580,6 +1489,151 @@ void Voice::writeNetwork(const uint8_t *data, defines::DUID::E duid, defines::Fr } } +/* Helper to perform RF traffic collision checking. */ + +bool Voice::checkRFTrafficCollision(uint32_t srcId, uint32_t dstId) +{ + // don't process RF frames if this modem isn't authoritative + if (!m_p25->m_authoritative && m_p25->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + + // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination + if (m_p25->m_netState != RS_NET_IDLE && dstId == m_p25->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup + if (m_p25->m_netState != RS_NET_IDLE) { + if (m_netLC.getSrcId() == srcId && m_p25->m_netLastDstId == dstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_netLC.getSrcId(), m_p25->m_netLastDstId); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, + m_p25->m_netLastDstId); + if (!m_p25->m_dedicatedControl) { + m_p25->m_affiliations->releaseGrant(m_p25->m_netLastDstId, false); + } + + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + + if (m_p25->m_duplex) { + m_p25->writeRF_TDU(true); + } + + m_p25->m_netTGHang.stop(); + } + + // is control is enabled, and the group was granted by network already ignore RF traffic + if (m_p25->m_enableControl && dstId == m_p25->m_netLastDstId) { + if (m_p25->m_affiliations->isNetGranted(dstId)) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, + m_netLC.getSrcId(), m_p25->m_netLastDstId); + resetRF(); + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + } + } + + return false; +} + +/* Helper to perform network traffic collision checking. */ + +bool Voice::checkNetTrafficCollision(uint32_t srcId, uint32_t dstId, defines::DUID::E duid) +{ + // don't process network frames if the destination ID's don't match and the RF TG hang timer is running + if (m_p25->m_rfLastDstId != 0U && dstId != 0U) { + if (m_p25->m_rfLastDstId != dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + + if (m_p25->m_rfLastDstId == dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + m_p25->m_rfTGHang.start(); + } + } + + // 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(); + } + + // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and + // the destination ID doesn't match the default net idle talkgroup + if (m_p25->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_p25->m_rfTGHang.isRunning()) { + if (m_p25->m_defaultNetIdleTalkgroup != dstId) { + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + } + + // 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 + if (m_p25->m_netLastDstId != 0U && dstId != 0U && (duid == DUID::LDU1 || duid == DUID::LDU2)) { + if (m_p25->m_netLastDstId != dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { + return true; + } + + if (m_p25->m_netLastDstId == dstId && (m_p25->m_netTGHang.isRunning() && !m_p25->m_netTGHang.hasExpired())) { + m_p25->m_netTGHang.start(); + } + } + + // don't process network frames if the RF modem isn't in a listening state + if (m_p25->m_rfState != RS_RF_LISTENING) { + if (m_rfLC.getSrcId() == srcId && m_rfLC.getDstId() == dstId) { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", m_rfLC.getSrcId(), m_rfLC.getDstId(), + srcId, dstId); + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + else { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", m_rfLC.getDstId(), + dstId); + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + } + } + + // don't process network frames if this modem isn't authoritative + if (!m_p25->m_authoritative && m_p25->m_permittedDstId != dstId) { + if (!g_disableNonAuthoritativeLogging) + LogWarning(LOG_NET, "[NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted, dstId = %u", dstId); + resetNet(); + if (m_p25->m_network != nullptr) + m_p25->m_network->resetP25(); + return true; + } + + return false; +} + /* Helper to write end of frame data. */ void Voice::writeRF_EndOfVoice() @@ -1930,10 +1984,14 @@ void Voice::writeNet_LDU1() } uint32_t netId = control.getNetId(); + if (netId == 0U) + netId = lc::LC::getSiteData().netId(); uint32_t sysId = control.getSysId(); + if (sysId == 0U) + sysId = lc::LC::getSiteData().sysId(); // is the network peer a different WACN or system ID? - if (m_p25->m_enableControl && m_p25->m_allowExplicitSourceId) { + if (m_p25->m_allowExplicitSourceId) { if (sysId != lc::LC::getSiteData().sysId()) { // per TIA-102.AABD-D transmit EXPLICIT_SOURCE_ID every other frame (e.g. every other LDU1) m_roamLDU1Count++; @@ -1945,16 +2003,14 @@ void Voice::writeNet_LDU1() } else { // flag explicit block to follow in next LDU1 - if (m_netLC.getLCO() == LCO::GROUP) { - m_netLC.setExplicitId(true); + m_netLC.setExplicitId(true); + + if (m_netLC.getLCO() == LCO::PRIVATE) { + m_netLC.setLCO(LCO::PRIVATE_EXT); } } } } - else { - netId = lc::LC::getSiteData().netId(); - sysId = lc::LC::getSiteData().sysId(); - } // are we swapping the LC out for the RFSS_STS_BCAST or LC_GROUP_UPDT? m_pktLDU1Count++; diff --git a/src/host/p25/packet/Voice.h b/src/host/p25/packet/Voice.h index 89de8eb3b..2f03c8983 100644 --- a/src/host/p25/packet/Voice.h +++ b/src/host/p25/packet/Voice.h @@ -153,6 +153,22 @@ namespace p25 * @param frameType Frame Type. */ void writeNetwork(const uint8_t* data, defines::DUID::E duid, defines::FrameType::E frameType = defines::FrameType::DATA_UNIT); + + /** + * @brief Helper to perform RF traffic collision checking. + * @param srcId Source ID. + * @param dstId Destination ID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkRFTrafficCollision(uint32_t srcId, uint32_t dstId); + /** + * @brief Helper to perform network traffic collision checking. + * @param srcId Source ID. + * @param dstId Destination ID. + * @param duid DUID. + * @return bool True, if traffic collision, otherwise false. + */ + bool checkNetTrafficCollision(uint32_t srcId, uint32_t dstId, defines::DUID::E duid); /** * @brief Helper to write end of voice frame data. diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 60b77799c..d9742e49a 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -513,7 +513,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp { uint8_t len = buffer[1U]; if (m_debug) { - Utils::dump(1U, "Modem Flash Contents", buffer + 3U, len - 3U); + Utils::dump(1U, "HostSetup::portModemHandler(), Modem Flash Contents", buffer + 3U, len - 3U); } if (len == 249U) { bool ret = edac::CRC::checkCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); @@ -569,7 +569,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp default: LogWarning(LOG_CAL, "Unknown message, type = %02X", buffer[2U]); - Utils::dump("Buffer dump", buffer, len); + Utils::dump("HostSetup::portModemHandler(), buffer", buffer, len); break; } } @@ -1292,20 +1292,19 @@ void HostSetup::processP25BER(const uint8_t* buffer) uint32_t bits = P25Utils::decode(buffer, pduBuffer, 0, P25_PDU_FRAME_LENGTH_BITS); - Utils::dump(1U, "Raw PDU Dump", buffer, P25_PDU_FRAME_LENGTH_BYTES); + Utils::dump(1U, "P25, Raw PDU Dump", buffer, P25_PDU_FRAME_LENGTH_BYTES); uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; ::memset(rfPDU, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); - uint32_t rfPDUBits = 0U; - rfPDUBits = Utils::getBits(pduBuffer, rfPDU, 0U, bits); + Utils::getBits(pduBuffer, rfPDU, 0U, bits); ::memset(pduBuffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); Utils::getBitRange(rfPDU, pduBuffer, P25_PREAMBLE_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS); bool ret = dataHeader.decode(pduBuffer); if (!ret) { LogWarning(LOG_CAL, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", pduBuffer, P25_PDU_FEC_LENGTH_BYTES); + Utils::dump(1U, "P25, Unfixable PDU Data", pduBuffer, P25_PDU_FEC_LENGTH_BYTES); } else { LogMessage(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", @@ -1321,7 +1320,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(buffer); - Utils::dump(1U, "Raw TSBK Dump", buffer, P25_TSDU_FRAME_LENGTH_BYTES); + Utils::dump(1U, "P25, Raw TSBK Dump", buffer, P25_TSDU_FRAME_LENGTH_BYTES); if (tsbk == nullptr) { LogWarning(LOG_CAL, P25_TSDU_STR ", undecodable LC"); diff --git a/src/host/setup/SiteParamSetWnd.h b/src/host/setup/SiteParamSetWnd.h index 251a3f024..b6c1bc493 100644 --- a/src/host/setup/SiteParamSetWnd.h +++ b/src/host/setup/SiteParamSetWnd.h @@ -217,7 +217,7 @@ class HOST_SW_API SiteParamSetWnd final : public CloseWndBase { m_p25RfssIdLabel.setGeometry(FPoint(2, 14), FSize(20, 1)); m_p25RfssId.setGeometry(FPoint(23, 14), FSize(10, 1)); - m_p25RfssId.setText(rfssConfig["dmrNetId"].as("1").c_str()); + m_p25RfssId.setText(rfssConfig["rfssId"].as("1").c_str()); m_p25RfssId.setShadow(false); m_p25RfssId.setMaxLength(3); m_p25RfssId.setInputFilter("[[:xdigit:]]"); diff --git a/src/host/win32/host.ico b/src/host/win32/host.ico new file mode 100644 index 000000000..874b99977 Binary files /dev/null and b/src/host/win32/host.ico differ diff --git a/src/host/win32/resource.h b/src/host/win32/resource.h new file mode 100644 index 000000000..056c0418b --- /dev/null +++ b/src/host/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/host/win32/resource.rc b/src/host/win32/resource.rc new file mode 100644 index 000000000..d1eb8f1b8 Binary files /dev/null and b/src/host/win32/resource.rc differ diff --git a/src/patch/CMakeLists.txt b/src/patch/CMakeLists.txt index d04d50046..de1c681b5 100644 --- a/src/patch/CMakeLists.txt +++ b/src/patch/CMakeLists.txt @@ -8,8 +8,18 @@ # * # */ file(GLOB patch_SRC + "src/patch/mmdvm/*.h" + "src/patch/mmdvm/*.cpp" "src/patch/network/*.h" "src/patch/network/*.cpp" + "src/patch/win32/*.h" "src/patch/*.h" "src/patch/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB patch_RC + "src/patch/win32/*.rc" +) diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index cd040b0d4..f9d12bfcc 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -18,6 +18,7 @@ #include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/LC.h" #include "common/p25/lc/LC.h" +#include "common/p25/Sync.h" #include "common/p25/P25Utils.h" #include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" @@ -44,6 +45,13 @@ using namespace network::udp; #include #endif // !defined(_WIN32) +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define TEK_AES "aes" +#define TEK_ARC4 "arc4" + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -64,23 +72,58 @@ HostPatch::HostPatch(const std::string& confFile) : m_srcSlot(1U), m_dstTGId(0U), m_dstSlot(1U), + m_twoWayPatch(false), + m_mmdvmP25Reflector(false), + m_mmdvmP25Net(nullptr), + m_netState(RS_NET_IDLE), + m_netLC(), + m_gotNetLDU1(false), + m_netLDU1(nullptr), + m_gotNetLDU2(false), + m_netLDU2(nullptr), m_identity(), m_digiMode(1U), m_dmrEmbeddedData(), m_grantDemand(false), m_callInProgress(false), + m_callAlgoId(P25DEF::ALGO_UNENCRYPT), m_rxStartTime(0U), m_rxStreamId(0U), + m_tekSrcAlgoId(P25DEF::ALGO_UNENCRYPT), + m_tekSrcKeyId(0U), + m_tekDstAlgoId(P25DEF::ALGO_UNENCRYPT), + m_tekDstKeyId(0U), + m_requestedSrcTek(false), + m_requestedDstTek(false), + m_p25SrcCrypto(nullptr), + m_p25DstCrypto(nullptr), + m_netId(P25DEF::WACN_STD_DEFAULT), + m_sysId(P25DEF::SID_STD_DEFAULT), m_running(false), m_trace(false), m_debug(false) { - /* stub */ + m_netLDU1 = new uint8_t[9U * 25U]; + m_netLDU2 = new uint8_t[9U * 25U]; + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + resetWithNullAudio(m_netLDU1, false); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + resetWithNullAudio(m_netLDU2, false); + + m_p25SrcCrypto = new p25::crypto::P25Crypto(); + m_p25DstCrypto = new p25::crypto::P25Crypto(); } /* Finalizes a instance of the HostPatch class. */ -HostPatch::~HostPatch() = default; +HostPatch::~HostPatch() +{ + delete[] m_netLDU1; + delete[] m_netLDU2; + delete m_p25SrcCrypto; + delete m_p25DstCrypto; +} /* Executes the main FNE processing loop. */ @@ -166,6 +209,13 @@ int HostPatch::run() if (!ret) return EXIT_FAILURE; + // initialize MMDVM P25 reflector networking + if (m_mmdvmP25Reflector) { + ret = createMMDVMP25Network(); + if (!ret) + return EXIT_FAILURE; + } + /* ** Initialize Threads */ @@ -173,6 +223,11 @@ int HostPatch::run() if (!Thread::runAsThread(this, threadNetworkProcess)) return EXIT_FAILURE; + if (m_mmdvmP25Reflector) { + if (!Thread::runAsThread(this, threadMMDVMProcess)) + return EXIT_FAILURE; + } + ::LogInfoEx(LOG_HOST, "Patch is up and running"); m_running = true; @@ -196,6 +251,11 @@ int HostPatch::run() m_network->clock(ms); } + if (m_mmdvmP25Reflector) { + std::lock_guard lock(HostPatch::m_networkMutex); + m_mmdvmP25Net->clock(ms); + } + if (ms < 2U) Thread::sleep(1U); } @@ -220,6 +280,24 @@ bool HostPatch::readParams() yaml::Node systemConf = m_conf["system"]; m_identity = systemConf["identity"].as(); + + m_netId = (uint32_t)::strtoul(systemConf["netId"].as("BB800").c_str(), NULL, 16); + m_netId = p25::P25Utils::netId(m_netId); + if (m_netId == 0xBEE00) { + ::fatal("error 4\n"); + } + + m_sysId = (uint32_t)::strtoul(systemConf["sysId"].as("001").c_str(), NULL, 16); + m_sysId = p25::P25Utils::sysId(m_sysId); + + /* + ** Site Data + */ + int8_t lto = (int8_t)systemConf["localTimeOffset"].as(0); + p25::SiteData siteData = p25::SiteData(m_netId, m_sysId, 1U, 1U, 0U, 0U, 1U, P25DEF::ServiceClass::VOICE, lto); + siteData.setNetActive(true); + + p25::lc::LC::setSiteData(siteData); m_digiMode = (uint8_t)systemConf["digiMode"].as(1U); if (m_digiMode < TX_MODE_DMR) @@ -229,12 +307,22 @@ bool HostPatch::readParams() m_grantDemand = systemConf["grantDemand"].as(false); + m_mmdvmP25Reflector = systemConf["mmdvmP25Reflector"].as(false); + + if (m_mmdvmP25Reflector && m_digiMode != TX_MODE_P25) { + LogError(LOG_HOST, "Patch does not currently support MMDVM patching in any mode other then P25."); + return false; + } + m_trace = systemConf["trace"].as(false); m_debug = systemConf["debug"].as(false); LogInfo("General Parameters"); + LogInfo(" System Id: $%03X", m_sysId); + LogInfo(" P25 Network Id: $%05X", m_netId); LogInfo(" Digital Mode: %s", m_digiMode == TX_MODE_DMR ? "DMR" : "P25"); LogInfo(" Grant Demands: %s", m_grantDemand ? "yes" : "no"); + LogInfo(" MMDVM P25 Reflector Patch: %s", m_mmdvmP25Reflector ? "yes" : "no"); if (m_debug) { LogInfo(" Debug: yes"); @@ -261,6 +349,53 @@ bool HostPatch::createNetwork() m_srcSlot = (uint8_t)networkConf["sourceSlot"].as(1U); m_dstTGId = (uint32_t)networkConf["destinationTGID"].as(1U); m_dstSlot = (uint8_t)networkConf["destinationSlot"].as(1U); + + // source TEK parameters + yaml::Node srcTekConf = networkConf["srcTek"]; + bool tekSrcEnable = srcTekConf["enable"].as(false); + std::string tekSrcAlgo = srcTekConf["tekAlgo"].as(); + std::transform(tekSrcAlgo.begin(), tekSrcAlgo.end(), tekSrcAlgo.begin(), ::tolower); + m_tekSrcKeyId = (uint32_t)::strtoul(srcTekConf["tekKeyId"].as("0").c_str(), NULL, 16); + if (tekSrcEnable && m_tekSrcKeyId > 0U) { + if (tekSrcAlgo == TEK_AES) + m_tekSrcAlgoId = P25DEF::ALGO_AES_256; + else if (tekSrcAlgo == TEK_ARC4) + m_tekSrcAlgoId = P25DEF::ALGO_ARC4; + else { + ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); + m_tekSrcAlgoId = P25DEF::ALGO_UNENCRYPT; + m_tekSrcKeyId = 0U; + } + } + + if (!tekSrcEnable) + m_tekSrcAlgoId = P25DEF::ALGO_UNENCRYPT; + if (m_tekSrcAlgoId == P25DEF::ALGO_UNENCRYPT) + m_tekSrcKeyId = 0U; + + // destination TEK parameters + yaml::Node dstTekConf = networkConf["srcTek"]; + bool tekDstEnable = dstTekConf["enable"].as(false); + std::string tekDstAlgo = dstTekConf["tekAlgo"].as(); + std::transform(tekDstAlgo.begin(), tekDstAlgo.end(), tekDstAlgo.begin(), ::tolower); + m_tekDstKeyId = (uint32_t)::strtoul(dstTekConf["tekKeyId"].as("0").c_str(), NULL, 16); + if (tekDstEnable && m_tekDstKeyId > 0U) { + if (tekDstAlgo == TEK_AES) + m_tekDstAlgoId = P25DEF::ALGO_AES_256; + else if (tekDstAlgo == TEK_ARC4) + m_tekDstAlgoId = P25DEF::ALGO_ARC4; + else { + ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); + m_tekDstAlgoId = P25DEF::ALGO_UNENCRYPT; + m_tekDstKeyId = 0U; + } + } + + if (!tekDstEnable) + m_tekDstAlgoId = P25DEF::ALGO_UNENCRYPT; + if (m_tekDstAlgoId == P25DEF::ALGO_UNENCRYPT) + m_tekDstKeyId = 0U; + m_twoWayPatch = networkConf["twoWay"].as(false); // make sure our destination ID is sane @@ -359,8 +494,22 @@ bool HostPatch::createNetwork() LogInfo(" Source TGID: %u", m_srcTGId); LogInfo(" Source DMR Slot: %u", m_srcSlot); + + LogInfo(" Source Traffic Encrypted: %s", tekSrcEnable ? "yes" : "no"); + if (tekSrcEnable) { + LogInfo(" Source TEK Algorithm: %s", tekSrcAlgo.c_str()); + LogInfo(" Source TEK Key ID: $%04X", m_tekSrcKeyId); + } + LogInfo(" Destination TGID: %u", m_dstTGId); LogInfo(" Destination DMR Slot: %u", m_dstSlot); + + LogInfo(" Destination Traffic Encrypted: %s", tekDstEnable ? "yes" : "no"); + if (tekDstEnable) { + LogInfo(" Destination TEK Algorithm: %s", tekDstAlgo.c_str()); + LogInfo(" Destination TEK Key ID: $%04X", m_tekDstKeyId); + } + LogInfo(" Two-Way Patch: %s", m_twoWayPatch ? "yes" : "no"); if (debug) { @@ -382,6 +531,9 @@ bool HostPatch::createNetwork() m_network->setMetadata(m_identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); m_network->setConventional(true); + m_network->setKeyResponseCallback([=](p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength) { + processTEKResponse(&ki, algId, keyLength); + }); if (encrypted) { m_network->setPresharedKey(presharedKey); @@ -401,6 +553,40 @@ bool HostPatch::createNetwork() return true; } +/* Initializes MMDVM network connectivity. */ + +bool HostPatch::createMMDVMP25Network() +{ + yaml::Node networkConf = m_conf["network"]; + + std::string address = networkConf["mmdvmGatewayAddress"].as(); + uint16_t port = (uint16_t)networkConf["mmdvmGatewayPort"].as(42020U); + uint16_t localPort = (uint16_t)networkConf["localGatewayPort"].as(32010U); + bool debug = networkConf["debug"].as(false); + + LogInfo("MMDVM Network Parameters"); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + LogInfo(" Local Port: %u", localPort); + + if (debug) { + LogInfo(" Debug: yes"); + } + + // initialize networking + m_mmdvmP25Net = new mmdvm::P25Network(address, port, localPort, debug); + + bool ret = m_mmdvmP25Net->open(); + if (!ret) { + delete m_mmdvmP25Net; + m_mmdvmP25Net = nullptr; + LogError(LOG_HOST, "failed to initialize MMDVM networking!"); + return false; + } + + return true; +} + /* Helper to process DMR network traffic. */ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) @@ -456,7 +642,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) UInt8Array data = std::unique_ptr(new uint8_t[DMR_FRAME_LENGTH_BYTES]); ::memset(data.get(), 0x00U, DMR_FRAME_LENGTH_BYTES); DataType::E dataType = DataType::VOICE_SYNC; - uint8_t n = 0U; + if (dataSync) { dataType = (DataType::E)(buffer[15U] & 0x0FU); ::memcpy(data.get(), buffer + 20U, DMR_FRAME_LENGTH_BYTES); @@ -465,7 +651,6 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) ::memcpy(data.get(), buffer + 20U, DMR_FRAME_LENGTH_BYTES); } else { - n = buffer[15U] & 0x0FU; dataType = DataType::VOICE; ::memcpy(data.get(), buffer + 20U, DMR_FRAME_LENGTH_BYTES); } @@ -690,9 +875,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (m_digiMode != TX_MODE_P25) return; - bool grantDemand = (buffer[14U] & 0x80U) == 0x80U; - bool grantDenial = (buffer[14U] & 0x40U) == 0x40U; - bool unitToUnit = (buffer[14U] & 0x01U) == 0x01U; + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; + bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; + bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; // process network message header DUID::E duid = (DUID::E)buffer[22U]; @@ -761,18 +946,62 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (dstId != m_srcTGId && dstId != m_dstTGId) return; - uint32_t actualDstId = m_dstTGId; - if (m_twoWayPatch) { - if (dstId == m_dstTGId) - actualDstId = m_srcTGId; - } else { - if (dstId == m_dstTGId) - return; + bool reverseEncrypt = false; + uint32_t actualDstId = m_srcTGId; + uint8_t tekAlgoId = m_tekSrcAlgoId; + uint16_t tekKeyId = m_tekSrcKeyId; + + if (!m_mmdvmP25Reflector) { + actualDstId = m_dstTGId; + if (m_twoWayPatch) { + if (dstId == m_dstTGId) { + actualDstId = m_srcTGId; + tekAlgoId = m_tekDstAlgoId; + tekKeyId = m_tekDstKeyId; + reverseEncrypt = true; + } + } else { + if (dstId == m_dstTGId) + return; + } } + // is this a new call stream? + uint16_t callKID = 0U; if (m_network->getP25StreamId() != m_rxStreamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { m_callInProgress = true; + // if this is the beginning of a call and we have a valid HDU frame, extract the algo ID + uint8_t frameType = buffer[180U]; + if (frameType == FrameType::HDU_VALID) { + m_callAlgoId = buffer[181U]; + if (m_callAlgoId != ALGO_UNENCRYPT) { + callKID = GET_UINT16(buffer, 182U); + + if (m_callAlgoId != tekAlgoId && callKID != tekKeyId) { + m_callAlgoId = ALGO_UNENCRYPT; + m_callInProgress = false; + + LogWarning(LOG_HOST, "P25, call ignored, using different encryption parameters, callAlgoId = $%02X, callKID = $%04X, tekAlgoId = $%02X, tekKID = $%04X", m_callAlgoId, callKID, tekAlgoId, tekKeyId); + return; + } else { + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { + mi[i] = buffer[184U + i]; + } + + if (reverseEncrypt) { + m_p25DstCrypto->setMI(mi); + m_p25DstCrypto->generateKeystream(); + } else { + m_p25SrcCrypto->setMI(mi); + m_p25SrcCrypto->generateKeystream(); + } + } + } + } + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; @@ -780,13 +1009,15 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (m_grantDemand) { p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(dstId); lc.setSrcId(srcId); p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - uint8_t controlByte = 0x80U; + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + if (m_callAlgoId != ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; // Grant Encrypt Flag m_network->writeP25TDU(lc, lsd, controlByte); } } @@ -797,7 +1028,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) return; p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(p25::defines::LCO::GROUP); + lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(actualDstId); lc.setSrcId(srcId); @@ -805,8 +1036,13 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) LogMessage(LOG_HOST, P25_TDU_STR); - uint8_t controlByte = 0x00U; - m_network->writeP25TDU(lc, lsd, controlByte); + if (m_mmdvmP25Reflector) { + m_mmdvmP25Net->writeTDU(); + } + else { + uint8_t controlByte = 0x00U; + m_network->writeP25TDU(lc, lsd, controlByte); + } if (m_rxStartTime > 0U) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -819,8 +1055,14 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) m_rxStreamId = 0U; m_callInProgress = false; + m_callAlgoId = ALGO_UNENCRYPT; m_rxStartTime = 0U; m_rxStreamId = 0U; + + m_p25SrcCrypto->clearMI(); + m_p25SrcCrypto->resetKeystream(); + m_p25DstCrypto->clearMI(); + m_p25DstCrypto->resetKeystream(); return; } @@ -879,6 +1121,10 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + cryptP25AudioFrame(netLDU, reverseEncrypt, 1U); + } + control = lc::LC(*dfsiLC.control()); control.setSrcId(srcId); @@ -902,7 +1148,30 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } } - m_network->writeP25LDU1(control, lsd, netLDU, frameType); + // the previous is nice and all -- but if we're cross-encrypting, we need to use the TEK + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + control.setAlgId(tekAlgoId); + control.setKId(tekKeyId); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + if (!reverseEncrypt) + m_p25SrcCrypto->getMI(mi); + else + m_p25DstCrypto->getMI(mi); + + control.setMI(mi); + } + + if (m_mmdvmP25Reflector) { + ::memcpy(m_netLDU1, netLDU, 9U * 25U); + m_gotNetLDU1 = true; + m_netLC = control; + + writeNet_LDU1(false); + } else { + m_network->writeP25LDU1(control, lsd, netLDU, frameType); + } } break; case DUID::LDU2: @@ -952,12 +1221,39 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + cryptP25AudioFrame(netLDU, reverseEncrypt, 2U); + } + control = lc::LC(*dfsiLC.control()); control.setSrcId(srcId); control.setDstId(actualDstId); - m_network->writeP25LDU2(control, lsd, netLDU); + // set the algo ID and key ID + if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + control.setAlgId(tekAlgoId); + control.setKId(tekKeyId); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + if (!reverseEncrypt) + m_p25SrcCrypto->getMI(mi); + else + m_p25DstCrypto->getMI(mi); + + control.setMI(mi); + } + + if (m_mmdvmP25Reflector) { + ::memcpy(m_netLDU2, netLDU, 9U * 25U); + m_gotNetLDU2 = true; + m_netLC = control; + + writeNet_LDU2(false); + } else { + m_network->writeP25LDU2(control, lsd, netLDU); + } } break; @@ -975,6 +1271,296 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } } +/* Helper to cross encrypt P25 network traffic audio frames. */ + +void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p25N) +{ + assert(ldu != nullptr); + using namespace p25; + using namespace p25::defines; + + uint8_t tekSrcAlgoId = m_tekSrcAlgoId; + uint16_t tekSrcKeyId = m_tekSrcKeyId; + uint8_t tekDstAlgoId = m_tekDstAlgoId; + uint16_t tekDstKeyId = m_tekDstKeyId; + + if (reverseEncrypt) { + tekSrcAlgoId = m_tekDstAlgoId; + tekSrcKeyId = m_tekDstKeyId; + tekDstAlgoId = m_tekSrcAlgoId; + tekDstKeyId = m_tekSrcKeyId; + } + + // decode 9 IMBE codewords into PCM samples + for (int n = 0; n < 9; n++) { + uint8_t imbe[RAW_IMBE_LENGTH_BYTES]; + switch (n) { + case 0: + ::memcpy(imbe, ldu + 10U, RAW_IMBE_LENGTH_BYTES); + break; + case 1: + ::memcpy(imbe, ldu + 26U, RAW_IMBE_LENGTH_BYTES); + break; + case 2: + ::memcpy(imbe, ldu + 55U, RAW_IMBE_LENGTH_BYTES); + break; + case 3: + ::memcpy(imbe, ldu + 80U, RAW_IMBE_LENGTH_BYTES); + break; + case 4: + ::memcpy(imbe, ldu + 105U, RAW_IMBE_LENGTH_BYTES); + break; + case 5: + ::memcpy(imbe, ldu + 130U, RAW_IMBE_LENGTH_BYTES); + break; + case 6: + ::memcpy(imbe, ldu + 155U, RAW_IMBE_LENGTH_BYTES); + break; + case 7: + ::memcpy(imbe, ldu + 180U, RAW_IMBE_LENGTH_BYTES); + break; + case 8: + ::memcpy(imbe, ldu + 204U, RAW_IMBE_LENGTH_BYTES); + break; + } + + // Utils::dump(1U, "P25, HostPatch::cryptP25AudioFrame(), IMBE", imbe, RAW_IMBE_LENGTH_BYTES); + + // first -- decrypt the IMBE codeword + if (tekSrcAlgoId != P25DEF::ALGO_UNENCRYPT && tekSrcKeyId > 0U) { + if (!reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { + switch (tekSrcAlgoId) { + case P25DEF::ALGO_AES_256: + m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case P25DEF::ALGO_ARC4: + m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); + break; + } + } else { + if (reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { + switch (tekDstAlgoId) { + case P25DEF::ALGO_AES_256: + m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case P25DEF::ALGO_ARC4: + m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); + break; + } + } + } + } + + // second -- reencrypt the IMBE codeword + if (tekDstAlgoId != P25DEF::ALGO_UNENCRYPT && tekDstKeyId > 0U) { + if (!reverseEncrypt && m_p25DstCrypto->getTEKLength() > 0U) { + switch (tekDstAlgoId) { + case P25DEF::ALGO_AES_256: + m_p25DstCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case P25DEF::ALGO_ARC4: + m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); + break; + } + } else { + if (reverseEncrypt && m_p25SrcCrypto->getTEKLength() > 0U) { + switch (tekSrcAlgoId) { + case P25DEF::ALGO_AES_256: + m_p25SrcCrypto->cryptAES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + case P25DEF::ALGO_ARC4: + m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; + default: + LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); + break; + } + } + } + } + } +} + +/* Helper to process a FNE KMM TEK response. */ + +void HostPatch::processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength) +{ + if (ki == nullptr) + return; + + if (algId == m_tekSrcAlgoId && ki->kId() == m_tekSrcKeyId) { + LogMessage(LOG_HOST, "Source TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + UInt8Array tek = std::make_unique(keyLength); + ki->getKey(tek.get()); + + m_p25SrcCrypto->setTEKAlgoId(algId); + m_p25SrcCrypto->setTEKKeyId(ki->kId()); + m_p25SrcCrypto->setKey(tek.get(), keyLength); + } + + if (algId == m_tekDstAlgoId && ki->kId() == m_tekDstKeyId) { + LogMessage(LOG_HOST, "Destination TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + UInt8Array tek = std::make_unique(keyLength); + ki->getKey(tek.get()); + + m_p25DstCrypto->setTEKAlgoId(algId); + m_p25DstCrypto->setTEKKeyId(ki->kId()); + m_p25DstCrypto->setKey(tek.get(), keyLength); + } +} + +/* Helper to check for an unflushed LDU1 packet. */ + +void HostPatch::checkNet_LDU1() +{ + if (m_netState == RS_NET_IDLE) + return; + + // check for an unflushed LDU1 + if ((m_netLDU1[10U] != 0x00U || m_netLDU1[26U] != 0x00U || m_netLDU1[55U] != 0x00U || + m_netLDU1[80U] != 0x00U || m_netLDU1[105U] != 0x00U || m_netLDU1[130U] != 0x00U || + m_netLDU1[155U] != 0x00U || m_netLDU1[180U] != 0x00U || m_netLDU1[204U] != 0x00U) && + m_gotNetLDU1) + writeNet_LDU1(false); +} + +/* Helper to write a network P25 LDU1 packet. */ + +void HostPatch::writeNet_LDU1(bool toFNE) +{ + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + if (toFNE) { + if (m_netState == RS_NET_IDLE) { + m_callInProgress = true; + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_rxStartTime = now; + + uint8_t lco = m_netLDU1[51U]; + uint8_t mfId = m_netLDU1[52U]; + uint32_t dstId = GET_UINT24(m_netLDU1, 76U); + uint32_t srcId = GET_UINT24(m_netLDU1, 101U); + + LogMessage(LOG_HOST, "MMDVM P25, call start, srcId = %u, dstId = %u", srcId, dstId); + + lc::LC lc = lc::LC(); + m_netLC = lc; + m_netLC.setLCO(lco); + m_netLC.setMFId(mfId); + m_netLC.setDstId(dstId); + m_netLC.setSrcId(srcId); + + if (m_grantDemand) { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; // Grant Demand Flag + m_network->writeP25TDU(lc, lsd, controlByte); + } + } + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU1[201U]); + lsd.setLSD2(m_netLDU1[202U]); + + LogMessage(LOG_NET, "MMDVM " P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); + + if (m_debug) + Utils::dump(1U, "P25, HostPatch::writeNet_LDU1(), MMDVM -> DVM LDU1", m_netLDU1, 9U * 25U); + + m_network->writeP25LDU1(m_netLC, lsd, m_netLDU1, FrameType::DATA_UNIT); + + m_netState = RS_NET_AUDIO; + resetWithNullAudio(m_netLDU1, false); + m_gotNetLDU1 = false; + } + else { + if (m_debug) + Utils::dump(1U, "P25, HostPatch::writeNet_LDU1(), DVM -> MMDVM LDU1", m_netLDU1, 9U * 25U); + + // add the Low Speed Data + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU1[201U]); + lsd.setLSD2(m_netLDU1[202U]); + + m_mmdvmP25Net->writeLDU1(m_netLDU1, m_netLC, lsd, false); + + resetWithNullAudio(m_netLDU1, false); + m_gotNetLDU1 = false; + } +} + +/* Helper to check for an unflushed LDU2 packet. */ + +void HostPatch::checkNet_LDU2() +{ + if (m_netState == RS_NET_IDLE) + return; + + // check for an unflushed LDU2 + if ((m_netLDU2[10U] != 0x00U || m_netLDU2[26U] != 0x00U || m_netLDU2[55U] != 0x00U || + m_netLDU2[80U] != 0x00U || m_netLDU2[105U] != 0x00U || m_netLDU2[130U] != 0x00U || + m_netLDU2[155U] != 0x00U || m_netLDU2[180U] != 0x00U || m_netLDU2[204U] != 0x00U) && + m_gotNetLDU2) { + writeNet_LDU2(false); + } +} + +/* Helper to write a network P25 LDU2 packet. */ + +void HostPatch::writeNet_LDU2(bool toFNE) +{ + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + if (toFNE) { + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU2[201U]); + lsd.setLSD2(m_netLDU2[202U]); + + LogMessage(LOG_NET, "MMDVM " P25_LDU2_STR " audio"); + + if (m_debug) + Utils::dump(1U, "P25, HostPatch::writeNet_LDU2(), MMDVM -> DVM LDU2", m_netLDU2, 9U * 25U); + + m_network->writeP25LDU2(m_netLC, lsd, m_netLDU2); + + resetWithNullAudio(m_netLDU2, false); + m_gotNetLDU2 = false; + } + else { + if (m_debug) + Utils::dump(1U, "P25, HostPatch::writeNet_LDU2(), DVM -> MMDVM LDU2", m_netLDU2, 9U * 25U); + + // add the Low Speed Data + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + lsd.setLSD1(m_netLDU2[201U]); + lsd.setLSD2(m_netLDU2[202U]); + + m_mmdvmP25Net->writeLDU2(m_netLDU2, m_netLC, lsd, false); + + resetWithNullAudio(m_netLDU2, false); + m_gotNetLDU2 = false; + } +} + /* Entry point to network processing thread. */ void* HostPatch::threadNetworkProcess(void* arg) @@ -1010,6 +1596,26 @@ void* HostPatch::threadNetworkProcess(void* arg) continue; } + if (patch->m_network->getStatus() == NET_STAT_RUNNING) { + // check if we need to request a TEK for the source TGID + if (patch->m_tekSrcAlgoId != P25DEF::ALGO_UNENCRYPT && patch->m_tekSrcKeyId > 0U) { + if (patch->m_p25SrcCrypto->getTEKLength() == 0U && !patch->m_requestedSrcTek) { + patch->m_requestedSrcTek = true; + LogMessage(LOG_HOST, "Patch source TGID encryption enabled, requesting TEK from network."); + patch->m_network->writeKeyReq(patch->m_tekSrcKeyId, patch->m_tekSrcAlgoId); + } + } + + // check if we need to request a TEK for the destination TGID + if (patch->m_tekDstAlgoId != P25DEF::ALGO_UNENCRYPT && patch->m_tekDstKeyId > 0U) { + if (patch->m_p25DstCrypto->getTEKLength() == 0U && !patch->m_requestedDstTek) { + patch->m_requestedDstTek = true; + LogMessage(LOG_HOST, "Patch destination TGID encryption enabled, requesting TEK from network."); + patch->m_network->writeKeyReq(patch->m_tekDstKeyId, patch->m_tekDstAlgoId); + } + } + } + uint32_t length = 0U; bool netReadRet = false; if (patch->m_digiMode == TX_MODE_DMR) { @@ -1038,6 +1644,181 @@ void* HostPatch::threadNetworkProcess(void* arg) return nullptr; } +/* Entry point to MMDVM network processing thread. */ + +void* HostPatch::threadMMDVMProcess(void* arg) +{ + using namespace p25; + using namespace p25::defines; + using namespace p25::dfsi::defines; + + thread_t* th = (thread_t*)arg; + if (th != nullptr) { +#if defined(_WIN32) + ::CloseHandle(th->thread); +#else + ::pthread_detach(th->thread); +#endif // defined(_WIN32) + + std::string threadName("patch:mmdvm-net-process"); + HostPatch* patch = static_cast(th->obj); + if (patch == nullptr) { + g_killed = true; + LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); + } + + if (g_killed) { + delete th; + return nullptr; + } + + LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); +#ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); +#endif // _GNU_SOURCE + + StopWatch stopWatch; + stopWatch.start(); + + while (!g_killed) { + if (!patch->m_running) { + Thread::sleep(1U); + continue; + } + + uint32_t ms = stopWatch.elapsed(); + + ms = stopWatch.elapsed(); + stopWatch.start(); + + if (patch->m_digiMode == TX_MODE_P25) { + std::lock_guard lock(HostPatch::m_networkMutex); + + DECLARE_UINT8_ARRAY(buffer, 100U); + uint32_t len = patch->m_mmdvmP25Net->read(buffer, 100U); + if (len != 0U) { + switch (buffer[0U]) { + // LDU1 + case DFSIFrameType::LDU1_VOICE1: + ::memcpy(patch->m_netLDU1 + 0U, buffer, 22U); + break; + case DFSIFrameType::LDU1_VOICE2: + ::memcpy(patch->m_netLDU1 + 25U, buffer, 14U); + break; + case DFSIFrameType::LDU1_VOICE3: + ::memcpy(patch->m_netLDU1 + 50U, buffer, 17U); + break; + case DFSIFrameType::LDU1_VOICE4: + ::memcpy(patch->m_netLDU1 + 75U, buffer, 17U); + break; + case DFSIFrameType::LDU1_VOICE5: + ::memcpy(patch->m_netLDU1 + 100U, buffer, 17U); + break; + case DFSIFrameType::LDU1_VOICE6: + ::memcpy(patch->m_netLDU1 + 125U, buffer, 17U); + break; + case DFSIFrameType::LDU1_VOICE7: + ::memcpy(patch->m_netLDU1 + 150U, buffer, 17U); + break; + case DFSIFrameType::LDU1_VOICE8: + ::memcpy(patch->m_netLDU1 + 175U, buffer, 17U); + break; + case DFSIFrameType::LDU1_VOICE9: + ::memcpy(patch->m_netLDU1 + 200U, buffer, 16U); + patch->checkNet_LDU2(); + + if (patch->m_netState != RS_NET_IDLE) { + patch->m_gotNetLDU1 = true; + patch->writeNet_LDU1(true); + } + break; + + // LDU2 + case DFSIFrameType::LDU2_VOICE10: + ::memcpy(patch->m_netLDU2 + 0U, buffer, 22U); + break; + case DFSIFrameType::LDU2_VOICE11: + ::memcpy(patch->m_netLDU2 + 25U, buffer, 14U); + break; + case DFSIFrameType::LDU2_VOICE12: + ::memcpy(patch->m_netLDU2 + 50U, buffer, 17U); + break; + case DFSIFrameType::LDU2_VOICE13: + ::memcpy(patch->m_netLDU2 + 75U, buffer, 17U); + break; + case DFSIFrameType::LDU2_VOICE14: + ::memcpy(patch->m_netLDU2 + 100U, buffer, 17U); + break; + case DFSIFrameType::LDU2_VOICE15: + ::memcpy(patch->m_netLDU2 + 125U, buffer, 17U); + break; + case DFSIFrameType::LDU2_VOICE16: + ::memcpy(patch->m_netLDU2 + 150U, buffer, 17U); + break; + case DFSIFrameType::LDU2_VOICE17: + ::memcpy(patch->m_netLDU2 + 175U, buffer, 17U); + break; + case DFSIFrameType::LDU2_VOICE18: + ::memcpy(patch->m_netLDU2 + 200U, buffer, 16U); + if (patch->m_netState == RS_NET_IDLE) { + patch->writeNet_LDU1(true); + } else { + patch->checkNet_LDU1(); + } + + patch->writeNet_LDU2(true); + break; + + case 0x80U: + { + patch->m_netState = RS_NET_IDLE; + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + LogMessage(LOG_HOST, "MMDVM " P25_TDU_STR); + + uint8_t controlByte = 0x00U; + patch->m_network->writeP25TDU(patch->m_netLC, lsd, controlByte); + + if (patch->m_rxStartTime > 0U) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t diff = now - patch->m_rxStartTime; + + LogMessage(LOG_HOST, "MMDVM P25, call end, srcId = %u, dstId = %u, dur = %us", patch->m_netLC.getSrcId(), patch->m_netLC.getDstId(), diff / 1000U); + } + + patch->m_rxStartTime = 0U; + patch->m_rxStreamId = 0U; + + patch->m_callInProgress = false; + patch->m_rxStartTime = 0U; + patch->m_rxStreamId = 0U; + } + break; + + case 0xF0U: + case 0xF1U: + // these are MMDVM control bytes -- we ignore these + break; + + default: + LogError(LOG_NET, "unknown opcode from MMDVM gateway $%02X", buffer[0U]); + break; + } + } + } + + if (ms < 5U) + Thread::sleep(5U); + } + + LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; +} + /* Helper to reset IMBE buffer with null frames. */ void HostPatch::resetWithNullAudio(uint8_t* data, bool encrypted) diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h index 2e91a899a..2bc363741 100644 --- a/src/patch/HostPatch.h +++ b/src/patch/HostPatch.h @@ -20,10 +20,13 @@ #include "common/dmr/data/EmbeddedData.h" #include "common/dmr/lc/LC.h" #include "common/dmr/lc/PrivacyLC.h" +#include "common/p25/lc/LC.h" +#include "common/p25/Crypto.h" #include "common/network/udp/Socket.h" #include "common/yaml/Yaml.h" #include "common/Timer.h" #include "network/PeerNetwork.h" +#include "mmdvm/P25Network.h" #include #include @@ -73,6 +76,16 @@ class HOST_SW_API HostPatch { uint8_t m_dstSlot; bool m_twoWayPatch; + bool m_mmdvmP25Reflector; + mmdvm::P25Network* m_mmdvmP25Net; + + RPT_NET_STATE m_netState; + p25::lc::LC m_netLC; + bool m_gotNetLDU1; + uint8_t* m_netLDU1; + bool m_gotNetLDU2; + uint8_t* m_netLDU2; + std::string m_identity; uint8_t m_digiMode; @@ -82,9 +95,23 @@ class HOST_SW_API HostPatch { bool m_grantDemand; bool m_callInProgress; + uint8_t m_callAlgoId; uint64_t m_rxStartTime; uint32_t m_rxStreamId; + uint8_t m_tekSrcAlgoId; + uint16_t m_tekSrcKeyId; + uint8_t m_tekDstAlgoId; + uint16_t m_tekDstKeyId; + bool m_requestedSrcTek; + bool m_requestedDstTek; + + p25::crypto::P25Crypto* m_p25SrcCrypto; + p25::crypto::P25Crypto* m_p25DstCrypto; + + uint32_t m_netId; + uint32_t m_sysId; + bool m_running; bool m_trace; bool m_debug; @@ -101,6 +128,11 @@ class HOST_SW_API HostPatch { * @returns bool True, if network connectivity was initialized, otherwise false. */ bool createNetwork(); + /** + * @brief Initializes MMDVM network connectivity. + * @returns bool True, if network connectivity was initialized, otherwise false. + */ + bool createMMDVMP25Network(); /** * @brief Helper to process DMR network traffic. @@ -116,12 +148,53 @@ class HOST_SW_API HostPatch { */ void processP25Network(uint8_t* buffer, uint32_t length); + /** + * @brief Helper to cross encrypt P25 network traffic audio frames. + * @param ldu + * @param reverseEncrypt Flag indicating whether or not to reverse the encryption (i.e. use destination TEK vs source TEK). + * @param p25N + */ + void cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p25N); + + /** + * @brief Helper to process a FNE KMM TEK response. + * @param ki Key Item. + * @param algId Algorithm ID. + * @param keyLength Length of key in bytes. + */ + void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); + + /** + * @brief Helper to check for an unflushed LDU1 packet. + */ + void checkNet_LDU1(); + /** + * @brief Helper to write a network P25 LDU1 packet. + * @param toFNE Flag indicating whether or not the packet is being written to the DVM FNE. + */ + void writeNet_LDU1(bool toFNE); + /** + * @brief Helper to check for an unflushed LDU2 packet. + */ + void checkNet_LDU2(); + /** + * @brief Helper to write a network P25 LDU2 packet. + * @param toFNE Flag indicating whether or not the packet is being written to the DVM FNE. + */ + void writeNet_LDU2(bool toFNE); + /** * @brief Entry point to network processing thread. * @param arg Instance of the thread_t structure. * @returns void* (Ignore) */ static void* threadNetworkProcess(void* arg); + /** + * @brief Entry point to MMDVM network processing thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadMMDVMProcess(void* arg); /** * @brief Helper to reset IMBE buffer with null frames. diff --git a/src/patch/mmdvm/P25Network.cpp b/src/patch/mmdvm/P25Network.cpp new file mode 100644 index 000000000..892cf6f14 --- /dev/null +++ b/src/patch/mmdvm/P25Network.cpp @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - TG Patch + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2009-2014,2016,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "patch/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "mmdvm/P25Network.h" + +using namespace p25::defines; +using namespace network::udp; +using namespace mmdvm; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/* +** bryanb: we purposely do handling this way, instead of using the DFSI classes, to ensure we maintain compat +** with upstream MMDVM, extra data handled from our DFSI could confuse the P25 gateway +*/ + +const uint8_t REC62[] = { + 0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC63[] = { + 0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC64[] = { + 0x64U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC65[] = { + 0x65U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC66[] = { + 0x66U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC67[] = { + 0x67U, 0xF0U, 0x9DU, 0x6AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC68[] = { + 0x68U, 0x19U, 0xD4U, 0x26U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC69[] = { + 0x69U, 0xE0U, 0xEBU, 0x7BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6A[] = { + 0x6AU, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC6B[] = { + 0x6BU, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC6C[] = { + 0x6CU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6D[] = { + 0x6DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6E[] = { + 0x6EU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC6F[] = { + 0x6FU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC70[] = { + 0x70U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC71[] = { + 0x71U, 0xACU, 0xB8U, 0xA4U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC72[] = { + 0x72U, 0x9BU, 0xDCU, 0x75U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U +}; + +const uint8_t REC73[] = { + 0x73U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint8_t REC80[] = { + 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint32_t BUFFER_LENGTH = 100U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the P25Network class. */ + +P25Network::P25Network(const std::string& gatewayAddress, uint16_t gatewayPort, uint16_t localPort, bool debug) : + m_socket(localPort), + m_addr(), + m_addrLen(0U), + m_debug(debug), + m_buffer(1000U, "MMDVM P25 Network") +{ + if (Socket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) + m_addrLen = 0U; +} + +/* Finalizes a instance of the P25Network class. */ + +P25Network::~P25Network() = default; + +/* Reads P25 raw frame data from the P25 ring buffer. */ + +uint32_t P25Network::read(uint8_t* data, uint32_t length) +{ + assert(data != nullptr); + + if (m_buffer.isEmpty()) + return 0U; + + uint8_t c = 0U; + m_buffer.get(&c, 1U); + + if (c == 0U) { + return 0U; + } + + if (c > length) { + return 0U; + } + + m_buffer.get(data, c); + return c; +} + +/* Writes P25 LDU1 frame data to the network. */ + +bool P25Network::writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end) +{ + assert(ldu1 != nullptr); + + uint8_t buffer[22U]; + + // The '62' record + ::memcpy(buffer, REC62, 22U); + ::memcpy(buffer + 10U, ldu1 + 10U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $62 LDU1 Sent", buffer, 22U); + + bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '63' record + ::memcpy(buffer, REC63, 14U); + ::memcpy(buffer + 1U, ldu1 + 26U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $63 LDU1 Sent", buffer, 14U); + + ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '64' record + ::memcpy(buffer, REC64, 17U); + buffer[1U] = control.getLCO(); + buffer[2U] = control.getMFId(); + ::memcpy(buffer + 5U, ldu1 + 55U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $64 LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '65' record + ::memcpy(buffer, REC65, 17U); + uint32_t id = control.getDstId(); + buffer[1U] = (id >> 16) & 0xFFU; + buffer[2U] = (id >> 8) & 0xFFU; + buffer[3U] = (id >> 0) & 0xFFU; + ::memcpy(buffer + 5U, ldu1 + 80U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $65 LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '66' record + ::memcpy(buffer, REC66, 17U); + id = control.getSrcId(); + buffer[1U] = (id >> 16) & 0xFFU; + buffer[2U] = (id >> 8) & 0xFFU; + buffer[3U] = (id >> 0) & 0xFFU; + ::memcpy(buffer + 5U, ldu1 + 105U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $66 LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '67' record + ::memcpy(buffer, REC67, 17U); + ::memcpy(buffer + 5U, ldu1 + 130U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $67 LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '68' record + ::memcpy(buffer, REC68, 17U); + ::memcpy(buffer + 5U, ldu1 + 155U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $68 LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '69' record + ::memcpy(buffer, REC69, 17U); + ::memcpy(buffer + 5U, ldu1 + 180U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $69 LDU1 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6A' record + ::memcpy(buffer, REC6A, 16U); + buffer[1U] = lsd.getLSD1(); + buffer[2U] = lsd.getLSD2(); + ::memcpy(buffer + 5U, ldu1 + 204U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network $6A LDU1 Sent", buffer, 16U); + + ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); + if (!ret) + return false; + + if (end) { + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU1(), MMDVM Network END Sent", REC80, 17U); + + ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); + if (!ret) + return false; + } + + return true; +} + +/* Writes P25 LDU2 frame data to the network. */ + +bool P25Network::writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end) +{ + assert(ldu2 != nullptr); + + uint8_t buffer[22U]; + + // The '6B' record + ::memcpy(buffer, REC6B, 22U); + ::memcpy(buffer + 10U, ldu2 + 10U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6B LDU2 Sent", buffer, 22U); + + bool ret = m_socket.write(buffer, 22U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6C' record + ::memcpy(buffer, REC6C, 14U); + ::memcpy(buffer + 1U, ldu2 + 26U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6C LDU2 Sent", buffer, 14U); + + ret = m_socket.write(buffer, 14U, m_addr, m_addrLen); + if (!ret) + return false; + + uint8_t mi[MI_LENGTH_BYTES]; + control.getMI(mi); + + // The '6D' record + ::memcpy(buffer, REC6D, 17U); + buffer[1U] = mi[0U]; + buffer[2U] = mi[1U]; + buffer[3U] = mi[2U]; + ::memcpy(buffer + 5U, ldu2 + 55U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6D LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6E' record + ::memcpy(buffer, REC6E, 17U); + buffer[1U] = mi[3U]; + buffer[2U] = mi[4U]; + buffer[3U] = mi[5U]; + ::memcpy(buffer + 5U, ldu2 + 80U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6E LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '6F' record + ::memcpy(buffer, REC6F, 17U); + buffer[1U] = mi[6U]; + buffer[2U] = mi[7U]; + buffer[3U] = mi[8U]; + ::memcpy(buffer + 5U, ldu2 + 105U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $6F LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '70' record + ::memcpy(buffer, REC70, 17U); + buffer[1U] = control.getAlgId(); + uint32_t id = control.getKId(); + buffer[2U] = (id >> 8) & 0xFFU; + buffer[3U] = (id >> 0) & 0xFFU; + ::memcpy(buffer + 5U, ldu2 + 130U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $70 LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '71' record + ::memcpy(buffer, REC71, 17U); + ::memcpy(buffer + 5U, ldu2 + 155U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $71 LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '72' record + ::memcpy(buffer, REC72, 17U); + ::memcpy(buffer + 5U, ldu2 + 180U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $72 LDU2 Sent", buffer, 17U); + + ret = m_socket.write(buffer, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + // The '73' record + ::memcpy(buffer, REC73, 16U); + buffer[1U] = lsd.getLSD1(); + buffer[2U] = lsd.getLSD2(); + ::memcpy(buffer + 5U, ldu2 + 204U, RAW_IMBE_LENGTH_BYTES); + + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network $73 LDU2 Sent", buffer, 16U); + + ret = m_socket.write(buffer, 16U, m_addr, m_addrLen); + if (!ret) + return false; + + if (end) { + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeLDU2(), MMDVM Network END Sent", REC80, 17U); + + ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); + if (!ret) + return false; + } + + return true; +} + +/* Writes a TDU frame to the network. */ + +bool P25Network::writeTDU() +{ + if (m_debug) + Utils::dump(1U, "P25, P25Network::writeTDU(), MMDVM Network END Sent", REC80, 17U); + + bool ret = m_socket.write(REC80, 17U, m_addr, m_addrLen); + if (!ret) + return false; + + return true; +} + +/* Updates the timer by the passed number of milliseconds. */ + +void P25Network::clock(uint32_t ms) +{ + uint8_t buffer[BUFFER_LENGTH]; + + sockaddr_storage address; + uint32_t addrLen; + int length = m_socket.read(buffer, BUFFER_LENGTH, address, addrLen); + if (length <= 0) + return; + + if (!Socket::match(m_addr, address)) { + LogMessage(LOG_NET, "MMDVM, packet received from an invalid source"); + return; + } + + if (m_debug) + Utils::dump(1U, "P25Network::clock(), MMDVM Network Data Received", buffer, length); + + uint8_t c = length; + m_buffer.addData(&c, 1U); + + m_buffer.addData(buffer, length); +} + +/* Helper to determine if we are connected to a MMDVM gateway. */ + +bool P25Network::isConnected() const +{ + return (m_addrLen != 0); +} + +/* Opens connection to the network. */ + +bool P25Network::open() +{ + if (m_addrLen == 0U) { + LogError(LOG_NET, "MMDVM, Unable to resolve the address of the P25 Gateway"); + return false; + } + + LogMessage(LOG_NET, "MMDVM, Opening P25 network connection"); + + return m_socket.open(m_addr); +} + +/* Closes connection to the network. */ + +void P25Network::close() +{ + m_socket.close(); + + LogMessage(LOG_NET, "MMDVM, Closing P25 network connection"); +} diff --git a/src/patch/mmdvm/P25Network.h b/src/patch/mmdvm/P25Network.h new file mode 100644 index 000000000..ca3a5bb2a --- /dev/null +++ b/src/patch/mmdvm/P25Network.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - TG Patch + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2009-2014,2016,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup patch_mmdvm MMDVM Networking + * @brief Implementation for the patch MMDVM networking. + * @ingroup patch + * + * @file PeerNetwork.h + * @ingroup patch_mmdvm + * @file PeerNetwork.cpp + * @ingroup patch_mmdvm + */ +#if !defined(__P25_NETWORK_H__) +#define __P25_NETWORK_H__ + +#include "Defines.h" +#include "common/p25/data/LowSpeedData.h" +#include "common/p25/lc/LC.h" +#include "common/network/udp/Socket.h" +#include "common/RingBuffer.h" + +#include +#include + +namespace mmdvm +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the MMDVM networking logic. + // --------------------------------------------------------------------------- + + class P25Network { + public: + /** + * @brief Initializes a new instance of the P25Network class. + * @param gatewayAddress Network Hostname/IP address to connect to. + * @param gatewayPort Network port number. + * @param localPort + * @param debug Flag indicating whether network debug is enabled. + */ + P25Network(const std::string& gatewayAddress, uint16_t gatewayPort, uint16_t localPort, bool debug); + /** + * @brief Finalizes a instance of the P25Network class. + */ + ~P25Network(); + + /** + * @brief Reads P25 raw frame data from the P25 ring buffer. + * @param[out] data Buffer to write received frame data to. + * @param[out] length Length in bytes of received frame. + * @returns + */ + uint32_t read(uint8_t* data, uint32_t length); + + /** + * @brief Writes P25 LDU1 frame data to the network. + * @param[in] ldu1 Buffer containing P25 LDU1 data to send. + * @param[in] control Instance of p25::lc::LC containing link control data. + * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. + * @param[in] end + * @returns bool True, if message was sent, otherwise false. + */ + bool writeLDU1(const uint8_t* ldu1, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end); + + /** + * @brief Writes P25 LDU2 frame data to the network. + * @param[in] ldu1 Buffer containing P25 LDU2 data to send. + * @param[in] control Instance of p25::lc::LC containing link control data. + * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. + * @param[in] end + * @returns bool True, if message was sent, otherwise false. + */ + bool writeLDU2(const uint8_t* ldu2, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, bool end); + + /** + * @brief Writes a TDU frame to the network. + */ + bool writeTDU(); + + /** + * @brief Updates the timer by the passed number of milliseconds. + * @param ms Number of milliseconds. + */ + void clock(uint32_t ms); + + /** + * @brief Helper to determine if we are connected to a MMDVM gateway. + * @return bool True, if connected, otherwise false. + */ + bool isConnected() const; + + /** + * @brief Opens connection to the network. + * @returns bool True, if networking has started, otherwise false. + */ + bool open(); + + /** + * @brief Closes connection to the network. + */ + void close(); + + private: + network::udp::Socket m_socket; + + sockaddr_storage m_addr; + uint32_t m_addrLen; + + bool m_debug; + + RingBuffer m_buffer; + }; +} // namespace mmdvm + +#endif // __P25_NETWORK_H__ diff --git a/src/patch/network/PeerNetwork.cpp b/src/patch/network/PeerNetwork.cpp index a16c0ecfa..6bbe6ad54 100644 --- a/src/patch/network/PeerNetwork.cpp +++ b/src/patch/network/PeerNetwork.cpp @@ -29,7 +29,7 @@ using namespace network; PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) + Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, false, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup) { assert(!address.empty()); assert(port > 0U); @@ -38,7 +38,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc /* Writes P25 LDU1 frame data to the network. */ -bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, p25::defines::FrameType::E frameType) +bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + P25DEF::FrameType::E frameType, uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -50,7 +51,7 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType); + UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType, controlByte); if (message == nullptr) { return false; } @@ -60,7 +61,8 @@ bool PeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS /* Writes P25 LDU2 frame data to the network. */ -bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -72,7 +74,7 @@ bool PeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS } uint32_t messageLength = 0U; - UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data); + UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data, controlByte); if (message == nullptr) { return false; } @@ -198,7 +200,7 @@ bool PeerNetwork::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "PeerNetowrk::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); @@ -211,7 +213,7 @@ bool PeerNetwork::writeConfig() /* Creates an P25 LDU1 frame message. */ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType) + const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -223,7 +225,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType); + createP25_MessageHdr(buffer, DUID::LDU1, control, lsd, frameType, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -277,7 +279,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "PeerNetwork::createP25_LDU1Message_Raw(), Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); @@ -286,7 +288,7 @@ UInt8Array PeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::l /* Creates an P25 LDU2 frame message. */ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data) + const uint8_t* data, uint8_t controlByte) { using namespace p25::defines; using namespace p25::dfsi::defines; @@ -298,7 +300,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l ::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD); // construct P25 message header - createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT); + createP25_MessageHdr(buffer, DUID::LDU2, control, lsd, FrameType::DATA_UNIT, controlByte); // pack DFSI data uint32_t count = MSG_HDR_SIZE; @@ -352,7 +354,7 @@ UInt8Array PeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::l buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); + Utils::dump(1U, "PeerNetwork::createP25_LDU2Message_Raw(), Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD)); length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD); return UInt8Array(buffer); diff --git a/src/patch/network/PeerNetwork.h b/src/patch/network/PeerNetwork.h index 081856bc2..53b9f666a 100644 --- a/src/patch/network/PeerNetwork.h +++ b/src/patch/network/PeerNetwork.h @@ -9,7 +9,7 @@ */ /** * @defgroup patch_network Networking - * @brief Implementation for the bridge networking. + * @brief Implementation for the patch networking. * @ingroup patch * * @file PeerNetwork.h @@ -63,18 +63,21 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, - p25::defines::FrameType::E frameType) override; + P25DEF::FrameType::E frameType, uint8_t controlByte = 0U) override; /** * @brief Writes P25 LDU2 frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns bool True, if message was sent, otherwise false. */ - bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) override; + bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t controlByte = 0U) override; /** * @brief Helper to send a DMR terminator with LC message. @@ -104,10 +107,11 @@ namespace network * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU1 data to send. * @param[in] frameType DVM P25 frame type. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data, p25::defines::FrameType::E frameType); + const uint8_t* data, P25DEF::FrameType::E frameType, uint8_t controlByte); /** * @brief Creates an P25 LDU2 frame message. * @@ -118,10 +122,11 @@ namespace network * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] lsd Instance of p25::data::LowSpeedData containing low speed data. * @param[in] data Buffer containing P25 LDU2 data to send. + * @param[in] controlByte DVM Network Control Byte. * @returns UInt8Array Buffer containing the built network message. */ UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, - const uint8_t* data); + const uint8_t* data, uint8_t controlByte); }; } // namespace network diff --git a/src/patch/win32/project.ico b/src/patch/win32/project.ico new file mode 100644 index 000000000..7557168b5 Binary files /dev/null and b/src/patch/win32/project.ico differ diff --git a/src/patch/win32/resource.h b/src/patch/win32/resource.h new file mode 100644 index 000000000..114be5674 --- /dev/null +++ b/src/patch/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - TG Patch + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/patch/win32/resource.rc b/src/patch/win32/resource.rc new file mode 100644 index 000000000..bb9ede682 Binary files /dev/null and b/src/patch/win32/resource.rc differ diff --git a/src/peered/PeerEditWnd.h b/src/peered/PeerEditWnd.h index bfac5039e..1dabf2873 100644 --- a/src/peered/PeerEditWnd.h +++ b/src/peered/PeerEditWnd.h @@ -22,6 +22,41 @@ #include using namespace finalcut; +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/* Helper to generate a mostly random password. */ + +std::string randPasswordGen(int len) +{ + srand((unsigned int)(time(NULL))); + + const char numbers[] = "0123456789"; + const char letter[] = "abcdefghijklmnoqprstuvwyzx"; + const char LETTER[] = "ABCDEFGHIJKLMNOQPRSTUYWVZX"; + + std::string password; + int randomizer = rand() % 6; + + for (int i = 0; i < len; i++) { + if (randomizer == 1 || randomizer == 4) { + password.append(1U, numbers[rand() % 10]); + randomizer = rand() % 4; + } + else if (randomizer == 2 || randomizer == 5) { + password.append(1U, LETTER[rand() % 26]); + randomizer = rand() % 4; + } + else { + password.append(1U, letter[rand() % 26]); + randomizer = rand() % 4; + } + } + + return password; +} + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -80,12 +115,19 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { */ explicit PeerEditWnd(lookups::PeerId rule, FWidget* widget = nullptr) : CloseWndBase{widget} { + m_new = false; m_rule = rule; - if (m_rule.peerDefault()) { + + if (m_rule.peerDefault() || (m_rule.peerId() == 0U)) { m_new = true; } else { m_origPeerId = m_rule.peerId(); } + + if (m_new) { + std::string password = randPasswordGen(20); + m_rule.peerPassword(password); + } } private: @@ -110,6 +152,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { FButtonGroup m_configGroup{"Configuration", this}; FCheckBox m_peerLinkEnabled{"Peer Link", &m_configGroup}; FCheckBox m_canReqKeysEnabled{"Request Keys", &m_configGroup}; + FCheckBox m_canInhibitEnabled{"Issue Inhibit", &m_configGroup}; /** * @brief Initializes the window layout. @@ -117,7 +160,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { void initLayout() override { FDialog::setText("Peer ID"); - FDialog::setSize(FSize{60, 18}); + FDialog::setSize(FSize{65, 18}); m_enableSetButton = false; CloseWndBase::initLayout(); @@ -138,7 +181,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { m_peerAlias.setShadow(false); m_peerAlias.addCallback("changed", [&]() { m_rule.peerAlias(m_peerAlias.getText().toString()); }); - m_saveCopy.setGeometry(FPoint(36, 2), FSize(18, 1)); + m_saveCopy.setGeometry(FPoint(41, 2), FSize(18, 1)); m_saveCopy.addCallback("toggled", [&]() { if (m_saveCopy.isChecked()) { m_incOnSave.setEnable(); @@ -149,14 +192,14 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { redraw(); }); - m_incOnSave.setGeometry(FPoint(36, 3), FSize(18, 1)); + m_incOnSave.setGeometry(FPoint(41, 3), FSize(18, 1)); m_incOnSave.setDisable(); // talkgroup source { - m_sourceGroup.setGeometry(FPoint(2, 5), FSize(30, 5)); + m_sourceGroup.setGeometry(FPoint(2, 5), FSize(35, 5)); m_peerIdLabel.setGeometry(FPoint(2, 1), FSize(10, 1)); - m_peerId.setGeometry(FPoint(11, 1), FSize(17, 1)); + m_peerId.setGeometry(FPoint(11, 1), FSize(22, 1)); m_peerId.setAlignment(finalcut::Align::Right); if (!m_rule.peerDefault()) { m_peerId.setText(std::to_string(m_rule.peerId())); @@ -210,7 +253,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { }); m_peerPasswordLabel.setGeometry(FPoint(2, 2), FSize(10, 1)); - m_peerPassword.setGeometry(FPoint(11, 2), FSize(17, 1)); + m_peerPassword.setGeometry(FPoint(11, 2), FSize(22, 1)); if (!m_rule.peerDefault()) { m_peerPassword.setText(m_rule.peerPassword()); } @@ -220,7 +263,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { // configuration { - m_configGroup.setGeometry(FPoint(34, 5), FSize(23, 5)); + m_configGroup.setGeometry(FPoint(39, 5), FSize(23, 5)); m_peerLinkEnabled.setGeometry(FPoint(2, 1), FSize(10, 1)); m_peerLinkEnabled.setChecked(m_rule.peerLink()); @@ -233,6 +276,12 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { m_canReqKeysEnabled.addCallback("toggled", [&]() { m_rule.canRequestKeys(m_canReqKeysEnabled.isChecked()); }); + + m_canInhibitEnabled.setGeometry(FPoint(2, 3), FSize(10, 1)); + m_canInhibitEnabled.setChecked(m_rule.canIssueInhibit()); + m_canInhibitEnabled.addCallback("toggled", [&]() { + m_rule.canIssueInhibit(m_canInhibitEnabled.isChecked()); + }); } CloseWndBase::initControls(); @@ -247,8 +296,9 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { uint32_t peerId = m_rule.peerId(); bool peerLink = m_rule.peerLink(); bool canRequestKeys = m_rule.canRequestKeys(); + bool canIssueInhibit = m_rule.canIssueInhibit(); - ::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u PEER LINK: %u CAN REQUEST KEYS: %u", peerAlias.c_str(), peerId, peerLink, canRequestKeys); + ::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u PEER LINK: %u CAN REQUEST KEYS: %u CAN ISSUE INHIBIT: %u", peerAlias.c_str(), peerId, peerLink, canRequestKeys, canIssueInhibit); } /* @@ -314,7 +364,13 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { if (it != peers.end()) { LogMessage(LOG_HOST, "Updating peer %s (%u) to %s (%u)", it->peerAlias().c_str(), it->peerId(), m_rule.peerAlias().c_str(), m_rule.peerId()); g_pidLookups->eraseEntry(m_origPeerId); - g_pidLookups->addEntry(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), m_rule.peerLink(), m_rule.canRequestKeys()); + + lookups::PeerId entry = lookups::PeerId(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), false); + entry.peerLink(m_rule.peerLink()); + entry.canRequestKeys(m_rule.canRequestKeys()); + entry.canIssueInhibit(m_rule.canIssueInhibit()); + + g_pidLookups->addEntry(m_rule.peerId(), entry); logRuleInfo(); } @@ -345,7 +401,13 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { } else { LogMessage(LOG_HOST, "Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId()); } - g_pidLookups->addEntry(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), m_rule.peerLink(), m_rule.canRequestKeys()); + + lookups::PeerId entry = lookups::PeerId(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), false); + entry.peerLink(m_rule.peerLink()); + entry.canRequestKeys(m_rule.canRequestKeys()); + entry.canIssueInhibit(m_rule.canIssueInhibit()); + + g_pidLookups->addEntry(m_rule.peerId(), entry); logRuleInfo(); diff --git a/src/peered/PeerListWnd.h b/src/peered/PeerListWnd.h index 609fce203..7fa6fd14f 100644 --- a/src/peered/PeerListWnd.h +++ b/src/peered/PeerListWnd.h @@ -119,11 +119,12 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { bool masterPassword = (entry.peerPassword().size() == 0U); // build list view entry - const std::array columns = { + const std::array columns = { oss.str(), (masterPassword) ? "X" : "", (entry.peerLink()) ? "X" : "", (entry.canRequestKeys()) ? "X" : "", + (entry.canIssueInhibit()) ? "X" : "", entry.peerAlias() }; @@ -210,14 +211,16 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { m_listView.addColumn("Peer ID", 10); m_listView.addColumn("Master Password", 16); m_listView.addColumn("Peer Link", 12); - m_listView.addColumn("Can Request Keys", 12); + m_listView.addColumn("Request Keys", 12); + m_listView.addColumn("Can Inhibit", 12); m_listView.addColumn("Alias", 40); // set right alignment for peer ID m_listView.setColumnAlignment(2, finalcut::Align::Center); m_listView.setColumnAlignment(3, finalcut::Align::Center); m_listView.setColumnAlignment(4, finalcut::Align::Center); - m_listView.setColumnAlignment(5, finalcut::Align::Left); + m_listView.setColumnAlignment(5, finalcut::Align::Center); + m_listView.setColumnAlignment(6, finalcut::Align::Left); // set type of sorting m_listView.setColumnSortType(1, finalcut::SortType::Name); diff --git a/src/remote/CMakeLists.txt b/src/remote/CMakeLists.txt index bfd1753de..4ebdd5899 100644 --- a/src/remote/CMakeLists.txt +++ b/src/remote/CMakeLists.txt @@ -4,10 +4,17 @@ # * 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 # * # */ file(GLOB dvmcmd_SRC "src/remote/*.h" "src/remote/*.cpp" ) + +# +# Windows Resource Scripts +# +file(GLOB dvmcmd_RC + "src/remote/win32/*.rc" +) diff --git a/src/remote/win32/project.ico b/src/remote/win32/project.ico new file mode 100644 index 000000000..7557168b5 Binary files /dev/null and b/src/remote/win32/project.ico differ diff --git a/src/remote/win32/resource.h b/src/remote/win32/resource.h new file mode 100644 index 000000000..6dec41ff5 --- /dev/null +++ b/src/remote/win32/resource.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Remote Command Client + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ + +#define IDS_APP_TITLE 102 +#define IDI_APP_ICON 103 diff --git a/src/remote/win32/resource.rc b/src/remote/win32/resource.rc new file mode 100644 index 000000000..1f7e53e24 Binary files /dev/null and b/src/remote/win32/resource.rc differ diff --git a/src/sysview/HostWS.cpp b/src/sysview/HostWS.cpp index d8ea75911..7efcaeeed 100644 --- a/src/sysview/HostWS.cpp +++ b/src/sysview/HostWS.cpp @@ -452,7 +452,7 @@ void HostWS::send(json::object obj) m_wsServer.send(*it, json, websocketpp::frame::opcode::text); } } - catch (websocketpp::exception) { /* stub */ } + catch (websocketpp::exception const&) { /* stub */ } } // --------------------------------------------------------------------------- diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index d7923c180..21a3e11c3 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.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) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" #include "common/Clock.h" +#include "common/analog/AnalogDefines.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/lc/csbk/CSBKFactory.h" #include "common/dmr/lc/LC.h" @@ -93,6 +94,7 @@ typedef std::pair StatusMapPair; std::unordered_map g_dmrStatus; std::unordered_map g_p25Status; std::unordered_map g_nxdnStatus; +std::unordered_map g_analogStatus; // --------------------------------------------------------------------------- // Global Functions @@ -237,7 +239,7 @@ bool createPeerNetwork() } // initialize networking - g_network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, true, true, true, true, true, false, true, false); + g_network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, false, true, false); g_network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, ""); g_network->setLookups(g_ridLookup, g_tidLookup); @@ -518,8 +520,8 @@ void* threadNetworkPump(void* arg) if (std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_p25Status.end()) { g_p25Status.erase(dstId); - LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X, duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId, duration / 1000); } } @@ -539,8 +541,8 @@ void* threadNetworkPump(void* arg) status.dstId = dstId; g_p25Status[dstId] = status; - LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s)", - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId); } } } @@ -583,9 +585,9 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_GRP_VCH: case P25DEF::TSBKO::IOSP_UU_VCH: { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u (%s), dstId = %u (%s)", + LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchId(), tsbk->getGrpVchNo(), - srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId); // generate a net event for this if (g_netDataEvent != nullptr) { @@ -981,6 +983,58 @@ void* threadNetworkPump(void* arg) if (g_debug) LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); } + + UInt8Array analogBuffer = g_network->readAnalog(netReadRet, length); + if (netReadRet) { + using namespace analog; + + uint32_t srcId = GET_UINT24(analogBuffer, 5U); + uint32_t dstId = GET_UINT24(analogBuffer, 8U); + + ANODEF::AudioFrameType::E frameType = (ANODEF::AudioFrameType::E)(analogBuffer[15U] & 0x0FU); + + // specifically only check the following logic for end of call, voice or data frames + if (frameType == ANODEF::AudioFrameType::TERMINATOR) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid TX_REL, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + RxStatus status = g_analogStatus[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_analogStatus.end()) { + g_analogStatus.erase(dstId); + + LogMessage(LOG_NET, "Analog, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); + } + } + + // is this a new call stream? + if (frameType == ANODEF::AudioFrameType::VOICE_START) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "Analog, invalid call, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + + auto it = std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + if (it == g_analogStatus.end()) { + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + g_analogStatus[dstId] = status; + + LogMessage(LOG_NET, "Analog, Call Start, srcId = %u (%s), dstId = %u (%s)", + srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); + } + } + + if (g_debug) + LogMessage(LOG_NET, "Analog, frameType = $%02X, srcId = %u, dstId = %u, len = %u", frameType, srcId, dstId, length); + } } if (ms < 2U) diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index cd8f174b5..5235c4a88 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -35,8 +35,8 @@ std::mutex PeerNetwork::m_peerStatusMutex; /* Initializes a new instance of the PeerNetwork class. */ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : - Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), + bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : + Network(address, port, localPort, peerId, password, duplex, debug, true, true, true, true, true, true, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), peerStatus(), m_peerLink(false), m_tgidPkt(true, "Peer-Link, TGID List"), @@ -56,7 +56,8 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc /* User overrideable handler that allows user code to process network packets not handled by this class. */ -void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId) +void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader) { switch (opcode.first) { case NET_FUNC::TRANSFER: @@ -229,7 +230,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco break; default: - Utils::dump("unknown opcode from the master", data, length); + Utils::dump("Unknown opcode from the master", data, length); break; } } @@ -295,7 +296,7 @@ bool PeerNetwork::writeConfig() ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "PeerNetwork::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId); diff --git a/src/sysview/network/PeerNetwork.h b/src/sysview/network/PeerNetwork.h index 7c1aa20a9..c4bb5daa7 100644 --- a/src/sysview/network/PeerNetwork.h +++ b/src/sysview/network/PeerNetwork.h @@ -49,17 +49,12 @@ namespace network * @param password Network authentication password. * @param duplex Flag indicating full-duplex operation. * @param debug Flag indicating whether network debug is enabled. - * @param dmr Flag indicating whether DMR is enabled. - * @param p25 Flag indicating whether P25 is enabled. - * @param nxdn Flag indicating whether NXDN is enabled. - * @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic. - * @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic. * @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network. * @param allowDiagnosticTransfer Flag indicating that the system diagnostic logs will be sent to the network. * @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. */ PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); + bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** * @brief Flag indicating whether or not SysView has received Peer-Link data transfers. @@ -88,9 +83,11 @@ namespace network * @param[in] data Buffer containing message to send to peer. * @param length Length of buffer. * @param streamId Stream ID. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. */ void userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data = nullptr, uint32_t length = 0U, - uint32_t streamId = 0U) override; + uint32_t streamId = 0U, const frame::RTPFNEHeader& fneHeader = frame::RTPFNEHeader(), const frame::RTPHeader& rtpHeader = frame::RTPHeader()) override; /** * @brief Writes configuration to the network. diff --git a/tools/colorize-fne.sh b/tools/colorize-fne.sh index 578ea6d01..bee978b3b 100755 --- a/tools/colorize-fne.sh +++ b/tools/colorize-fne.sh @@ -7,7 +7,7 @@ #* #*/ #/* -#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +#* Copyright (C) 2022-2025 by Bryan Biedenkapp N2PLL #* #* This program is free software; you can redistribute it and/or modify #* it under the terms of the GNU General Public License as published by @@ -25,10 +25,12 @@ #*/ LOG_COLOR="s#W:#\x1b[0m\x1b[1m\x1b[33m&#; s#E:#\x1b[0m\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&#; s#U:#\x1b[44m\x1b[1m\x1b[33m&#;" +DMR_COLOR="s#VOICE#\x1b[36m&#; s#TERMINATOR_WITH_LC#\x1b[0m\x1b[32m&#; s#CSBK#\x1b[0m\x1b[35m&#" P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#" -AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#;" +NXDN_COLOR="s#VCALL#\x1b[36m&#; s#TX_REL#\x1b[0m\x1b[32m&#" +AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#; s#Affiliation#\x1b[1m\x1b[36m&#;" RF_HIGHLIGHT="s#(RF)#\x1b[1m\x1b[34m&\x1b[0m#;" NET_HIGHLIGHT="s#(NET)#\x1b[1m\x1b[36m&\x1b[0m#;" -sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${P25_COLOR}; ${AFF_COLOR}" \ No newline at end of file +sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}" \ No newline at end of file diff --git a/tools/colorize-host.sh b/tools/colorize-host.sh new file mode 100755 index 000000000..f51668769 --- /dev/null +++ b/tools/colorize-host.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +#/** +#* Digital Voice Modem - Host Software +#* GPLv2 Open Source. Use is subject to license terms. +#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +#* +#*/ +#/* +#* Copyright (C) 2025 by Bryan Biedenkapp N2PLL +#* +#* This program is free software; you can redistribute it and/or modify +#* it under the terms of the GNU General Public License as published by +#* the Free Software Foundation; either version 2 of the License, or +#* (at your option) any later version. +#* +#* This program is distributed in the hope that it will be useful, +#* but WITHOUT ANY WARRANTY; without even the implied warranty of +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#* GNU General Public License for more details. +#* +#* You should have received a copy of the GNU General Public License +#* along with this program; if not, write to the Free Software +#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +#*/ +LOG_COLOR="s#W:#\x1b[1m\x1b[33m&#; s#E:#\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&\x1b[0m\x1b[1m#;" + +DMR_COLOR="s#VOICE#\x1b[36m&#; s#TERMINATOR_WITH_LC#\x1b[0m\x1b[32m&#; s#CSBK#\x1b[0m\x1b[35m&#" +P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#" +NXDN_COLOR="s#VCALL#\x1b[36m&#; s#TX_REL#\x1b[0m\x1b[32m&#" +AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#; s#Affiliation#\x1b[1m\x1b[36m&#;" + +RF_HIGHLIGHT="s#RF#\x1b[1m\x1b[35m&\x1b[0m#;" +NET_HIGHLIGHT="s#NET#\x1b[1m\x1b[36m&\x1b[0m#;" + +sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}"