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__