From 4239d21a2b5ecfec881a1d39ad1a71f45fa733c4 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 1 Apr 2022 23:21:32 -0400 Subject: [PATCH] initial experimental support for DFSI communication via DVM modem serial interface (DFSI support is disabled from compilation entirely by default, the -DENABLE_DFSI_SUPPORT compiler directive is required to enable it); --- DVMHost.vcxproj | 6 +- DVMHost.vcxproj.filters | 14 +- Makefile | 2 + host/Host.cpp | 20 + host/Host.h | 1 + modem/Modem.cpp | 2 +- p25/Control.cpp | 214 +++++- p25/Control.h | 9 + p25/TrunkPacket.cpp | 10 +- p25/TrunkPacket.h | 20 +- p25/VoicePacket.cpp | 2 +- p25/VoicePacket.h | 18 +- p25/dfsi/DFSITrunkPacket.cpp | 211 ++++++ p25/dfsi/DFSITrunkPacket.h | 87 +++ p25/dfsi/DFSIVoicePacket.cpp | 1246 ++++++++++++++++++++++++++++++++++ p25/dfsi/DFSIVoicePacket.h | 92 +++ 16 files changed, 1921 insertions(+), 33 deletions(-) create mode 100644 p25/dfsi/DFSITrunkPacket.cpp create mode 100644 p25/dfsi/DFSITrunkPacket.h create mode 100644 p25/dfsi/DFSIVoicePacket.cpp create mode 100644 p25/dfsi/DFSIVoicePacket.h diff --git a/DVMHost.vcxproj b/DVMHost.vcxproj index e14b1284..bdbb9072 100644 --- a/DVMHost.vcxproj +++ b/DVMHost.vcxproj @@ -213,7 +213,9 @@ + + @@ -291,7 +293,9 @@ + + @@ -320,7 +324,7 @@ - + true Document true diff --git a/DVMHost.vcxproj.filters b/DVMHost.vcxproj.filters index a4d1b4a9..8cb4b8dc 100644 --- a/DVMHost.vcxproj.filters +++ b/DVMHost.vcxproj.filters @@ -377,6 +377,12 @@ Header Files\p25\dfsi + + Header Files\p25\dfsi + + + Header Files\p25\dfsi + @@ -598,6 +604,12 @@ Source Files\p25\dfsi + + Source Files\p25\dfsi + + + Source Files\p25\dfsi + @@ -606,9 +618,9 @@ - + \ No newline at end of file diff --git a/Makefile b/Makefile index 54951389..68d12e62 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,8 @@ HOST_OBJECTS = \ p25/data/DataHeader.o \ p25/data/LowSpeedData.o \ p25/dfsi/LC.o \ + p25/dfsi/DFSITrunkPacket.o \ + p25/dfsi/DFSIVoicePacket.o \ p25/edac/Trellis.o \ p25/lc/LC.o \ p25/lc/TDULC.o \ diff --git a/host/Host.cpp b/host/Host.cpp index 4c3bb9ad..f9755697 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -504,6 +504,14 @@ int Host::run() g_killed = true; } +#if ENABLE_DFSI_SUPPORT + // DFSI checks + if (m_useDFSI && m_dmrEnabled) { + ::LogError(LOG_HOST, "Cannot have DMR enabled when using DFSI!"); + g_killed = true; + } +#endif + // P25 control channel checks if (m_dmrEnabled && m_p25CtrlChannel) { ::LogError(LOG_HOST, "Cannot have DMR enabled when using dedicated P25 control!"); @@ -1497,6 +1505,11 @@ bool Host::createModem() yaml::Node modemProtocol = modemConf["protocol"]; std::string portType = modemProtocol["type"].as("null"); +#if NO_NO_FEATURE + m_useDFSI = modemProtocol["dfsi"].as(false); +#else + m_useDFSI = false; +#endif yaml::Node uartProtocol = modemProtocol["uart"]; std::string uartPort = uartProtocol["port"].as(); @@ -1662,6 +1675,10 @@ bool Host::createModem() LogInfo(" Packet Playout Time: %u ms", packetPlayoutTime); LogInfo(" Disable Overflow Reset: %s", disableOFlowReset ? "yes" : "no"); + if (m_useDFSI) { + LogInfo(" Digital Fixed Station Interface: yes"); + } + if (ignoreModemConfigArea) { LogInfo(" Ignore Modem Configuration Area: yes"); } @@ -1679,6 +1696,9 @@ bool Host::createModem() m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, rfPower, dmrDiscBWAdj, p25DiscBWAdj, dmrPostBWAdj, p25PostBWAdj, adfGainMode); m_modem->setDMRColorCode(m_dmrColorCode); m_modem->setP25NAC(m_p25NAC); +#ifdef ENABLE_DFSI_SUPPORT + m_modem->setP25DFSI(m_useDFSI); +#endif if (m_modemRemote) { m_modem->setOpenHandler(MODEM_OC_PORT_HANDLER_BIND(Host::rmtPortModemOpen, this)); diff --git a/host/Host.h b/host/Host.h index 6cd841a5..6bbff26b 100644 --- a/host/Host.h +++ b/host/Host.h @@ -88,6 +88,7 @@ private: bool m_duplex; bool m_fixedMode; + bool m_useDFSI; uint32_t m_timeout; uint32_t m_rfModeHang; diff --git a/modem/Modem.cpp b/modem/Modem.cpp index 134519b7..c93b6753 100644 --- a/modem/Modem.cpp +++ b/modem/Modem.cpp @@ -76,7 +76,7 @@ using namespace modem; } // Check flash configuration value against class value. -#define FLASH_VALUE_CHECK_FLOAT(_CLASS_VAL, _FLASH_VAL, _DEFAULT, _STR) \ +#define FLASH_VALUE_CHECK_FLOAT(_CLASS_VAL, _FLASH_VAL, _DEFAULT, _STR) \ if (_CLASS_VAL == _DEFAULT && _CLASS_VAL != _FLASH_VAL) { \ LogWarning(LOG_MODEM, CONFIG_OPT_MISMATCH_STR MODEM_CONFIG_AREA_DISAGREE_STR _STR " = %f, " _STR " (flash) = %f", _CLASS_VAL, _FLASH_VAL); \ _CLASS_VAL = _FLASH_VAL; \ diff --git a/p25/Control.cpp b/p25/Control.cpp index 3abb6347..5ace0bb9 100644 --- a/p25/Control.cpp +++ b/p25/Control.cpp @@ -32,6 +32,8 @@ #include "p25/P25Defines.h" #include "p25/Control.h" #include "p25/acl/AccessControl.h" +#include "p25/dfsi/DFSITrunkPacket.h" +#include "p25/dfsi/DFSIVoicePacket.h" #include "p25/P25Utils.h" #include "p25/Sync.h" #include "edac/CRC.h" @@ -140,9 +142,22 @@ Control::Control(uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Mod m_hangCount = callHang * 4U; +#if ENABLE_DFSI_SUPPORT + if (m_modem->isP25DFSI()) { + LogMessage(LOG_P25, "DFSI protocol mode is enabled."); + m_voice = new dfsi::DFSIVoicePacket(this, network, debug, verbose); + m_trunk = new dfsi::DFSITrunkPacket(this, network, dumpTSBKData, debug, verbose); + } + else { + m_voice = new VoicePacket(this, network, debug, verbose); + m_trunk = new TrunkPacket(this, network, dumpTSBKData, debug, verbose); + m_data = new DataPacket(this, network, dumpPDUData, repeatPDU, debug, verbose); + } +#else m_voice = new VoicePacket(this, network, debug, verbose); - m_data = new DataPacket(this, network, dumpPDUData, repeatPDU, debug, verbose); m_trunk = new TrunkPacket(this, network, dumpTSBKData, debug, verbose); + m_data = new DataPacket(this, network, dumpPDUData, repeatPDU, debug, verbose); +#endif } /// @@ -150,9 +165,17 @@ Control::Control(uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Mod /// Control::~Control() { - delete m_voice; - delete m_data; - delete m_trunk; + if (m_voice != NULL) { + delete m_voice; + } + + if (m_trunk != NULL) { + delete m_trunk; + } + + if (m_data != NULL) { + delete m_data; + } } /// @@ -162,8 +185,14 @@ void Control::reset() { m_rfState = RS_RF_LISTENING; - m_voice->resetRF(); - m_data->resetRF(); + if (m_voice != NULL) { + m_voice->resetRF(); + } + + if (m_data != NULL) { + m_data->resetRF(); + } + m_queue.clear(); } @@ -216,6 +245,12 @@ void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const s m_ackTSBKRequests = control["ackRequests"].as(true); m_trunk->m_ctrlTSDUMBF = !control["disableTSDUMBF"].as(false); +#if ENABLE_DFSI_SUPPORT + if (m_modem->isP25DFSI()) { + m_trunk->m_ctrlTSDUMBF = true; // force SBF for TSDUs when using DFSI + } +#endif + m_voice->m_silenceThreshold = p25Protocol["silenceThreshold"].as(p25::DEFAULT_SILENCE_THRESHOLD); if (m_voice->m_silenceThreshold > MAX_P25_VOICE_ERRORS) { LogWarning(LOG_P25, "Silence threshold > %u, defaulting to %u", p25::MAX_P25_VOICE_ERRORS, p25::DEFAULT_SILENCE_THRESHOLD); @@ -314,6 +349,12 @@ bool Control::processFrame(uint8_t* data, uint32_t len) { assert(data != NULL); +#if ENABLE_DFSI_SUPPORT + if (m_modem->isP25DFSI()) { + return processDFSI(data, len); + } +#endif + bool sync = data[1U] == 0x01U; if (data[0U] == modem::TAG_LOST && m_rfState == RS_RF_AUDIO) { @@ -758,6 +799,160 @@ void Control::writeQueueNet(const uint8_t* data, uint32_t length) m_queue.addData(data, len); } +#if ENABLE_DFSI_SUPPORT +/// +/// Process a DFSI data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Control::processDFSI(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + dfsi::LC dfsiLC = dfsi::LC(); + + if (data[0U] == modem::TAG_LOST && m_rfState == RS_RF_AUDIO) { + if (m_rssi != 0U) { + ::ActivityLog("P25", true, "transmission lost, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", + float(m_voice->m_rfFrames) / 5.56F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + } + else { + ::ActivityLog("P25", true, "transmission lost, %.1f seconds, BER: %.1f%%", + float(m_voice->m_rfFrames) / 5.56F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); + } + + LogMessage(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); + + if (m_control) { + m_trunk->releaseDstIdGrant(m_voice->m_rfLC.getDstId(), false); + } + + writeRF_TDU(false); + m_voice->m_lastDUID = P25_DUID_TDU; + m_voice->writeNetworkRF(data + 2U, P25_DUID_TDU); + + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + m_rfTGHang.stop(); + + m_tailOnIdle = true; + + m_rfTimeout.stop(); + m_queue.clear(); + + if (m_network != NULL) + m_network->resetP25(); + + return false; + } + + if (data[0U] == modem::TAG_LOST && m_rfState == RS_RF_DATA) { + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + m_rfTGHang.stop(); + + m_tailOnIdle = true; + + m_data->resetRF(); + + m_rfTimeout.stop(); + m_queue.clear(); + + return false; + } + + if (data[0U] == modem::TAG_LOST) { + m_rfState = RS_RF_LISTENING; + + m_voice->resetRF(); + m_data->resetRF(); + + m_trunk->m_rfTSBK = lc::TSBK(m_siteData, m_idenEntry); + + return false; + } + + // Decode the NID + bool valid = dfsiLC.decodeNID(data + 2U); + + if (!valid && m_rfState == RS_RF_LISTENING) + return false; + + uint8_t duid = 0xFFU; // a very invalid DUID + uint8_t frameType = dfsiLC.getFrameType(); + + if (m_debug) { + LogDebug(LOG_RF, "P25 DFSI, rfState = %u, netState = %u, frameType = %u", m_rfState, m_netState, dfsiLC.getFrameType()); + } + + // are we interrupting a running CC? + if (m_ccRunning) { + if (duid != P25_DUID_TSDU) { + g_interruptP25Control = true; + } + } + + // convert DFSI frame-types to DUIDs (this doesn't 100% line up) + if (frameType == dfsi::P25_DFSI_START_STOP || frameType == dfsi::P25_DFSI_VHDR1 || + frameType == dfsi::P25_DFSI_VHDR2) { + duid = P25_DUID_HDU; + } + else if (frameType >= dfsi::P25_DFSI_LDU1_VOICE1 && frameType <= dfsi::P25_DFSI_LDU1_VOICE9) { + duid = P25_DUID_LDU1; + } + else if (frameType >= dfsi::P25_DFSI_LDU2_VOICE10 && frameType <= dfsi::P25_DFSI_LDU2_VOICE18) { + duid = P25_DUID_LDU2; + } + else if (frameType == dfsi::P25_DFSI_TSBK) { + duid = P25_DUID_TSDU; + } + + bool ret = false; + + // handle individual DUIDs + switch (duid) { + case P25_DUID_HDU: + case P25_DUID_LDU1: + case P25_DUID_LDU2: + if (!m_dedicatedControl) + ret = m_voice->process(data, len); + else { + if (m_voiceOnControl && m_trunk->isChBusy(m_siteData.channelNo())) { + ret = m_voice->process(data, len); + } + } + break; + + case P25_DUID_TDU: + case P25_DUID_TDULC: + ret = m_voice->process(data, len); + break; + + case P25_DUID_PDU: + if (!m_dedicatedControl) + ret = m_data->process(data, len); + else { + if (m_voiceOnControl && m_trunk->isChBusy(m_siteData.channelNo())) { + ret = m_data->process(data, len); + } + } + break; + + case P25_DUID_TSDU: + ret = m_trunk->process(data, len); + break; + + default: + LogError(LOG_RF, "P25 unhandled DUID, duid = $%02X", duid); + return false; + } + + return ret; +} +#endif + /// /// Process a data frames from the network. /// @@ -883,6 +1078,13 @@ void Control::writeRF_Preamble(uint32_t preambleCount, bool force) /// void Control::writeRF_TDU(bool noNetwork) { +#ifdef ENABLE_DFSI_SUPPORT + // for now abort out of this... + if (m_modem->isP25DFSI()) { + return; + } +#endif + uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); diff --git a/p25/Control.h b/p25/Control.h index 3eaef324..afa71bde 100644 --- a/p25/Control.h +++ b/p25/Control.h @@ -56,8 +56,10 @@ namespace p25 // Class Prototypes // --------------------------------------------------------------------------- class HOST_SW_API VoicePacket; + namespace dfsi { class HOST_SW_API DFSIVoicePacket; } class HOST_SW_API DataPacket; class HOST_SW_API TrunkPacket; + namespace dfsi { class HOST_SW_API DFSITrunkPacket; } // --------------------------------------------------------------------------- // Class Declaration @@ -112,10 +114,12 @@ namespace p25 private: friend class VoicePacket; + friend class dfsi::DFSIVoicePacket; VoicePacket* m_voice; friend class DataPacket; DataPacket* m_data; friend class TrunkPacket; + friend class dfsi::DFSITrunkPacket; TrunkPacket* m_trunk; uint32_t m_nac; @@ -184,6 +188,11 @@ namespace p25 /// Write data processed from the network to the data ring buffer. void writeQueueNet(const uint8_t* data, uint32_t length); +#if ENABLE_DFSI_SUPPORT + /// Process a DFSI data frame from the RF interface. + bool processDFSI(uint8_t* data, uint32_t len); +#endif + /// Process a data frames from the network. void processNetwork(); diff --git a/p25/TrunkPacket.cpp b/p25/TrunkPacket.cpp index 5f0a381f..cedb5379 100644 --- a/p25/TrunkPacket.cpp +++ b/p25/TrunkPacket.cpp @@ -158,9 +158,9 @@ void TrunkPacket::resetNet() /// /// Buffer containing data frame. /// Length of data frame. -/// Flag indicating the TSBK data is pre-decoded TSBK data. +/// Flag indicating the TSBK data is pre-decoded TSBK data. /// -bool TrunkPacket::process(uint8_t* data, uint32_t len, bool mbtDecoded) +bool TrunkPacket::process(uint8_t* data, uint32_t len, bool preDecoded) { assert(data != NULL); @@ -168,7 +168,7 @@ bool TrunkPacket::process(uint8_t* data, uint32_t len, bool mbtDecoded) return false; uint8_t duid = 0U; - if (!mbtDecoded) { + if (!preDecoded) { // Decode the NID bool valid = m_p25->m_nid.decode(data + 2U); @@ -190,7 +190,7 @@ bool TrunkPacket::process(uint8_t* data, uint32_t len, bool mbtDecoded) m_p25->m_queue.clear(); - if (!mbtDecoded) { + if (!preDecoded) { resetRF(); resetNet(); @@ -1243,7 +1243,7 @@ void TrunkPacket::setTSBKVerbose(bool verbose) } // --------------------------------------------------------------------------- -// Private Class Members +// Protected Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the TrunkPacket class. diff --git a/p25/TrunkPacket.h b/p25/TrunkPacket.h index ce547c08..a29092ec 100644 --- a/p25/TrunkPacket.h +++ b/p25/TrunkPacket.h @@ -47,6 +47,7 @@ namespace p25 // Class Prototypes // --------------------------------------------------------------------------- class HOST_SW_API VoicePacket; + namespace dfsi { class HOST_SW_API DFSIVoicePacket; } class HOST_SW_API DataPacket; class HOST_SW_API Control; @@ -58,14 +59,14 @@ namespace p25 class HOST_SW_API TrunkPacket { public: /// Resets the data states for the RF interface. - void resetRF(); + virtual void resetRF(); /// Resets the data states for the network. - void resetNet(); + virtual void resetNet(); /// Process a data frame from the RF interface. - bool process(uint8_t* data, uint32_t len, bool mbtDecoded = false); + virtual bool process(uint8_t* data, uint32_t len, bool preDecoded = false); /// Process a data frame from the network. - bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + virtual bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); /// Helper used to process AMBTs from PDU data. bool processMBT(data::DataHeader dataHeader, data::DataBlock* blocks); @@ -109,8 +110,9 @@ namespace p25 /// Helper to change the TSBK verbose state. void setTSBKVerbose(bool verbose); - private: + protected: friend class VoicePacket; + friend class dfsi::DFSIVoicePacket; friend class DataPacket; friend class Control; Control* m_p25; @@ -166,7 +168,7 @@ namespace p25 /// Initializes a new instance of the TrunkPacket class. TrunkPacket(Control* p25, network::BaseNetwork* network, bool dumpTSBKData, bool debug, bool verbose); /// Finalizes a instance of the TrunkPacket class. - ~TrunkPacket(); + virtual ~TrunkPacket(); /// Write data processed from RF to the network. void writeNetworkRF(const uint8_t* data, bool autoReset); @@ -180,7 +182,7 @@ namespace p25 void writeRF_TDULC_ChanRelease(bool grp, uint32_t srcId, uint32_t dstId); /// Helper to write a single-block P25 TSDU packet. - void writeRF_TSDU_SBF(bool noNetwork, bool clearBeforeWrite = false, bool force = false); + virtual void writeRF_TSDU_SBF(bool noNetwork, bool clearBeforeWrite = false, bool force = false); /// Helper to write a multi-block (3-block) P25 TSDU packet. void writeRF_TSDU_MBF(bool clearBeforeWrite = false); @@ -213,9 +215,9 @@ namespace p25 void writeNet_TSDU_From_RF(uint8_t* data); /// Helper to write a network P25 TDU w/ link control packet. - void writeNet_TDULC(lc::TDULC lc); + virtual void writeNet_TDULC(lc::TDULC lc); /// Helper to write a network single-block P25 TSDU packet. - void writeNet_TSDU(); + virtual void writeNet_TSDU(); /// Helper to automatically inhibit a source ID on a denial. void denialInhibit(uint32_t srcId); diff --git a/p25/VoicePacket.cpp b/p25/VoicePacket.cpp index c60ab3bf..e047473e 100644 --- a/p25/VoicePacket.cpp +++ b/p25/VoicePacket.cpp @@ -871,7 +871,7 @@ bool VoicePacket::writeEndRF() } // --------------------------------------------------------------------------- -// Private Class Members +// Protected Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the VoicePacket class. diff --git a/p25/VoicePacket.h b/p25/VoicePacket.h index b24a5646..b6b1ddf7 100644 --- a/p25/VoicePacket.h +++ b/p25/VoicePacket.h @@ -58,19 +58,19 @@ namespace p25 class HOST_SW_API VoicePacket { public: /// Resets the data states for the RF interface. - void resetRF(); + virtual void resetRF(); /// Resets the data states for the network. - void resetNet(); + virtual void resetNet(); /// Process a data frame from the RF interface. - bool process(uint8_t* data, uint32_t len); + virtual bool process(uint8_t* data, uint32_t len); /// Process a data frame from the network. - bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + virtual bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); /// Helper to write end of frame data. bool writeEndRF(); - private: + protected: friend class TrunkPacket; friend class Control; Control* m_p25; @@ -119,7 +119,7 @@ namespace p25 /// Initializes a new instance of the VoicePacket class. VoicePacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose); /// Finalizes a instance of the VoicePacket class. - ~VoicePacket(); + virtual ~VoicePacket(); /// Write data processed from RF to the network. void writeNetworkRF(const uint8_t* data, uint8_t duid); @@ -128,15 +128,15 @@ namespace p25 void writeRF_EndOfVoice(); /// Helper to write a network P25 TDU packet. - void writeNet_TDU(); + virtual void writeNet_TDU(); /// Helper to check for an unflushed LDU1 packet. void checkNet_LDU1(); /// Helper to write a network P25 LDU1 packet. - void writeNet_LDU1(); + virtual void writeNet_LDU1(); /// Helper to check for an unflushed LDU2 packet. void checkNet_LDU2(); /// Helper to write a network P25 LDU1 packet. - void writeNet_LDU2(); + virtual void writeNet_LDU2(); /// Helper to insert IMBE silence frames for missing audio. void insertMissingAudio(uint8_t* data); diff --git a/p25/dfsi/DFSITrunkPacket.cpp b/p25/dfsi/DFSITrunkPacket.cpp new file mode 100644 index 00000000..ccaafb31 --- /dev/null +++ b/p25/dfsi/DFSITrunkPacket.cpp @@ -0,0 +1,211 @@ +/** +* 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. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2022 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. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/dfsi/DFSIDefines.h" +#include "p25/dfsi/DFSITrunkPacket.h" +#include "p25/P25Utils.h" +#include "p25/Sync.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; +using namespace p25::dfsi; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Resets the data states for the RF interface. +/// +void DFSITrunkPacket::resetRF() +{ + TrunkPacket::resetRF(); + LC lc = LC(); + m_rfDFSILC = lc; +} + +/// +/// Resets the data states for the network. +/// +void DFSITrunkPacket::resetNet() +{ + TrunkPacket::resetNet(); + LC lc = LC(); + m_netDFSILC = lc; +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// Flag indicating the TSBK data is pre-decoded TSBK data. +/// +bool DFSITrunkPacket::process(uint8_t* data, uint32_t len, bool preDecoded) +{ + assert(data != NULL); + + uint8_t tsbk[P25_TSBK_LENGTH_BYTES]; + ::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES); + + if (!m_p25->m_control) + return false; + + if (preDecoded) { + return TrunkPacket::process(data + 2U, len, preDecoded); + } + else { + resetRF(); + resetNet(); + + if (m_rfDFSILC.decodeTSBK(data + 2U)) { + m_rfTSBK = m_rfDFSILC.tsbk(); + return TrunkPacket::process(tsbk, P25_TSBK_LENGTH_BYTES, true); + } + } + + return false; +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DFSITrunkPacket class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether TSBK data is dumped to the log. +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. +DFSITrunkPacket::DFSITrunkPacket(Control* p25, network::BaseNetwork* network, bool dumpTSBKData, bool debug, bool verbose) : + TrunkPacket(p25, network, dumpTSBKData, debug, verbose) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the DFSITrunkPacket class. +/// +DFSITrunkPacket::~DFSITrunkPacket() +{ + /* stub */ +} + +/// +/// Helper to write a P25 TDU w/ link control packet. +/// +/// +/// +void DFSITrunkPacket::writeRF_TDULC(lc::TDULC lc, bool noNetwork) +{ + // for now this is ignored... +} + +/// +/// Helper to write a single-block P25 TSDU packet. +/// +/// +/// +/// +void DFSITrunkPacket::writeRF_TSDU_SBF(bool noNetwork, bool clearBeforeWrite, bool force) +{ + if (!m_p25->m_control) + return; + + uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); + + m_rfDFSILC.tsbk(m_rfTSBK); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_p25->m_nid.encode(data + 2U, P25_DUID_TSDU); + + // Generate TSBK block + m_rfTSBK.setLastBlock(true); // always set last block -- this a Single Block TSDU + m_rfTSBK.encode(data + 2U); + + if (m_debug) { + LogDebug(LOG_RF, P25_TSDU_STR " DFSI, lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", + m_rfTSBK.getLCO(), m_rfTSBK.getMFId(), m_rfTSBK.getLastBlock(), m_rfTSBK.getAIV(), m_rfTSBK.getEX(), m_rfTSBK.getSrcId(), m_rfTSBK.getDstId(), + m_rfTSBK.getSysId(), m_rfTSBK.getNetId()); + + Utils::dump(1U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES + 2U, P25_TSBK_FEC_LENGTH_BYTES); + } + + // Add busy bits + m_p25->addBusyBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, true, false); + + // Set first busy bits to 1,1 + m_p25->setBusyBits(data + 2U, P25_SS0_START, true, true); + + if (!noNetwork) + writeNetworkRF(data + 2U, true); + + if (!force) { + if (clearBeforeWrite) { + m_p25->m_modem->clearP25Data(); + m_p25->m_queue.clear(); + } + } + + ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); + + // Generate DFSI TSBK block + m_rfDFSILC.encodeTSBK(data + 2U); + + if (m_p25->m_duplex) { + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_p25->writeQueueRF(data, P25_DFSI_TSBK_FRAME_LENGTH_BYTES + 2U); + } +} + +/// +/// Helper to write a network single-block P25 TSDU packet. +/// +void DFSITrunkPacket::writeNet_TSDU() +{ + uint8_t buffer[P25_DFSI_TSBK_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_DFSI_TSBK_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + + // Regenerate TSDU Data + m_netDFSILC.tsbk(m_netTSBK); + m_netDFSILC.encodeTSBK(buffer + 2U); + + m_p25->writeQueueNet(buffer, P25_DFSI_TSBK_FRAME_LENGTH_BYTES + 2U); + + if (m_network != NULL) + m_network->resetP25(); +} diff --git a/p25/dfsi/DFSITrunkPacket.h b/p25/dfsi/DFSITrunkPacket.h new file mode 100644 index 00000000..1a23c3fd --- /dev/null +++ b/p25/dfsi/DFSITrunkPacket.h @@ -0,0 +1,87 @@ +/** +* 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. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2022 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. +*/ +#if !defined(__P25_DFSI_TRUNK_PACKET_H__) +#define __P25_DFSI_TRUNK_PACKET_H__ + +#include "Defines.h" +#include "p25/dfsi/LC.h" +#include "p25/TrunkPacket.h" +#include "p25/Control.h" +#include "network/BaseNetwork.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API TrunkPacket; + class HOST_SW_API Control; + + namespace dfsi + { + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for P25 trunking packets using + // the DFSI protocol instead of the P25 OTA protocol. + // --------------------------------------------------------------------------- + + class HOST_SW_API DFSITrunkPacket : public TrunkPacket { + public: + /// Resets the data states for the RF interface. + virtual void resetRF(); + /// Resets the data states for the network. + virtual void resetNet(); + + /// Process a data frame from the RF interface. + virtual bool process(uint8_t* data, uint32_t len, bool preDecoded = false); + + protected: + friend class DFSIVoicePacket; + friend class Control; + + LC m_rfDFSILC; + LC m_netDFSILC; + + /// Initializes a new instance of the DFSITrunkPacket class. + DFSITrunkPacket(Control* p25, network::BaseNetwork* network, bool dumpTSBKData, bool debug, bool verbose); + /// Finalizes a instance of the DFSITrunkPacket class. + virtual ~DFSITrunkPacket(); + + /// Helper to write a P25 TDU w/ link control packet. + virtual void writeRF_TDULC(lc::TDULC lc, bool noNetwork); + + /// Helper to write a single-block P25 TSDU packet. + virtual void writeRF_TSDU_SBF(bool noNetwork, bool clearBeforeWrite = false, bool force = false); + + /// Helper to write a network P25 TDU w/ link control packet. + //virtual void writeNet_TDULC(lc::TDULC lc); + /// Helper to write a network single-block P25 TSDU packet. + virtual void writeNet_TSDU(); + }; + } // namespace dfsi +} // namespace p25 + +#endif // __P25_DFSI_TRUNK_PACKET_H__ diff --git a/p25/dfsi/DFSIVoicePacket.cpp b/p25/dfsi/DFSIVoicePacket.cpp new file mode 100644 index 00000000..247e2dd0 --- /dev/null +++ b/p25/dfsi/DFSIVoicePacket.cpp @@ -0,0 +1,1246 @@ +/** +* 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. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 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. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/acl/AccessControl.h" +#include "p25/dfsi/DFSIDefines.h" +#include "p25/dfsi/DFSIVoicePacket.h" +#include "p25/P25Utils.h" +#include "p25/Sync.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; +using namespace p25::dfsi; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t VOC_LDU1_COUNT = 3U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Resets the data states for the RF interface. +/// +void DFSIVoicePacket::resetRF() +{ + VoicePacket::resetRF(); + + LC lc = LC(); + m_rfDFSILC = lc; +} + +/// +/// Resets the data states for the network. +/// +void DFSIVoicePacket::resetNet() +{ + VoicePacket::resetNet(); + + LC lc = LC(); + m_netDFSILC = lc; +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool DFSIVoicePacket::process(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + bool valid = m_rfDFSILC.decodeNID(data + 2U); + + if (m_p25->m_rfState == RS_RF_LISTENING && !valid) + return false; + + uint8_t frameType = m_rfDFSILC.getFrameType(); + if (frameType == P25_DFSI_VHDR2) { + if (m_p25->m_rfState == RS_RF_LISTENING && m_p25->m_ccRunning) { + m_p25->m_modem->clearP25Data(); + m_p25->m_queue.clear(); + resetRF(); + resetNet(); + } + + if (m_p25->m_rfState == RS_RF_LISTENING || m_p25->m_rfState == RS_RF_AUDIO) { + resetRF(); + resetNet(); + + bool ret = m_rfDFSILC.decodeVHDR2(data + 2U); + if (!ret) { + LogWarning(LOG_RF, P25_HDU_STR " DFSI, undecodable LC"); + m_rfUndecodableLC++; + return false; + } + + m_rfLC = m_rfDFSILC.control(); + + if (m_verbose) { + LogMessage(LOG_RF, P25_HDU_STR " DFSI, HDU_BSDWNACT, dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); + } + + // 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 && m_rfLC.getDstId() == m_p25->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + return false; + } + + // stop network frames from processing -- RF wants to transmit on a different talkgroup + if (m_p25->m_netState != RS_NET_IDLE) { + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", m_rfLC.getDstId(), + m_p25->m_netLastDstId); + resetNet(); + } + + m_p25->m_rfTGHang.start(); + m_p25->m_rfLastDstId = m_rfLC.getDstId(); + + m_rfLastHDU = m_rfLC; + } + + return true; + } + else if (frameType >= P25_DFSI_LDU1_VOICE1 && frameType <= P25_DFSI_LDU1_VOICE9) { + uint8_t n = 10U; + switch (frameType) { + case P25_DFSI_LDU1_VOICE1: + n = 10U; + break; + case P25_DFSI_LDU1_VOICE2: + n = 26U; + break; + case P25_DFSI_LDU1_VOICE3: + n = 55U; + break; + case P25_DFSI_LDU1_VOICE4: + n = 80U; + break; + case P25_DFSI_LDU1_VOICE5: + n = 105U; + break; + case P25_DFSI_LDU1_VOICE6: + n = 130U; + break; + case P25_DFSI_LDU1_VOICE7: + n = 155U; + break; + case P25_DFSI_LDU1_VOICE8: + n = 180U; + break; + case P25_DFSI_LDU1_VOICE9: + n = 204U; + break; + } + + if (m_rfDFSILC.decodeLDU1(data + 2U, m_dfsiLDU1 + n)) { + // if this is the last LDU1 frame process the full LDU1 + if (frameType == P25_DFSI_LDU1_VOICE9) { + bool alreadyDecoded = false; + m_lastDUID = P25_DUID_LDU1; + + if (m_p25->m_rfState == RS_RF_LISTENING) { + if (m_p25->m_control) { + if (!m_p25->m_ccRunning && m_p25->m_voiceOnControl) { + m_p25->m_trunk->writeRF_ControlData(255U, 0U, false); + } + } + + lc::LC lc = m_rfDFSILC.control(); + + uint32_t srcId = lc.getSrcId(); + uint32_t dstId = lc.getDstId(); + bool group = lc.getGroup(); + bool encrypted = lc.getEncrypted(); + + alreadyDecoded = 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(); + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + 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 RF 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(); + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + 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); + resetNet(); + } + } + + m_p25->m_trunk->m_rfTSBK = lc::TSBK(&lc); + m_p25->m_trunk->m_rfTSBK.setVerbose(m_p25->m_trunk->m_dumpTSBK); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + if (m_lastRejectId == 0U || m_lastRejectId != srcId) { + LogWarning(LOG_RF, P25_HDU_STR " denial, RID rejection, srcId = %u", srcId); + if (m_p25->m_control) { + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_VALID, (group ? TSBK_IOSP_GRP_VCH : TSBK_IOSP_UU_VCH)); + m_p25->m_trunk->denialInhibit(srcId); + } + + ::ActivityLog("P25", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_p25->m_rfLastDstId = 0U; + m_p25->m_rfTGHang.stop(); + m_p25->m_rfState = RS_RF_REJECTED; + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + + // is this a group or individual operation? + if (!group) { + // validate the target RID + if (!acl::AccessControl::validateSrcId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, P25_HDU_STR " denial, RID rejection, dstId = %u", dstId); + if (m_p25->m_control) { + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_TGT_UNIT_NOT_VALID, TSBK_IOSP_UU_VCH); + } + + ::ActivityLog("P25", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_p25->m_rfLastDstId = 0U; + m_p25->m_rfTGHang.stop(); + m_p25->m_rfState = RS_RF_REJECTED; + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + } + else { + // validate the target ID, if the target is a talkgroup + if (!acl::AccessControl::validateTGId(dstId)) { + if (m_lastRejectId == 0 || m_lastRejectId != dstId) { + LogWarning(LOG_RF, P25_HDU_STR " denial, TGID rejection, dstId = %u", dstId); + if (m_p25->m_control) { + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_TGT_GROUP_NOT_VALID, TSBK_IOSP_GRP_VCH); + } + + ::ActivityLog("P25", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = dstId; + } + + m_p25->m_rfLastDstId = 0U; + m_p25->m_rfTGHang.stop(); + m_p25->m_rfState = RS_RF_REJECTED; + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + } + + // verify the source RID is affiliated to the group TGID; only if control data + // is supported + if (group && m_p25->m_control) { + if (!m_p25->m_trunk->hasSrcIdGrpAff(srcId, dstId) && m_p25->m_trunk->m_verifyAff) { + if (m_lastRejectId == 0 || m_lastRejectId != srcId) { + LogWarning(LOG_RF, P25_HDU_STR " denial, RID not affiliated to TGID, srcId = %u, dstId = %u", srcId, dstId); + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_AUTH, TSBK_IOSP_GRP_VCH); + m_p25->m_trunk->writeRF_TSDU_U_Reg_Cmd(srcId); + + ::ActivityLog("P25", true, "RF voice rejection from %u to %s%u ", srcId, group ? "TG " : "", dstId); + m_lastRejectId = srcId; + } + + m_p25->m_rfLastDstId = 0U; + m_p25->m_rfTGHang.stop(); + m_p25->m_rfState = RS_RF_REJECTED; + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + } + + m_rfLC = lc; + m_rfLastLDU1 = m_rfLC; + + m_lastRejectId = 0U; + ::ActivityLog("P25", true, "RF %svoice transmission from %u to %s%u", encrypted ? "encrypted " : "", srcId, group ? "TG " : "", dstId); + + if (m_p25->m_control) { + if (group && (m_lastPatchGroup != dstId) && + (dstId != m_p25->m_trunk->m_patchSuperGroup)) { + m_p25->m_trunk->writeRF_TSDU_Mot_Patch(dstId, 0U, 0U); + m_lastPatchGroup = dstId; + } + + // if the group wasn't granted out -- explicitly grant the group + if (!m_p25->m_trunk->hasDstIdGranted(dstId)) { + if (m_p25->m_legacyGroupGrnt) { + // are we auto-registering legacy radios to groups? + if (m_p25->m_legacyGroupReg && group) { + if (!m_p25->m_trunk->hasSrcIdGrpAff(srcId, dstId)) { + if (!m_p25->m_trunk->writeRF_TSDU_Grp_Aff_Rsp(srcId, dstId)) { + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + } + } + + if (!m_p25->m_trunk->writeRF_TSDU_Grant(group)) { + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + } + else { + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return false; + } + } + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + m_p25->m_ccRunning = false; // otherwise the grant will be bundled with other packets + m_p25->m_trunk->writeRF_TSDU_Grant(group, true); + } + + m_hadVoice = true; + + m_p25->m_rfState = RS_RF_AUDIO; + + m_p25->m_rfTGHang.start(); + m_p25->m_rfLastDstId = dstId; + + // make sure we actually got a HDU -- otherwise treat the call as a late entry + if (m_rfLastHDU.getDstId() != 0U) { + // copy destination and encryption parameters from the last HDU received (if possible) + if (m_rfLC.getDstId() != m_rfLastHDU.getDstId()) { + m_rfLC.setDstId(m_rfLastHDU.getDstId()); + } + + m_rfLC.setAlgId(m_rfLastHDU.getAlgId()); + m_rfLC.setKId(m_rfLastHDU.getKId()); + + uint8_t mi[P25_MI_LENGTH_BYTES]; + m_rfLastHDU.getMI(mi); + m_rfLC.setMI(mi); + + uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_HDU); + + // Generate HDU + m_rfLC.encodeHDU(buffer + 2U); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false, true); + + writeNetworkRF(buffer, P25_DUID_HDU); + + if (m_verbose) { + LogMessage(LOG_RF, P25_HDU_STR " DFSI, dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); + } + } + else { + LogWarning(LOG_RF, P25_HDU_STR " DFSI, not transmitted; possible late entry, dstId = %u, algo = $%02X, kid = $%04X", m_rfLastHDU.getDstId(), m_rfLastHDU.getAlgId(), m_rfLastHDU.getKId()); + } + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfUndecodableLC = 0U; + m_vocLDU1Count = 0U; + m_p25->m_rfTimeout.start(); + m_lastDUID = P25_DUID_HDU; + + m_rfLastHDU = lc::LC(m_p25->m_siteData); + } + + if (m_p25->m_rfState == RS_RF_AUDIO) { + if (!alreadyDecoded) { + m_rfLC = m_rfDFSILC.control(); + m_rfLastLDU1 = m_rfLC; + } + + if (m_p25->m_control) { + m_p25->m_trunk->touchDstIdGrant(m_rfLC.getDstId()); + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + // per TIA-102.AABD-B transmit RFSS_STS_BCAST every 3 superframes (e.g. every 3 LDU1s) + m_vocLDU1Count++; + if (m_vocLDU1Count > VOC_LDU1_COUNT) { + m_vocLDU1Count = 0U; + m_rfLC.setLCO(LC_RFSS_STS_BCAST); + } + } + + uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_LDU1); + + // Generate LDU1 Data + m_rfLC.encodeLDU1(buffer + 2U); + + // Generate Low Speed Data + m_rfLSD.process(buffer + 2U); + + insertMissingAudio(m_dfsiLDU1); + + // Add the Audio + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 10U, 0U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 26U, 1U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 55U, 2U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 80U, 3U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 105U, 4U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 130U, 5U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 155U, 6U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 180U, 7U); + m_audio.encode(buffer + 2U, m_dfsiLDU1 + 204U, 8U); + + m_rfBits += 1233U; + m_rfFrames++; + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true); + + writeNetworkRF(buffer + 2U, P25_DUID_LDU1); + + if (m_verbose) { + LogMessage(LOG_RF, P25_LDU1_STR " DFSI, audio, srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u", + m_rfLC.getSrcId(), m_rfLC.getDstId(), m_rfLC.getGroup(), m_rfLC.getEmergency(), m_rfLC.getEncrypted(), m_rfLC.getPriority()); + } + + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + return true; + } + } + } + } + else if (frameType >= P25_DFSI_LDU2_VOICE10 && frameType <= P25_DFSI_LDU2_VOICE18) { + uint8_t n = 10U; + switch (frameType) { + case P25_DFSI_LDU2_VOICE10: + n = 10U; + break; + case P25_DFSI_LDU2_VOICE11: + n = 26U; + break; + case P25_DFSI_LDU2_VOICE12: + n = 55U; + break; + case P25_DFSI_LDU2_VOICE13: + n = 80U; + break; + case P25_DFSI_LDU2_VOICE14: + n = 105U; + break; + case P25_DFSI_LDU2_VOICE15: + n = 130U; + break; + case P25_DFSI_LDU2_VOICE16: + n = 155U; + break; + case P25_DFSI_LDU2_VOICE17: + n = 180U; + break; + case P25_DFSI_LDU2_VOICE18: + n = 204U; + break; + } + + if (m_rfDFSILC.decodeLDU2(data + 2U, m_dfsiLDU2 + n)) { + // if this is the last LDU2 frame process the full LDU2 + if (frameType == P25_DFSI_LDU2_VOICE18) { + m_lastDUID = P25_DUID_LDU2; + + if (m_p25->m_rfState == RS_RF_LISTENING) { + ::memset(m_dfsiLDU2, 0x00U, 9U * 25U); + return false; + } + else if (m_p25->m_rfState == RS_RF_AUDIO) { + m_rfLC = m_rfDFSILC.control(); + m_rfLastLDU2 = m_rfLC; + + uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_LDU2); + + // Generate LDU2 data + m_rfLC.encodeLDU2(buffer + 2U); + + // Generate Low Speed Data + m_rfLSD.process(buffer + 2U); + + insertMissingAudio(m_dfsiLDU2); + + // Add the Audio + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 10U, 0U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 26U, 1U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 55U, 2U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 80U, 3U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 105U, 4U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 130U, 5U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 155U, 6U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 180U, 7U); + m_audio.encode(buffer + 2U, m_dfsiLDU2 + 204U, 8U); + + m_rfBits += 1233U; + m_rfFrames++; + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true); + + writeNetworkRF(buffer + 2U, P25_DUID_LDU2); + + if (m_verbose) { + LogMessage(LOG_RF, P25_LDU2_STR " DFSI, audio, algo = $%02X, kid = $%04X", + m_rfLC.getAlgId(), m_rfLC.getKId()); + } + + ::memset(m_dfsiLDU2, 0x00U, 9U * 25U); + return true; + } + } + } + } + + return false; +} + +/// +/// Process a data frame from the network. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +/// +/// +/// +bool DFSIVoicePacket::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid) +{ + uint32_t count = 0U; + + switch (duid) { + case P25_DUID_LDU1: + if ((data[0U] == dfsi::P25_DFSI_LDU1_VOICE1) && (data[22U] == dfsi::P25_DFSI_LDU1_VOICE2) && + (data[36U] == dfsi::P25_DFSI_LDU1_VOICE3) && (data[53U] == dfsi::P25_DFSI_LDU1_VOICE4) && + (data[70U] == dfsi::P25_DFSI_LDU1_VOICE5) && (data[87U] == dfsi::P25_DFSI_LDU1_VOICE6) && + (data[104U] == dfsi::P25_DFSI_LDU1_VOICE7) && (data[121U] == dfsi::P25_DFSI_LDU1_VOICE8) && + (data[138U] == dfsi::P25_DFSI_LDU1_VOICE9)) { + + m_dfsiLC = dfsi::LC(control, lsd); + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE1); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 10U); + count += 22U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE2); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 26U); + count += 14U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE3); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 55U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE4); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 80U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE5); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 105U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE6); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 130U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE7); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 155U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE8); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 180U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU1_VOICE9); + m_dfsiLC.decodeLDU1(data + count, m_netLDU1 + 204U); + count += 16U; + + m_netLastLDU1 = control; + + if (m_p25->m_netState == RS_NET_IDLE) { + // are we interrupting a running CC? + if (m_p25->m_ccRunning) { + g_interruptP25Control = true; + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + m_p25->m_ccRunning = false; // otherwise the grant will be bundled with other packets + } + } + + checkNet_LDU2(); + if (m_p25->m_netState != RS_NET_IDLE) { + writeNet_LDU1(); + } + } + break; + case P25_DUID_LDU2: + if ((data[0U] == dfsi::P25_DFSI_LDU2_VOICE10) && (data[22U] == dfsi::P25_DFSI_LDU2_VOICE11) && + (data[36U] == dfsi::P25_DFSI_LDU2_VOICE12) && (data[53U] == dfsi::P25_DFSI_LDU2_VOICE13) && + (data[70U] == dfsi::P25_DFSI_LDU2_VOICE14) && (data[87U] == dfsi::P25_DFSI_LDU2_VOICE15) && + (data[104U] == dfsi::P25_DFSI_LDU2_VOICE16) && (data[121U] == dfsi::P25_DFSI_LDU2_VOICE17) && + (data[138U] == dfsi::P25_DFSI_LDU2_VOICE18)) { + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE10); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 10U); + count += 22U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE11); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 26U); + count += 14U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE12); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 55U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE13); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 80U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE14); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 105U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE15); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 130U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE16); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 155U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE17); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 180U); + count += 17U; + + m_dfsiLC.setFrameType(dfsi::P25_DFSI_LDU2_VOICE18); + m_dfsiLC.decodeLDU2(data + count, m_netLDU2 + 204U); + count += 16U; + + if (m_p25->m_netState == RS_NET_IDLE) { + if (!m_p25->m_voiceOnControl) { + m_p25->m_modem->clearP25Data(); + } + m_p25->m_queue.clear(); + + resetRF(); + resetNet(); + + m_p25->m_trunk->m_rfTSBK = lc::TSBK(m_p25->m_siteData, m_p25->m_idenEntry, m_p25->m_trunk->m_dumpTSBK); + m_p25->m_trunk->m_netTSBK = lc::TSBK(m_p25->m_siteData, m_p25->m_idenEntry, m_p25->m_trunk->m_dumpTSBK); + + writeNet_LDU1(); + } + else { + checkNet_LDU1(); + } + + if (m_p25->m_netState != RS_NET_IDLE) { + writeNet_LDU2(); + } + } + break; + case P25_DUID_TDU: + case P25_DUID_TDULC: + // don't process network frames if the RF modem isn't in a listening state + if (m_p25->m_rfState != RS_RF_LISTENING) { + resetNet(); + return false; + } + + if (m_p25->m_control) { + m_p25->m_trunk->releaseDstIdGrant(m_netLC.getDstId(), false); + } + + if (m_p25->m_netState != RS_NET_IDLE) { + if (duid == P25_DUID_TDU) + writeNet_TDU(); + + resetNet(); + } + break; + } + + return true; +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DFSIVoicePacket class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. +DFSIVoicePacket::DFSIVoicePacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose) : + VoicePacket(p25, network, debug, verbose), + m_rfDFSILC(), + m_netDFSILC(), + m_dfsiLDU1(NULL), + m_dfsiLDU2(NULL) +{ + m_dfsiLDU1 = new uint8_t[9U * 25U]; + m_dfsiLDU2 = new uint8_t[9U * 25U]; + + ::memset(m_dfsiLDU1, 0x00U, 9U * 25U); + ::memset(m_dfsiLDU2, 0x00U, 9U * 25U); +} + +/// +/// Finalizes a instance of the DFSIVoicePacket class. +/// +DFSIVoicePacket::~DFSIVoicePacket() +{ + delete[] m_dfsiLDU1; + delete[] m_dfsiLDU2; +} + +/// +/// Helper to write a network P25 TDU packet. +/// +void DFSIVoicePacket::writeNet_TDU() +{ + if (m_p25->m_control) { + m_p25->m_trunk->releaseDstIdGrant(m_netLC.getDstId(), false); + } + + m_rfDFSILC.setFrameType(P25_DFSI_START_STOP); + + uint8_t buffer[P25_DFSI_SS_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_DFSI_SS_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = modem::TAG_EOT; + buffer[1U] = 0x00U; + + // Generate DFSI NID + m_rfDFSILC.encodeNID(buffer + 2U); + + // stop writes the NID twice... + for (uint8_t i = 0; i < 2; i++) { + m_p25->writeQueueNet(buffer, P25_DFSI_SS_FRAME_LENGTH_BYTES + 2U); + } + + if (m_verbose) { + LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u", m_netLC.getSrcId()); + } + + if (m_netFrames > 0) { + ::ActivityLog("P25", false, "network end of transmission, %.1f seconds, %u%% packet loss", + float(m_netFrames) / 50.0F, (m_netLost * 100U) / m_netFrames); + } + else { + ::ActivityLog("P25", false, "network end of transmission, %u frames", m_netFrames); + } + + if (m_network != NULL) + m_network->resetP25(); + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_p25->m_netTimeout.stop(); + m_p25->m_networkWatchdog.stop(); + resetNet(); + m_p25->m_netState = RS_NET_IDLE; + m_p25->m_netLastDstId = 0U; + m_p25->m_tailOnIdle = true; +} + +/// +/// Helper to write a network P25 LDU1 packet. +/// +/// +/// +void DFSIVoicePacket::writeNet_LDU1() +{ + lc::LC control = lc::LC(m_dfsiLC.control()); + data::LowSpeedData lsd = data::LowSpeedData(m_dfsiLC.lsd()); + + uint32_t dstId = control.getDstId(); + uint32_t srcId = control.getSrcId(); + bool group = control.getLCO() == LC_GROUP; + + // ensure our srcId and dstId are sane from the last LDU1 + if (m_netLastLDU1.getDstId() != 0U) { + if (dstId != m_netLastLDU1.getDstId()) { + LogWarning(LOG_NET, P25_HDU_STR ", dstId = %u doesn't match last LDU1 dstId = %u, fixing", + m_rfLC.getDstId(), m_rfLastLDU1.getDstId()); + dstId = m_netLastLDU1.getDstId(); + } + } + else { + LogWarning(LOG_NET, P25_HDU_STR ", last LDU1 LC has bad data, dstId = 0"); + } + + if (m_netLastLDU1.getSrcId() != 0U) { + if (srcId != m_netLastLDU1.getSrcId()) { + LogWarning(LOG_NET, P25_HDU_STR ", srcId = %u doesn't match last LDU1 srcId = %u, fixing", + m_rfLC.getSrcId(), m_rfLastLDU1.getSrcId()); + srcId = m_netLastLDU1.getSrcId(); + } + } + else { + LogWarning(LOG_NET, P25_HDU_STR ", last LDU1 LC has bad data, srcId = 0"); + } + + // 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_rfLastDstId != 0U) { + if (m_p25->m_rfLastDstId != dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + resetNet(); + return; + } + + if (m_p25->m_rfLastDstId == dstId && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + m_p25->m_rfTGHang.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_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", m_rfLC.getSrcId(), m_rfLC.getDstId(), + srcId, dstId); + resetNet(); + return; + } + else { + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", m_rfLC.getDstId(), + dstId); + resetNet(); + return; + } + } + + if (m_p25->m_control) { + m_p25->m_trunk->touchDstIdGrant(m_rfLC.getDstId()); + } + + // set network and RF link control states + m_netLC = lc::LC(m_p25->m_siteData); + m_netLC.setLCO(control.getLCO()); + m_netLC.setMFId(control.getMFId()); + m_netLC.setSrcId(srcId); + m_netLC.setDstId(dstId); + m_netLC.setGroup(group); + m_netLC.setEmergency(control.getEmergency()); + m_netLC.setEncrypted(control.getEncrypted()); + m_netLC.setPriority(control.getPriority()); + + m_rfLC = lc::LC(m_p25->m_siteData); + m_rfLC.setLCO(control.getLCO()); + m_rfLC.setMFId(control.getMFId()); + m_rfLC.setSrcId(srcId); + m_rfLC.setDstId(dstId); + m_rfLC.setGroup(group); + m_rfLC.setEmergency(control.getEmergency()); + m_rfLC.setEncrypted(control.getEncrypted()); + m_rfLC.setPriority(control.getPriority()); + + // if we are idle lets generate HDU data + if (m_p25->m_netState == RS_NET_IDLE) { + uint8_t mi[P25_MI_LENGTH_BYTES]; + control.getMI(mi); + + if (m_verbose && m_debug) { + Utils::dump(1U, "Network HDU MI", mi, P25_MI_LENGTH_BYTES); + } + + m_netLC.setMI(mi); + m_rfLC.setMI(mi); + m_netLC.setAlgId(control.getAlgId()); + m_rfLC.setAlgId(control.getAlgId()); + m_netLC.setKId(control.getKId()); + m_rfLC.setKId(control.getKId()); + + m_p25->m_trunk->m_rfTSBK = lc::TSBK(&m_rfLC); + m_p25->m_trunk->m_rfTSBK.setVerbose(m_p25->m_trunk->m_dumpTSBK); + m_p25->m_trunk->m_netTSBK = lc::TSBK(&m_netLC); + m_p25->m_trunk->m_netTSBK.setVerbose(m_p25->m_trunk->m_dumpTSBK); + + // validate source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_NET, P25_HDU_STR " denial, RID rejection, srcId = %u", srcId); + return; + } + + // is this a group or individual operation? + if (!group) { + // validate the target RID + if (!acl::AccessControl::validateSrcId(dstId)) { + LogWarning(LOG_NET, P25_HDU_STR " denial, RID rejection, dstId = %u", dstId); + return; + } + } + else { + // validate the target ID, if the target is a talkgroup + if (!acl::AccessControl::validateTGId(dstId)) { + LogWarning(LOG_NET, P25_HDU_STR " denial, TGID rejection, dstId = %u", dstId); + return; + } + } + + ::ActivityLog("P25", false, "network %svoice transmission from %u to %s%u", m_netLC.getEncrypted() ? "encrypted " : "", srcId, group ? "TG " : "", dstId); + + if (m_p25->m_control) { + if (group && (m_lastPatchGroup != dstId) && + (dstId != m_p25->m_trunk->m_patchSuperGroup)) { + m_p25->m_trunk->writeRF_TSDU_Mot_Patch(dstId, 0U, 0U); + m_lastPatchGroup = dstId; + } + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + m_p25->m_ccRunning = false; // otherwise the grant will be bundled with other packets + if (!m_p25->m_trunk->writeRF_TSDU_Grant(group, false, true)) { + if (m_network != NULL) + m_network->resetP25(); + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_p25->m_netTimeout.stop(); + m_p25->m_networkWatchdog.stop(); + + m_netLC = lc::LC(m_p25->m_siteData); + m_netLastLDU1 = lc::LC(m_p25->m_siteData); + + m_p25->m_netState = RS_NET_IDLE; + m_p25->m_netLastDstId = 0U; + + if (m_p25->m_rfState == RS_RF_REJECTED) { + m_p25->m_rfState = RS_RF_LISTENING; + } + + return; + } + + m_p25->writeRF_Preamble(0, true); + } + + m_hadVoice = true; + m_p25->m_netState = RS_NET_AUDIO; + m_p25->m_netLastDstId = dstId; + m_p25->m_netTimeout.start(); + m_netFrames = 0U; + m_netLost = 0U; + m_vocLDU1Count = 0U; + + m_netDFSILC.control(m_netLC); + m_netDFSILC.lsd(lsd); + + if (!m_p25->m_disableNetworkHDU) { + uint8_t buffer[P25_DFSI_VHDR1_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_DFSI_VHDR1_FRAME_LENGTH_BYTES + 2U); + + // Generate Start/Stop + m_netDFSILC.setFrameType(P25_DFSI_START_STOP); + m_netDFSILC.encodeNID(buffer + 2U); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + m_p25->writeQueueNet(buffer, P25_DFSI_SS_FRAME_LENGTH_BYTES + 2U); + + // Generate Voice Header 1 + m_netDFSILC.setFrameType(P25_DFSI_VHDR1); + m_netDFSILC.encodeVHDR1(buffer + 2U); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + m_p25->writeQueueNet(buffer, P25_DFSI_VHDR1_FRAME_LENGTH_BYTES + 2U); + + // Generate Voice Header 2 + m_netDFSILC.setFrameType(P25_DFSI_VHDR2); + m_netDFSILC.encodeVHDR2(buffer + 2U); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + m_p25->writeQueueNet(buffer, P25_DFSI_VHDR2_FRAME_LENGTH_BYTES + 2U); + + if (m_verbose) { + LogMessage(LOG_NET, P25_HDU_STR " DFSI, dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); + } + } + else { + if (m_verbose) { + LogMessage(LOG_NET, P25_HDU_STR " DFSI, not transmitted; network HDU disabled, dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); + } + } + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + // per TIA-102.AABD-B transmit RFSS_STS_BCAST every 3 superframes (e.g. every 3 LDU1s) + m_vocLDU1Count++; + if (m_vocLDU1Count > VOC_LDU1_COUNT) { + m_vocLDU1Count = 0U; + m_netLC.setLCO(LC_RFSS_STS_BCAST); + } + } + + insertMissingAudio(m_netLDU1); + + uint8_t buffer[P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES + 2U]; + for (uint8_t i = P25_DFSI_LDU1_VOICE1; i <= P25_DFSI_LDU1_VOICE9; i++) { + uint8_t len = P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES; + + // frame 2 + if (i == P25_DFSI_LDU1_VOICE2) { + len = P25_DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES; + } + + // frames 3 - 8 are the same size + if (i >= P25_DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES && i <= P25_DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES) { + len = P25_DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES; + } + + // frame 9 + if (i == P25_DFSI_LDU1_VOICE9) { + len = P25_DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; + } + + uint8_t n = 10U; + switch (i) { + case P25_DFSI_LDU1_VOICE1: + n = 10U; + break; + case P25_DFSI_LDU1_VOICE2: + n = 26U; + break; + case P25_DFSI_LDU1_VOICE3: + n = 55U; + break; + case P25_DFSI_LDU1_VOICE4: + n = 80U; + break; + case P25_DFSI_LDU1_VOICE5: + n = 105U; + break; + case P25_DFSI_LDU1_VOICE6: + n = 130U; + break; + case P25_DFSI_LDU1_VOICE7: + n = 155U; + break; + case P25_DFSI_LDU1_VOICE8: + n = 180U; + break; + case P25_DFSI_LDU1_VOICE9: + n = 204U; + break; + } + + ::memset(buffer, 0x00U, len + 2U); + + // Generate Voice Frame + m_netDFSILC.setFrameType(i); + m_netDFSILC.encodeLDU1(buffer + 2U, m_netLDU1 + n); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + m_p25->writeQueueNet(buffer, len + 2U); + } + + if (m_verbose) { + uint32_t loss = 0; + if (m_netFrames != 0) { + loss = (m_netLost * 100U) / m_netFrames; + } + else { + loss = (m_netLost * 100U) / 1U; + if (loss > 100) { + loss = 100; + } + } + + LogMessage(LOG_NET, P25_LDU1_STR " DFSI audio, srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, %u%% packet loss", + m_netLC.getSrcId(), m_netLC.getDstId(), m_netLC.getGroup(), m_netLC.getEmergency(), m_netLC.getEncrypted(), m_netLC.getPriority(), loss); + } + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + + m_netFrames += 9U; +} + +/// +/// Helper to write a network P25 LDU2 packet. +/// +/// +/// +void DFSIVoicePacket::writeNet_LDU2() +{ + lc::LC control = lc::LC(m_dfsiLC.control()); + data::LowSpeedData lsd = data::LowSpeedData(m_dfsiLC.lsd()); + + // 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_rfLastDstId != 0U) { + if (m_p25->m_rfLastDstId != m_netLastLDU1.getDstId() && (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired())) { + resetNet(); + return; + } + } + + uint8_t mi[P25_MI_LENGTH_BYTES]; + control.getMI(mi); + + if (m_verbose && m_debug) { + Utils::dump(1U, "Network LDU2 MI", mi, P25_MI_LENGTH_BYTES); + } + + m_netLC.setMI(mi); + m_netLC.setAlgId(control.getAlgId()); + m_netLC.setKId(control.getKId()); + + m_netDFSILC.control(m_netLC); + + insertMissingAudio(m_netLDU2); + + uint8_t buffer[P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES + 2U]; + for (uint8_t i = P25_DFSI_LDU2_VOICE10; i <= P25_DFSI_LDU2_VOICE18; i++) { + uint8_t len = P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES; + + // frame 11 + if (i == P25_DFSI_LDU2_VOICE11) { + len = P25_DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES; + } + + // frames 12 - 17 are the same size + if (i >= P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES && i <= P25_DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES) { + len = P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES; + } + + // frame 9 + if (i == P25_DFSI_LDU2_VOICE18) { + len = P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; + } + + uint8_t n = 10U; + switch (i) { + case P25_DFSI_LDU2_VOICE10: + n = 10U; + break; + case P25_DFSI_LDU2_VOICE11: + n = 26U; + break; + case P25_DFSI_LDU2_VOICE12: + n = 55U; + break; + case P25_DFSI_LDU2_VOICE13: + n = 80U; + break; + case P25_DFSI_LDU2_VOICE14: + n = 105U; + break; + case P25_DFSI_LDU2_VOICE15: + n = 130U; + break; + case P25_DFSI_LDU2_VOICE16: + n = 155U; + break; + case P25_DFSI_LDU2_VOICE17: + n = 180U; + break; + case P25_DFSI_LDU2_VOICE18: + n = 204U; + break; + } + + ::memset(buffer, 0x00U, len + 2U); + + // Generate Voice Frame + m_netDFSILC.setFrameType(i); + m_netDFSILC.encodeLDU2(buffer + 2U, m_netLDU2 + n); + + buffer[0U] = modem::TAG_DATA; + buffer[1U] = 0x00U; + m_p25->writeQueueNet(buffer, len + 2U); + } + + if (m_verbose) { + uint32_t loss = 0; + if (m_netFrames != 0) { + loss = (m_netLost * 100U) / m_netFrames; + } + else { + loss = (m_netLost * 100U) / 1U; + if (loss > 100) { + loss = 100; + } + } + + LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X, %u%% packet loss", m_netLC.getAlgId(), m_netLC.getKId(), loss); + } + + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_netFrames += 9U; +} diff --git a/p25/dfsi/DFSIVoicePacket.h b/p25/dfsi/DFSIVoicePacket.h new file mode 100644 index 00000000..c6a53dcd --- /dev/null +++ b/p25/dfsi/DFSIVoicePacket.h @@ -0,0 +1,92 @@ +/** +* 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. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 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. +*/ +#if !defined(__P25_DFSI_VOICE_PACKET_H__) +#define __P25_DFSI_VOICE_PACKET_H__ + +#include "Defines.h" +#include "p25/dfsi/LC.h" +#include "p25/TrunkPacket.h" +#include "p25/Control.h" +#include "network/BaseNetwork.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API VoicePacket; + class HOST_SW_API Control; + + namespace dfsi + { + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for P25 voice packets using + // the DFSI protocol instead of the P25 OTA protocol. + // --------------------------------------------------------------------------- + + class HOST_SW_API DFSIVoicePacket : public VoicePacket { + public: + /// Resets the data states for the RF interface. + virtual void resetRF(); + /// Resets the data states for the network. + virtual void resetNet(); + + /// Process a data frame from the RF interface. + virtual bool process(uint8_t* data, uint32_t len); + /// Process a data frame from the network. + virtual bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + + protected: + friend class DFSITrunkPacket; + friend class Control; + + LC m_rfDFSILC; + LC m_netDFSILC; + + uint8_t* m_dfsiLDU1; + uint8_t* m_dfsiLDU2; + + /// Initializes a new instance of the VoicePacket class. + DFSIVoicePacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose); + /// Finalizes a instance of the VoicePacket class. + virtual ~DFSIVoicePacket(); + + /// Helper to write a network P25 TDU packet. + virtual void writeNet_TDU(); + /// Helper to write a network P25 LDU1 packet. + virtual void writeNet_LDU1(); + /// Helper to write a network P25 LDU1 packet. + virtual void writeNet_LDU2(); + }; + } // namespace dfsi +} // namespace p25 + +#endif // __P25_DFSI_VOICE_PACKET_H__