From b8200f8a2b0f855aa35999de2fa0a064275927ae Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 9 Aug 2022 17:18:37 -0400 Subject: [PATCH] [EXPERIMENTAL] implement experimental NXDN CC transmit stream (no incoming data processing, yet)...; --- HostMain.cpp | 1 + HostMain.h | 1 + Makefile | 4 +- config.example.yml | 9 + dmr/Slot.cpp | 2 +- host/Host.cpp | 208 +++++++++++++++++- host/Host.h | 3 + network/BaseNetwork.cpp | 6 +- network/BaseNetwork.h | 8 +- nxdn/Control.cpp | 152 +++++++++++-- nxdn/Control.h | 31 ++- nxdn/NXDNDefines.h | 19 ++ nxdn/SiteData.h | 14 +- nxdn/channel/CAC.cpp | 4 +- nxdn/channel/SACCH.cpp | 3 - nxdn/channel/UDCH.cpp | 2 - nxdn/lc/RCCH.cpp | 406 +++++++++++++++++++++++++++++++++++ nxdn/lc/RCCH.h | 129 +++++++++++ nxdn/lc/{LC.cpp => RTCH.cpp} | 58 ++--- nxdn/lc/{LC.h => RTCH.h} | 26 +-- nxdn/packet/Data.cpp | 20 +- nxdn/packet/Data.h | 6 +- nxdn/packet/Trunk.cpp | 313 +++++++++++++++++++++++++++ nxdn/packet/Trunk.h | 106 +++++++++ nxdn/packet/Voice.cpp | 24 +-- nxdn/packet/Voice.h | 6 +- 26 files changed, 1445 insertions(+), 116 deletions(-) create mode 100644 nxdn/lc/RCCH.cpp create mode 100644 nxdn/lc/RCCH.h rename nxdn/lc/{LC.cpp => RTCH.cpp} (92%) rename nxdn/lc/{LC.h => RTCH.h} (89%) create mode 100644 nxdn/packet/Trunk.cpp create mode 100644 nxdn/packet/Trunk.h diff --git a/HostMain.cpp b/HostMain.cpp index 5814f3ae..963f917d 100644 --- a/HostMain.cpp +++ b/HostMain.cpp @@ -93,6 +93,7 @@ bool g_killed = false; bool g_fireDMRBeacon = false; bool g_fireP25Control = false; +bool g_fireNXDNControl = false; uint8_t* g_gitHashBytes = NULL; diff --git a/HostMain.h b/HostMain.h index d0566318..5e979024 100644 --- a/HostMain.h +++ b/HostMain.h @@ -49,6 +49,7 @@ extern bool g_killed; extern bool g_fireDMRBeacon; extern bool g_fireP25Control; +extern bool g_fireNXDNControl; extern uint8_t* g_gitHashBytes; diff --git a/Makefile b/Makefile index 904e221c..ef207f19 100644 --- a/Makefile +++ b/Makefile @@ -69,9 +69,11 @@ HOST_OBJECTS = \ nxdn/channel/LICH.o \ nxdn/channel/SACCH.o \ nxdn/channel/UDCH.o \ - nxdn/lc/LC.o \ nxdn/lc/PacketInformation.o \ + nxdn/lc/RCCH.o \ + nxdn/lc/RTCH.o \ nxdn/packet/Data.o \ + nxdn/packet/Trunk.o \ nxdn/packet/Voice.o \ nxdn/Audio.o \ nxdn/Control.o \ diff --git a/config.example.yml b/config.example.yml index 1e11d306..27831cf9 100644 --- a/config.example.yml +++ b/config.example.yml @@ -79,6 +79,15 @@ protocols: debug: false nxdn: enable: false + control: + enable: false + dedicated: false + broadcast: true + interval: 300 + duration: 1 + voiceOnControl: false + disableCompositeFlag: false + dumpRcchData: false callHang: 5 silenceThreshold: 14 queueSize: 12 diff --git a/dmr/Slot.cpp b/dmr/Slot.cpp index 6e0849e3..f49ca30b 100644 --- a/dmr/Slot.cpp +++ b/dmr/Slot.cpp @@ -882,7 +882,7 @@ void Slot::writeRF_ControlData(uint16_t frameCnt, uint8_t n) seqCnt = 3U; } - // shuld we insert the Git Hash burst? + // should we insert the Git Hash burst? bool hash = (frameCnt % 256U) == 0U; if (hash) { m_control->writeRF_TSCC_Git_Hash(); diff --git a/host/Host.cpp b/host/Host.cpp index 5803ece4..4777a208 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -122,6 +122,9 @@ Host::Host(const std::string& confFile) : m_p25CCData(false), m_p25CtrlChannel(false), m_p25CtrlBroadcast(false), + m_nxdnCCData(false), + m_nxdnCtrlChannel(false), + m_nxdnCtrlBroadcast(false), m_siteId(1U), m_dmrNetId(1U), m_dmrColorCode(1U), @@ -545,12 +548,19 @@ int Host::run() #endif // defined(ENABLE_P25) // initialize NXDN + Timer nxdnBcastIntervalTimer(1000U); + Timer nxdnBcastDurationTimer(1000U); + nxdn::Control* nxdn = NULL; #if defined(ENABLE_NXDN) LogInfo("NXDN Parameters"); LogInfo(" Enabled: %s", m_nxdnEnabled ? "yes" : "no"); if (m_nxdnEnabled) { yaml::Node nxdnProtocol = protocolConf["nxdn"]; + m_nxdnCCData = nxdnProtocol["control"]["enable"].as(false); + bool nxdnCtrlChannel = nxdnProtocol["control"]["dedicated"].as(false); + bool nxdnCtrlBroadcast = nxdnProtocol["control"]["broadcast"].as(true); + bool nxdnDumpRcchData = nxdnProtocol["dumpRcchData"].as(false); uint32_t callHang = nxdnProtocol["callHang"].as(3U); uint32_t queueSize = nxdnProtocol["queueSize"].as(31U); bool nxdnVerbose = nxdnProtocol["verbose"].as(true); @@ -574,9 +584,35 @@ int Host::run() LogInfo(" Call Hang: %us", callHang); LogInfo(" Queue Size: %u (%u bytes)", queueSize, queueSizeBytes); + LogInfo(" Control: %s", m_nxdnCCData ? "yes" : "no"); + + uint32_t nxdnControlBcstInterval = nxdnProtocol["control"]["interval"].as(300U); + uint32_t nxdnControlBcstDuration = nxdnProtocol["control"]["duration"].as(1U); + if (m_nxdnCCData) { + LogInfo(" Control Broadcast: %s", nxdnCtrlBroadcast ? "yes" : "no"); + LogInfo(" Control Channel: %s", nxdnCtrlChannel ? "yes" : "no"); + if (nxdnCtrlChannel) { + m_nxdnCtrlChannel = nxdnCtrlChannel; + } + else { + LogInfo(" Control Broadcast Interval: %us", nxdnControlBcstInterval); + LogInfo(" Control Broadcast Duration: %us", nxdnControlBcstDuration); + } + + nxdnBcastDurationTimer.setTimeout(nxdnControlBcstDuration); + + nxdnBcastIntervalTimer.setTimeout(nxdnControlBcstInterval); + nxdnBcastIntervalTimer.start(); + + m_nxdnCtrlBroadcast = nxdnCtrlBroadcast; + if (nxdnCtrlBroadcast) { + g_fireNXDNControl = true; + } + } + nxdn = new nxdn::Control(m_nxdnRAN, callHang, queueSizeBytes, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, - nxdnDebug, nxdnVerbose); + nxdnDumpRcchData, nxdnDebug, nxdnVerbose); nxdn->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_siteId, m_channelId, m_channelNo, true); if (nxdnVerbose) { @@ -651,6 +687,26 @@ int Host::run() g_killed = true; } + // NXDN CC checks + if (m_dmrEnabled && m_nxdnCtrlChannel) { + ::LogError(LOG_HOST, "Cannot have DMR enabled when using dedicated NXDN control!"); + g_killed = true; + } + + if (m_p25Enabled && m_nxdnCtrlChannel) { + ::LogError(LOG_HOST, "Cannot have P25 enabled when using dedicated NXDN control!"); + g_killed = true; + } + + if (!m_fixedMode && m_nxdnCtrlChannel) { + ::LogWarning(LOG_HOST, "Fixed mode should be enabled when using dedicated NXDN control!"); + } + + if (!m_duplex && m_nxdnCCData) { + ::LogError(LOG_HOST, "Cannot have NXDN control and simplex mode at the same time."); + g_killed = true; + } + // DMR beacon checks if (m_dmrBeacons && m_p25CCData) { ::LogError(LOG_HOST, "Cannot have DMR roaming becaons and P25 control at the same time."); @@ -673,6 +729,13 @@ int Host::run() setState(STATE_P25); } #endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + if (m_nxdnCtrlChannel) { + m_fixedMode = true; + setState(STATE_NXDN); + } +#endif // defined(ENABLE_NXDN) + #if defined(ENABLE_DMR) if (dmr != NULL) setState(STATE_DMR); @@ -687,6 +750,19 @@ int Host::run() #endif // defined(ENABLE_NXDN) } else { +#if defined(ENABLE_P25) + if (m_p25CtrlChannel) { + m_fixedMode = true; + setState(STATE_P25); + } +#endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + if (m_nxdnCtrlChannel) { + m_fixedMode = true; + setState(STATE_NXDN); + } +#endif // defined(ENABLE_NXDN) + setState(STATE_IDLE); } @@ -756,6 +832,15 @@ int Host::run() dmrBeaconDurationTimer.stop(); \ } + // Macro to interrupt a running NXDN control channel transmission + #define INTERRUPT_NXDN_CONTROL \ + if (nxdn != NULL) { \ + nxdn->setCCHalted(true); \ + if (nxdnBcastDurationTimer.isRunning() && !nxdnBcastDurationTimer.isPaused()) { \ + nxdnBcastDurationTimer.pause(); \ + } \ + } + // Macro to start DMR duplex idle transmission (or beacon) #define START_DMR_DUPLEX_IDLE(x) \ if (dmr != NULL) { \ @@ -847,6 +932,14 @@ int Host::run() } } + // if there is a NXDN CC running; halt the CC + if (nxdn != NULL) { + if (nxdn->getCCRunning() && !nxdn->getCCHalted()) { + nxdn->setCCHalted(true); + INTERRUPT_NXDN_CONTROL; + } + } + m_modeTimer.start(); } } @@ -884,6 +977,14 @@ int Host::run() INTERRUPT_P25_CONTROL; } + // if there is a NXDN CC running; halt the CC + if (nxdn != NULL) { + if (nxdn->getCCRunning() && !nxdn->getCCHalted()) { + nxdn->setCCHalted(true); + INTERRUPT_NXDN_CONTROL; + } + } + m_modeTimer.start(); } } @@ -912,6 +1013,14 @@ int Host::run() m_modem->writeP25Data(data, len); INTERRUPT_DMR_BEACON; + + // if there is a NXDN CC running; halt the CC + if (nxdn != NULL) { + if (nxdn->getCCRunning() && !nxdn->getCCHalted()) { + nxdn->setCCHalted(true); + INTERRUPT_NXDN_CONTROL; + } + } m_modeTimer.start(); } @@ -1026,6 +1135,7 @@ int Host::run() INTERRUPT_DMR_BEACON; INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; } } else { @@ -1037,7 +1147,8 @@ int Host::run() dmr->processFrame(1U, data, len); INTERRUPT_DMR_BEACON; - p25BcastDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; } } else if (m_state == STATE_DMR) { @@ -1056,6 +1167,7 @@ int Host::run() if (ret) { INTERRUPT_DMR_BEACON; INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; m_modeTimer.start(); if (m_duplex) @@ -1083,6 +1195,7 @@ int Host::run() INTERRUPT_DMR_BEACON; INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; } } else { @@ -1095,6 +1208,7 @@ int Host::run() INTERRUPT_DMR_BEACON; INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; } } else if (m_state == STATE_DMR) { @@ -1113,6 +1227,7 @@ int Host::run() if (ret) { INTERRUPT_DMR_BEACON; INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; m_modeTimer.start(); if (m_duplex) @@ -1143,11 +1258,13 @@ int Host::run() INTERRUPT_DMR_BEACON; INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; } else { ret = p25->writeRF_VoiceEnd(); if (ret) { INTERRUPT_DMR_BEACON; + INTERRUPT_NXDN_CONTROL; if (m_state == STATE_IDLE) { m_modeTimer.setTimeout(m_rfModeHang); @@ -1218,11 +1335,11 @@ int Host::run() } } else if (m_state == STATE_NXDN) { - // process P25 frames + // process NXDN frames bool ret = nxdn->processFrame(data, len); if (ret) { m_modeTimer.start(); - INTERRUPT_P25_CONTROL; + INTERRUPT_NXDN_CONTROL; } } else if (m_state != HOST_STATE_LOCKOUT) { @@ -1447,6 +1564,73 @@ int Host::run() } #endif // defined(ENABLE_P25) + /** Next Generation Digital Narrowband */ +#if defined(ENABLE_NXDN) + if (nxdn != NULL) { + if (m_nxdnCCData) { + nxdnBcastIntervalTimer.clock(ms); + + if (!m_nxdnCtrlChannel && m_nxdnCtrlBroadcast) { + // clock and check NXDN CC broadcast interval timer + if ((nxdnBcastIntervalTimer.isRunning() && nxdnBcastIntervalTimer.hasExpired()) || g_fireNXDNControl) { + if ((m_state == STATE_IDLE || m_state == STATE_NXDN) && !m_modem->hasTX()) { + if (m_modeTimer.isRunning()) { + m_modeTimer.stop(); + } + + if (m_state != STATE_NXDN) + setState(STATE_NXDN); + + //nxdn->writeAdjSSNetwork(); + nxdn->setCCRunning(true); + + // hide this message for continuous CC -- otherwise display every time we process + if (!m_nxdnCtrlChannel) { + LogMessage(LOG_HOST, "NXDN, start CC broadcast"); + } + + g_fireNXDNControl = false; + nxdnBcastIntervalTimer.start(); + nxdnBcastDurationTimer.start(); + + // if the CC is continuous -- clock one cycle into the duration timer + if (m_nxdnCtrlChannel) { + nxdnBcastDurationTimer.clock(ms); + } + } + } + + if (nxdnBcastDurationTimer.isPaused()) { + nxdnBcastDurationTimer.resume(); + } + + // clock and check NXDN CC broadcast duration timer + nxdnBcastDurationTimer.clock(ms); + if (nxdnBcastDurationTimer.isRunning() && nxdnBcastDurationTimer.hasExpired()) { + nxdnBcastDurationTimer.stop(); + + nxdn->setCCRunning(false); + + if (m_state == STATE_NXDN && !m_modeTimer.isRunning()) { + m_modeTimer.setTimeout(m_rfModeHang); + m_modeTimer.start(); + } + } + } + else { + // simply use the NXDN CC interval timer in a non-broadcast state to transmit adjacent site data over + // the network + if (nxdnBcastIntervalTimer.isRunning() && nxdnBcastIntervalTimer.hasExpired()) { + if ((m_state == STATE_IDLE || m_state == STATE_NXDN) && !m_modem->hasTX()) { + //nxdn->writeAdjSSNetwork(); + nxdnBcastIntervalTimer.start(); + } + } + } + } + } +#endif // defined(ENABLE_NXDN) + if (g_killed) { #if defined(ENABLE_DMR) if (dmr != NULL) { @@ -1481,6 +1665,22 @@ int Host::run() } #endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + if (nxdn != NULL) { + if (m_nxdnCtrlChannel) { + if (!hasTxShutdown) { + m_modem->clearNXDNData(); + nxdn->reset(); + } + + nxdn->setCCRunning(false); + + nxdnBcastDurationTimer.stop(); + nxdnBcastIntervalTimer.stop(); + } + } +#endif // defined(ENABLE_NXDN) + hasTxShutdown = true; if (!m_modem->hasTX()) { killed = true; diff --git a/host/Host.h b/host/Host.h index b43026d8..850ff6c7 100644 --- a/host/Host.h +++ b/host/Host.h @@ -119,6 +119,9 @@ private: bool m_p25CCData; bool m_p25CtrlChannel; bool m_p25CtrlBroadcast; + bool m_nxdnCCData; + bool m_nxdnCtrlChannel; + bool m_nxdnCtrlBroadcast; uint8_t m_siteId; uint32_t m_dmrNetId; diff --git a/network/BaseNetwork.cpp b/network/BaseNetwork.cpp index b560edef..d5bdbae3 100644 --- a/network/BaseNetwork.cpp +++ b/network/BaseNetwork.cpp @@ -261,7 +261,7 @@ uint8_t* BaseNetwork::readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpe /// /// /// -uint8_t* BaseNetwork::readNXDN(bool& ret, nxdn::lc::LC& lc, uint32_t& len) +uint8_t* BaseNetwork::readNXDN(bool& ret, nxdn::lc::RTCH& lc, uint32_t& len) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) { ret = false; @@ -487,7 +487,7 @@ bool BaseNetwork::writeP25PDU(const p25::data::DataHeader& header, const p25::da /// /// /// -bool BaseNetwork::writeNXDN(const nxdn::lc::LC& lc, const uint8_t* data, const uint32_t len) +bool BaseNetwork::writeNXDN(const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -1086,7 +1086,7 @@ bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const /// /// /// -bool BaseNetwork::writeNXDN(const uint32_t id, const uint32_t streamId, const nxdn::lc::LC& lc, const uint8_t* data, const uint32_t len) +bool BaseNetwork::writeNXDN(const uint32_t id, const uint32_t streamId, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; diff --git a/network/BaseNetwork.h b/network/BaseNetwork.h index 52b2bec6..d56e17db 100644 --- a/network/BaseNetwork.h +++ b/network/BaseNetwork.h @@ -43,7 +43,7 @@ #include "p25/lc/TSBK.h" #include "p25/lc/TDULC.h" #include "p25/Audio.h" -#include "nxdn/lc/LC.h" +#include "nxdn/lc/RTCH.h" #include "network/UDPSocket.h" #include "RingBuffer.h" #include "Timer.h" @@ -141,7 +141,7 @@ namespace network /// Reads P25 frame data from the P25 ring buffer. virtual uint8_t* readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpeedData& lsd, uint8_t& duid, uint32_t& len); /// Reads NXDN frame data from the NXDN ring buffer. - virtual uint8_t* readNXDN(bool& ret, nxdn::lc::LC& lc, uint32_t& len); + virtual uint8_t* readNXDN(bool& ret, nxdn::lc::RTCH& lc, uint32_t& len); /// Reads a channel grant request from the network. virtual bool readGrantRsp(bool& grp, uint32_t& srcId, uint32_t& dstId, uint32_t& grpVchNo); @@ -161,7 +161,7 @@ namespace network const uint8_t* data, const uint32_t len); /// Writes NXDN frame data to the network. - virtual bool writeNXDN(const nxdn::lc::LC& lc, const uint8_t* data, const uint32_t len); + virtual bool writeNXDN(const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); /// Writes a channel grant request to the network. virtual bool writeGrantReq(const bool grp, const uint32_t srcId, const uint32_t dstId); @@ -240,7 +240,7 @@ namespace network bool writeP25PDU(const uint32_t id, const uint32_t streamId, const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, const uint8_t currentBlock, const uint8_t* data, const uint32_t len); /// Writes NXDN frame data to the network. - bool writeNXDN(const uint32_t id, const uint32_t streamId, const nxdn::lc::LC& layer3, const uint8_t* data, const uint32_t len); + bool writeNXDN(const uint32_t id, const uint32_t streamId, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); /// Writes data to the network. virtual bool write(const uint8_t* data, uint32_t length); diff --git a/nxdn/Control.cpp b/nxdn/Control.cpp index 42d51202..6fc398f9 100644 --- a/nxdn/Control.cpp +++ b/nxdn/Control.cpp @@ -76,15 +76,13 @@ const uint8_t SCRAMBLER[] = { /// Instance of the TalkgroupIdLookup class. /// Instance of the IdenTableLookup class. /// Instance of the RSSIInterpolator class. -/// -/// -/// Flag indicating whether TSBK data is dumped to the log. +/// Flag indicating whether RCCH data is dumped to the log. /// Flag indicating whether P25 debug is enabled. /// Flag indicating whether P25 verbose logging is enabled. Control::Control(uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, - bool debug, bool verbose) : + bool dumpRCCHData, bool debug, bool verbose) : m_voice(NULL), m_data(NULL), m_ran(ran), @@ -116,6 +114,9 @@ Control::Control(uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t t m_rfTGHang(1000U, tgHang), m_netTimeout(1000U, timeout), m_networkWatchdog(1000U, 0U, 1500U), + m_ccPacketInterval(1000U, 0U, 5U), + m_ccFrameCnt(0U), + m_ccSeq(0U), m_siteData(), m_rssiMapper(rssiMapper), m_rssi(0U), @@ -134,6 +135,7 @@ Control::Control(uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t t acl::AccessControl::init(m_ridLookup, m_tidLookup); m_voice = new Voice(this, network, debug, verbose); + m_trunk = new Trunk(this, network, debug, verbose, dumpRCCHData); m_data = new Data(this, network, debug, verbose); } @@ -146,6 +148,10 @@ Control::~Control() delete m_voice; } + if (m_trunk != NULL) { + delete m_trunk; + } + if (m_data != NULL) { delete m_data; } @@ -157,6 +163,7 @@ Control::~Control() void Control::reset() { m_rfState = RS_RF_LISTENING; + m_ccHalted = false; if (m_voice != NULL) { m_voice->resetRF(); @@ -250,6 +257,11 @@ void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const s if (m_data != NULL) { m_data->resetRF(); } + + if (m_trunk != NULL) { + m_trunk->resetRF(); + m_trunk->resetNet(); + } } /// @@ -325,18 +337,49 @@ bool Control::processFrame(uint8_t* data, uint32_t len) if (valid) m_rfLastLICH = lich; - uint8_t usc = m_rfLastLICH.getFCT(); + uint8_t rfct = m_rfLastLICH.getRFCT(); + uint8_t fct = m_rfLastLICH.getFCT(); uint8_t option = m_rfLastLICH.getOption(); + if (m_debug) { + LogDebug(LOG_RF, "NXDN, rfState = %u, netState = %u, fct = %u", m_rfState, m_netState, fct); + } + + // are we interrupting a running CC? + if (m_ccRunning) { + if ((fct != NXDN_LICH_CAC_INBOUND_SHORT) || (fct != NXDN_LICH_CAC_INBOUND_LONG)) { + m_ccHalted = true; + } + } + bool ret = false; - switch (usc) { - case NXDN_LICH_USC_UDCH: - ret = m_data->process(option, data, len); - break; - default: - ret = m_voice->process(usc, option, data, len); - break; + if (rfct == NXDN_LICH_RFCT_RCCH) { + ret = m_trunk->process(fct, option, data, len); + } + else if (rfct == NXDN_LICH_RFCT_RTCH || rfct == NXDN_LICH_RFCT_RDCH) { + switch (fct) { + case NXDN_LICH_USC_UDCH: + if (!m_dedicatedControl) { + ret = m_data->process(option, data, len); + } + else { + if (m_voiceOnControl) { + ret = m_data->process(option, data, len); + } + } + break; + default: + if (!m_dedicatedControl) { + ret = m_voice->process(fct, option, data, len); + } + else { + if (m_voiceOnControl) { + ret = m_voice->process(fct, option, data, len); + } + } + break; + } } return ret; @@ -369,6 +412,45 @@ void Control::clock(uint32_t ms) { if (m_network != NULL) { processNetwork(); + + if (m_network->getStatus() == network::NET_STAT_RUNNING) { + m_siteData.setNetActive(true); + } + else { + m_siteData.setNetActive(false); + } + } + + // if we have control enabled; do clocking to generate a CC data stream + if (m_control) { + if (m_ccRunning && !m_ccPacketInterval.isRunning()) { + m_ccPacketInterval.start(); + } + + if (m_ccHalted) { + if (!m_ccRunning) { + m_ccHalted = false; + m_ccPrevRunning = m_ccRunning; + } + } + else { + m_ccPacketInterval.clock(ms); + if (!m_ccPacketInterval.isRunning()) { + m_ccPacketInterval.start(); + } + + if (m_ccPacketInterval.isRunning() && m_ccPacketInterval.hasExpired()) { + if (m_ccRunning) { + writeRF_ControlData(); + } + + m_ccPacketInterval.start(); + } + } + + if (m_ccPrevRunning && !m_ccRunning) { + m_ccPrevRunning = m_ccRunning; + } } // handle timeouts and hang timers @@ -426,6 +508,11 @@ void Control::clock(uint32_t ms) m_rfState = RS_RF_LISTENING; } + + // clock data and trunking + if (m_trunk != NULL) { + m_trunk->clock(ms); + } } /// @@ -501,7 +588,7 @@ void Control::processNetwork() if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) return; - lc::LC lc; + lc::RTCH lc; uint32_t length = 100U; bool ret = false; @@ -544,6 +631,45 @@ void Control::processNetwork() delete data; } +/// +/// Helper to write control channel frame data. +/// +/// +bool Control::writeRF_ControlData() +{ + if (!m_control) + return false; + + if (m_ccFrameCnt == 254U) { + m_ccFrameCnt = 0U; + } + + // don't add any frames if the queue is full + uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; + uint32_t space = m_queue.freeSpace(); + if (space < (len + 1U)) { + return false; + } + + const uint8_t maxSeq = 8U; + if (m_ccSeq == maxSeq) { + m_ccSeq = 0U; + } + + if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { + m_trunk->writeRF_ControlData(m_ccFrameCnt, m_ccSeq, true); + + m_ccSeq++; + if (m_ccSeq == maxSeq) { + m_ccFrameCnt++; + } + + return true; + } + + return false; +} + /// /// Helper to write RF end of frame data. /// diff --git a/nxdn/Control.h b/nxdn/Control.h index f4c7e312..62f916f0 100644 --- a/nxdn/Control.h +++ b/nxdn/Control.h @@ -34,8 +34,9 @@ #include "Defines.h" #include "nxdn/NXDNDefines.h" #include "nxdn/channel/LICH.h" -#include "nxdn/lc/LC.h" +#include "nxdn/lc/RTCH.h" #include "nxdn/packet/Voice.h" +#include "nxdn/packet/Trunk.h" #include "nxdn/packet/Data.h" #include "nxdn/SiteData.h" #include "network/BaseNetwork.h" @@ -59,6 +60,7 @@ namespace nxdn // --------------------------------------------------------------------------- namespace packet { class HOST_SW_API Voice; } + namespace packet { class HOST_SW_API Trunk; } namespace packet { class HOST_SW_API Data; } // --------------------------------------------------------------------------- @@ -66,13 +68,13 @@ namespace nxdn // This class implements core logic for handling NXDN. // --------------------------------------------------------------------------- - class Control { + class HOST_SW_API Control { public: /// Initializes a new instance of the Control class. Control(uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, - bool debug, bool verbose); + bool dumpRCCHData, bool debug, bool verbose); /// Finalizes a instance of the Control class. ~Control(); @@ -82,6 +84,15 @@ namespace nxdn /// Helper to set NXDN configuration options. void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, uint16_t locId, uint8_t channelId, uint32_t channelNo, bool printOptions); + + /// Gets a flag indicating whether the NXDN control channel is running. + bool getCCRunning() { return m_ccRunning; } + /// Sets a flag indicating whether the NXDN control channel is running. + void setCCRunning(bool ccRunning) { m_ccPrevRunning = m_ccRunning; m_ccRunning = ccRunning; } + /// Gets a flag indicating whether the NXDN control channel is running. + bool getCCHalted() { return m_ccHalted; } + /// Sets a flag indicating whether the NXDN control channel is halted. + void setCCHalted(bool ccHalted) { m_ccHalted = ccHalted; } /// Process a data frame from the RF interface. bool processFrame(uint8_t* data, uint32_t len); @@ -102,6 +113,8 @@ namespace nxdn packet::Voice* m_voice; friend class packet::Data; packet::Data* m_data; + friend class packet::Trunk; + packet::Trunk* m_trunk; uint32_t m_ran; uint32_t m_timeout; @@ -115,8 +128,8 @@ namespace nxdn bool m_voiceOnControl; channel::LICH m_rfLastLICH; - lc::LC m_rfLC; - lc::LC m_netLC; + lc::RTCH m_rfLC; + lc::RTCH m_netLC; uint8_t m_rfMask; uint8_t m_netMask; @@ -143,6 +156,11 @@ namespace nxdn Timer m_netTimeout; Timer m_networkWatchdog; + Timer m_ccPacketInterval; + + uint8_t m_ccFrameCnt; + uint8_t m_ccSeq; + SiteData m_siteData; lookups::RSSIInterpolator* m_rssiMapper; @@ -161,6 +179,9 @@ namespace nxdn /// Process a data frames from the network. void processNetwork(); + /// Helper to write control channel frame data. + bool writeRF_ControlData(); + /// void scrambler(uint8_t* data) const; diff --git a/nxdn/NXDNDefines.h b/nxdn/NXDNDefines.h index ecabde78..78c2027e 100644 --- a/nxdn/NXDNDefines.h +++ b/nxdn/NXDNDefines.h @@ -12,6 +12,7 @@ // /* * Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX +* 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 @@ -80,6 +81,7 @@ namespace nxdn const uint32_t NXDN_UDCH_LENGTH_BITS = 203U; // Data + CRC-15 + 4-bit NULL const uint32_t NXDN_UDCH_LENGTH_BYTES = (NXDN_UDCH_LENGTH_BITS / 8U) + 1U; const uint32_t NXDH_UDCH_CRC_BITS = 184U; // Data + const uint32_t NXDH_UDCH_CRC_BYTES = NXDH_UDCH_CRC_BITS / 8U; const uint32_t NXDN_CAC_LENGTH_BITS = 384U; const uint32_t NXDN_CAC_LENGTH_BYTES = (NXDN_CAC_LENGTH_BITS / 8U); @@ -100,12 +102,17 @@ namespace nxdn const uint32_t NXDN_CAC_LONG_IN_LENGTH_BITS = 156U; // Data + CRC-16 + 4-bit NULL const uint32_t NXDN_CAC_LONG_IN_LENGTH_BYTES = (NXDN_CAC_LONG_IN_LENGTH_BITS / 8U) + 1U; const uint32_t NXDN_CAC_LONG_IN_CRC_BITS = 136U; // Data + const uint32_t NXDN_CAC_LONG_IN_CRC_BYTES = NXDN_CAC_LONG_IN_CRC_BITS / 8U; const uint32_t NXDN_CAC_SHORT_IN_FEC_LENGTH_BITS = 252U; // Interleave Length const uint32_t NXDN_CAC_SHORT_IN_FEC_LENGTH_BYTES = (NXDN_CAC_SHORT_IN_FEC_LENGTH_BITS / 8U) + 1U; const uint32_t NXDN_CAC_SHORT_IN_LENGTH_BITS = 126U; // Data + CRC-16 + 4-bit NULL const uint32_t NXDN_CAC_SHORT_IN_LENGTH_BYTES = (NXDN_CAC_SHORT_IN_LENGTH_BITS / 8U) + 1U; const uint32_t NXDN_CAC_SHORT_IN_CRC_BITS = 106U; // Data + const uint32_t NXDN_CAC_SHORT_IN_CRC_BYTES = (NXDN_CAC_SHORT_IN_CRC_BITS / 8U) + 1U; + + const uint32_t NXDN_RTCH_LC_LENGTH_BYTES = 22U; + const uint32_t NXDN_RCCH_LC_LENGTH_BYTES = 22U; const uint32_t NXDN_E_POST_FIELD_BITS = 24U; @@ -154,6 +161,8 @@ namespace nxdn const uint8_t NXDN_NULL_AMBE[] = { 0xB1U, 0xA8U, 0x22U, 0x25U, 0x6BU, 0xD1U, 0x6CU, 0xCFU, 0x67U }; + const uint8_t NXDN_CALLSIGN_LENGTH_BYTES = 8U; + const uint32_t NXDN_MI_LENGTH_BYTES = 8U; const uint32_t NXDN_PCKT_INFO_LENGTH_BYTES = 3U; @@ -197,6 +206,16 @@ namespace nxdn const uint8_t NXDN_SIF2_STATUS_CALL_REM_CTRL = 0x40U; const uint8_t NXDN_SIF2_SHORT_DATA_CALL_SVC = 0x80U; + const uint8_t NXDN_CH_ACCESS_STEP_SYS_DEFINED = 0x00U; + const uint8_t NXDN_CH_ACCESS_STEP_1DOT25K = 0x02U; + const uint8_t NXDN_CH_ACCESS_STEP_3DOT125K = 0x03U; + + const uint8_t NXDN_CH_ACCESS_BASE_FREQ_100 = 0x01U; + const uint8_t NXDN_CH_ACCESS_BASE_FREQ_330 = 0x02U; + const uint8_t NXDN_CH_ACCESS_BASE_FREQ_400 = 0x03U; + const uint8_t NXDN_CH_ACCESS_BASE_FREQ_750 = 0x04U; + const uint8_t NXDN_CH_ACCESS_BASE_FREQ_SYS_DEFINED = 0x07U; + // Common Message Types const uint8_t MESSAGE_TYPE_IDLE = 0x10U; // IDLE - Idle const uint8_t MESSAGE_TYPE_DISC = 0x11U; // DISC - Disconnect diff --git a/nxdn/SiteData.h b/nxdn/SiteData.h index ca16f45b..d48de9a4 100644 --- a/nxdn/SiteData.h +++ b/nxdn/SiteData.h @@ -11,7 +11,7 @@ // Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) // /* -* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* 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 @@ -61,7 +61,7 @@ namespace nxdn /// Channel Number. /// Service class. /// - SiteData(uint16_t locId, uint8_t channelId, uint32_t channelNo, uint8_t serviceClass, bool requireReq) : + SiteData(uint32_t locId, uint8_t channelId, uint32_t channelNo, uint8_t serviceClass, bool requireReq) : m_locId(locId), m_channelId(1U), m_channelNo(1U), @@ -71,8 +71,8 @@ namespace nxdn m_requireReg(requireReq), m_netActive(false) { - if (m_locId > 0xFFFU) - m_locId = 0xFFFU; + if (m_locId > 0xFFFFFU) + m_locId = 0xFFFFFU; // channel id clamping if (channelId > 15U) @@ -110,8 +110,8 @@ namespace nxdn /// Service class. void setAdjSite(uint32_t locId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, uint8_t serviceClass) { - if (locId > 0xFFFU) - locId = 0xFFFU; + if (m_locId > 0xFFFFFU) + m_locId = 0xFFFFFU; // channel id clamping if (channelId > 15U) @@ -165,7 +165,7 @@ namespace nxdn public: /// NXDN location ID. - __READONLY_PROPERTY_PLAIN(uint16_t, locId, locId); + __READONLY_PROPERTY_PLAIN(uint32_t, locId, locId); /// Channel ID. __READONLY_PROPERTY_PLAIN(uint8_t, channelId, channelId); /// Channel number. diff --git a/nxdn/channel/CAC.cpp b/nxdn/channel/CAC.cpp index e118a1f0..c81f524b 100644 --- a/nxdn/channel/CAC.cpp +++ b/nxdn/channel/CAC.cpp @@ -316,7 +316,7 @@ void CAC::getData(uint8_t* data) const assert(data != NULL); uint32_t offset = 8U; - for (uint32_t i = 0U; i < (NXDN_CAC_LONG_IN_CRC_BITS - 8); i++, offset++) { + for (uint32_t i = 0U; i < (NXDN_CAC_SHORT_IN_CRC_BITS - 8); i++, offset++) { bool b = READ_BIT(m_data, offset); WRITE_BIT(data, i, b); } @@ -335,8 +335,6 @@ void CAC::setData(const uint8_t* data) bool b = READ_BIT(data, i); WRITE_BIT(m_data, offset, b); } - - m_ran = m_data[0U] & 0x3FU; } // --------------------------------------------------------------------------- diff --git a/nxdn/channel/SACCH.cpp b/nxdn/channel/SACCH.cpp index 460a27a0..5484417e 100644 --- a/nxdn/channel/SACCH.cpp +++ b/nxdn/channel/SACCH.cpp @@ -273,9 +273,6 @@ void SACCH::setData(const uint8_t* data) bool b = READ_BIT(data, i); WRITE_BIT(m_data, offset, b); } - - m_ran = m_data[0U] & 0x3FU; - m_structure = (m_data[0U] >> 6) & 0x03U; } // --------------------------------------------------------------------------- diff --git a/nxdn/channel/UDCH.cpp b/nxdn/channel/UDCH.cpp index 7a0cce5e..0f470a1f 100644 --- a/nxdn/channel/UDCH.cpp +++ b/nxdn/channel/UDCH.cpp @@ -282,8 +282,6 @@ void UDCH::setData(const uint8_t* data) assert(data != NULL); ::memcpy(m_data + 1U, data, 22U); - - m_ran = m_data[0U] & 0x3FU; } // --------------------------------------------------------------------------- diff --git a/nxdn/lc/RCCH.cpp b/nxdn/lc/RCCH.cpp new file mode 100644 index 00000000..e155b998 --- /dev/null +++ b/nxdn/lc/RCCH.cpp @@ -0,0 +1,406 @@ +/** +* 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 "nxdn/NXDNDefines.h" +#include "nxdn/lc/RCCH.h" +#include "Log.h" +#include "Utils.h" + +using namespace nxdn; +using namespace nxdn::lc; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the RCCH class. +/// +/// +RCCH::RCCH(const RCCH& data) : RCCH(SiteData()) +{ + copy(data); +} + +/// +/// Initializes a new instance of the RCCH class. +/// +/// +/// +RCCH::RCCH(SiteData siteData, lookups::IdenTable entry) : RCCH(siteData) +{ + m_siteIdenEntry = entry; +} + +/// +/// Initializes a new instance of the RCCH class. +/// +/// +/// +/// +RCCH::RCCH(SiteData siteData, lookups::IdenTable entry, bool verbose) : RCCH(siteData) +{ + m_verbose = verbose; + m_siteIdenEntry = entry; +} + +/// +/// Finalizes a instance of RCCH class. +/// +RCCH::~RCCH() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +RCCH& RCCH::operator=(const RCCH& data) +{ + if (&data != this) { + ::memcpy(m_data, data.m_data, NXDN_RCCH_LC_LENGTH_BYTES); + decodeLC(m_data); + } + + return *this; +} + +/// +/// Decode call link control data. +/// +/// +/// True, if RCCH was decoded, otherwise false. +void RCCH::decode(const uint8_t* data, uint32_t length, uint32_t offset) +{ + assert(data != NULL); + + for (uint32_t i = 0U; i < length; i++, offset++) { + bool b = READ_BIT(data, i); + WRITE_BIT(m_data, offset, b); + } + + if (m_verbose) { + Utils::dump(2U, "Decoded RCCH Data", m_data, NXDN_RCCH_LC_LENGTH_BYTES); + } + + decodeLC(m_data); +} + +/// +/// Encode call link control data. +/// +/// +/// +/// +void RCCH::encode(uint8_t* data, uint32_t length, uint32_t offset) +{ + assert(data != NULL); + + encodeLC(m_data); + + for (uint32_t i = 0U; i < length; i++, offset++) { + bool b = READ_BIT(m_data, offset); + WRITE_BIT(data, i, b); + } + + if (m_verbose) { + Utils::dump(2U, "Encoded RCCH Data", data, length); + } +} + +/// +/// +/// +void RCCH::reset() +{ + ::memset(m_data, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + m_messageType = MESSAGE_TYPE_IDLE; + + m_srcId = 0U; + m_dstId = 0U; + + m_locId = 0U; + m_regOption = 0U; + + m_version = 0U; + + m_causeRsp = NXDN_CAUSE_MM_NORMAL_1; +} + +/// +/// Gets the raw layer 3 data. +/// +/// +void RCCH::getData(uint8_t* data) const +{ + ::memcpy(data, m_data, NXDN_RCCH_LC_LENGTH_BYTES); +} + +/// +/// Sets the raw layer 3 data. +/// +/// +/// +void RCCH::setData(const uint8_t* data, uint32_t length) +{ + ::memset(m_data, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + ::memcpy(m_data, data, length); + + decodeLC(m_data); +} + +/// +/// Sets the callsign. +/// +/// Callsign. +void RCCH::setCallsign(std::string callsign) +{ + uint32_t idLength = callsign.length(); + if (idLength > 0) { + ::memset(m_siteCallsign, 0x20U, NXDN_CALLSIGN_LENGTH_BYTES); + + if (idLength > NXDN_CALLSIGN_LENGTH_BYTES) + idLength = NXDN_CALLSIGN_LENGTH_BYTES; + for (uint32_t i = 0; i < idLength; i++) + m_siteCallsign[i] = callsign[i]; + } +} + +/// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the RCCH class. +/// +RCCH::RCCH() : RCCH(SiteData()) +{ + /* stub */ +} + +/// +/// Initializes a new instance of the RCCH class. +/// +/// +RCCH::RCCH(SiteData siteData) : + m_verbose(false), + m_messageType(MESSAGE_TYPE_IDLE), + m_srcId(0U), + m_dstId(0U), + m_locId(0U), + m_regOption(0U), + m_version(0U), + m_causeRsp(NXDN_CAUSE_MM_NORMAL_1), + m_siteData(siteData), + m_siteIdenEntry(), + m_data(NULL) +{ + m_data = new uint8_t[NXDN_RCCH_LC_LENGTH_BYTES]; + ::memset(m_data, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + m_siteCallsign = new uint8_t[NXDN_CALLSIGN_LENGTH_BYTES]; + ::memset(m_siteCallsign, 0x00U, NXDN_CALLSIGN_LENGTH_BYTES); + setCallsign(siteData.callsign()); +} + +/// +/// Decode link control. +/// +/// +/// +bool RCCH::decodeLC(const uint8_t* data) +{ + if (m_verbose) { + Utils::dump(2U, "Decoded RCCH", data, NXDN_RCCH_LC_LENGTH_BYTES); + } + + m_messageType = data[0U] & 0x3FU; // Message Type + + // message type opcodes + switch (m_messageType) { + case MESSAGE_TYPE_IDLE: + break; + case RCCH_MESSAGE_TYPE_REG: + m_regOption = data[1U] >> 3; // Registration Option + m_locId = (uint16_t)((data[2U] << 8) | data[3U]) & 0xFFFFU; // Location ID + m_srcId = (uint16_t)((data[4U] << 8) | data[5U]) & 0xFFFFU; // Source Radio Address + m_dstId = (uint16_t)((data[6U] << 8) | data[7U]) & 0xFFFFU; // Target Radio Address + // bryanb: maybe process subscriber type? (byte 8 and 9) + m_version = data[10U]; // Version + break; + case RCCH_MESSAGE_TYPE_REG_C: + m_regOption = data[1U] >> 3; // Registration Option + m_locId = (uint16_t)((data[2U] << 8) | data[3U]) & 0xFFFFU; // Location ID + m_srcId = (uint16_t)((data[4U] << 8) | data[5U]) & 0xFFFFU; // Source Radio Address + break; + case RCCH_MESSAGE_TYPE_GRP_REG: + m_regOption = data[1U]; // Group Registration Option + m_srcId = (uint16_t)((data[2U] << 8) | data[3U]) & 0xFFFFU; // Source Radio Address + m_dstId = (uint16_t)((data[4U] << 8) | data[5U]) & 0xFFFFU; // Target Radio Address + break; + default: + LogError(LOG_NXDN, "RCCH::decodeRCCH(), unknown RCCH value, messageType = $%02X", m_messageType); + return false; + } + + return true; +} + +/// +/// Encode link control. +/// +/// +void RCCH::encodeLC(uint8_t* data) +{ + m_messageType = m_data[0U] & 0x3FU; // Message Type + + // message type opcodes + switch (m_messageType) { + case MESSAGE_TYPE_IDLE: + break; + case MESSAGE_TYPE_DST_ID_INFO: + m_data[1U] = 0xC0U + NXDN_CALLSIGN_LENGTH_BYTES; // Station ID Option - Start / End / Character Count + m_data[2U] = (m_siteCallsign[0]); // Character 0 + for (uint8_t i = 1; i < NXDN_CALLSIGN_LENGTH_BYTES; i++) { + m_data[i + 2U] = m_siteCallsign[i]; // Character 1 - 7 + } + break; + case RCCH_MESSAGE_TYPE_SITE_INFO: + { + m_data[1U] = (m_siteData.locId() >> 16) & 0xFFU; // Location ID + m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ... + m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + + // bryanb: this is currently fixed -- maybe dynamic in the future + m_data[4U] = (1 << 6) + // Channel Structure - Number of BCCH (1) + (1 << 3) + // ... - Number of Grouping (1) + (1 << 0); // ... - Number of Paging Frames (1) + m_data[5U] = (1 << 0); // ... - Number of Iteration (1) + + m_data[6U] = m_siteData.serviceClass(); // Service Information + m_data[7U] = (m_siteData.netActive() ? NXDN_SIF2_IP_NETWORK : 0x00U); // ... + + // bryanb: this is currently fixed -- maybe dynamic in the future + m_data[8U] = 0U; // Restriction Information - No access restriction / No cycle restriction + m_data[9U] = 0x08U; // ... - No group restriction / GMS; Location Registration Restriction + m_data[10U] = (!m_siteData.netActive() ? 0x01U : 0x00U); // ... - No group ratio restriction / No delay time extension / ISO + + // bryanb: this is currently fixed -- maybe dynamic in the future + m_data[11U] = NXDN_CH_ACCESS_BASE_FREQ_SYS_DEFINED; // Channel Access Information - Channel Version / Sys Defined Step / Sys Defined Base Freq + + m_data[14U] = 1U; // Version + + uint32_t channelNo = m_siteData.channelNo(); + m_data[15U] = (channelNo >> 6) & 0x0FU; // 1st Control Channel + m_data[16U] = (channelNo & 0x3FU) << 2; // ... + } + break; + case MESSAGE_TYPE_SRV_INFO: + m_data[1U] = (m_siteData.locId() >> 16) & 0xFFU; // Location ID + m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ... + m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + m_data[4U] = m_siteData.serviceClass(); // Service Information + m_data[5U] = (m_siteData.netActive() ? NXDN_SIF2_IP_NETWORK : 0x00U); // ... + + // bryanb: this is currently fixed -- maybe dynamic in the future + m_data[6U] = 0U; // Restriction Information - No access restriction / No cycle restriction + m_data[7U] = 0x08U; // ... - No group restriction / GMS; Location Registration Restriction + m_data[8U] = (!m_siteData.netActive() ? 0x01U : 0x00U); // ... - No group ratio restriction / No delay time extension / ISO + break; + case RCCH_MESSAGE_TYPE_REG: + m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ... + m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + m_data[4U] = (m_srcId >> 8U) & 0xFFU; // Source Radio Address + m_data[5U] = (m_srcId >> 0U) & 0xFFU; // ... + m_data[6U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address + m_data[7U] = (m_dstId >> 0U) & 0xFFU; // ... + m_data[8U] = m_causeRsp; // Cause (MM) + break; + case RCCH_MESSAGE_TYPE_REG_C: + m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID + m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + m_data[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address + m_data[5U] = (m_dstId >> 0U) & 0xFFU; // ... + m_data[6U] = m_causeRsp; // Cause (MM) + break; + case RCCH_MESSAGE_TYPE_REG_COMM: + m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID + m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + m_data[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address + m_data[5U] = (m_dstId >> 0U) & 0xFFU; // ... + break; + case RCCH_MESSAGE_TYPE_GRP_REG: + m_data[2U] = (m_srcId >> 8U) & 0xFFU; // Source Radio Address + m_data[3U] = (m_srcId >> 0U) & 0xFFU; // ... + m_data[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address + m_data[5U] = (m_dstId >> 0U) & 0xFFU; // ... + m_data[6U] = m_causeRsp; // Cause (MM) + m_data[8U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID + m_data[9U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + break; + default: + LogError(LOG_NXDN, "RCCH::encodeRCCH(), unknown RCCH value, messageType = $%02X", m_messageType); + return; + } + + if (m_verbose) { + Utils::dump(2U, "Encoded RCCH", m_data, NXDN_RCCH_LC_LENGTH_BYTES); + } +} + +// +/// Internal helper to copy the the class. +/// +/// +void RCCH::copy(const RCCH& data) +{ + m_data = new uint8_t[22U]; + ::memcpy(m_data, data.m_data, 22U); + + m_verbose = data.m_verbose; + + m_siteData = data.m_siteData; + m_siteIdenEntry = data.m_siteIdenEntry; + + delete[] m_siteCallsign; + + uint8_t* callsign = new uint8_t[NXDN_CALLSIGN_LENGTH_BYTES]; + ::memcpy(callsign, data.m_siteCallsign, NXDN_CALLSIGN_LENGTH_BYTES); + + m_siteCallsign = callsign; + + decodeLC(m_data); +} diff --git a/nxdn/lc/RCCH.h b/nxdn/lc/RCCH.h new file mode 100644 index 00000000..1d865e6d --- /dev/null +++ b/nxdn/lc/RCCH.h @@ -0,0 +1,129 @@ +/** +* 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(__NXDN_LC__RCCH_H__) +#define __NXDN_LC__RCCH_H__ + +#include "Defines.h" +#include "nxdn/lc/PacketInformation.h" +#include "nxdn/SiteData.h" +#include "lookups/IdenTableLookup.h" + +namespace nxdn +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents link control data for control channel NXDN calls. + // --------------------------------------------------------------------------- + + class HOST_SW_API RCCH { + public: + /// Initializes a copy instance of the RCCH class. + RCCH(const RCCH& data); + /// Initializes a new instance of the TSBK class. + RCCH(SiteData siteData, lookups::IdenTable entry); + /// Initializes a new instance of the TSBK class. + RCCH(SiteData siteData, lookups::IdenTable entry, bool verbose); + /// Finalizes a instance of the RCCH class. + ~RCCH(); + + /// Equals operator. + RCCH& operator=(const RCCH& data); + + /// Decode layer 3 data. + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + /// Encode layer 3 data. + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// + void reset(); + + /// Gets the raw layer 3 data. + void getData(uint8_t* data) const; + /// Sets the raw layer 3 data. + void setData(const uint8_t* data, uint32_t length); + + /// Sets the callsign. + void setCallsign(std::string callsign); + + public: + /// Flag indicating verbose log output. + __PROPERTY(bool, verbose, Verbose); + + /** Common Data */ + /// Message Type + __PROPERTY(uint8_t, messageType, MessageType); + + /// Source ID. + __PROPERTY(uint16_t, srcId, SrcId); + /// Destination ID. + __PROPERTY(uint16_t, dstId, DstId); + + /// Location ID. + __PROPERTY(uint32_t, locId, LocId); + /// Registration Option. + __PROPERTY(uint8_t, regOption, RegOption); + + /// Version Number. + __PROPERTY(uint8_t, version, Version); + + /// Cause Response. + __PROPERTY(uint8_t, causeRsp, CauseResponse); + + /** Local Site data */ + /// Local Site Data. + __PROPERTY_PLAIN(SiteData, siteData, siteData); + /// Local Site Identity Entry. + __PROPERTY_PLAIN(lookups::IdenTable, siteIdenEntry, siteIdenEntry); + + private: + /// Initializes a new instance of the RCCH class. + RCCH(); + /// Initializes a new instance of the RCCH class. + RCCH(SiteData siteData); + + uint8_t* m_data; + + /** Local Site data */ + uint8_t* m_siteCallsign; + + /// Decode link control. + bool decodeLC(const uint8_t* data); + /// Encode link control. + void encodeLC(uint8_t* data); + + /// Internal helper to copy the class. + void copy(const RCCH& data); + }; + } // namespace lc +} // namespace nxdn + +#endif // __NXDN_LC__RCCH_H__ diff --git a/nxdn/lc/LC.cpp b/nxdn/lc/RTCH.cpp similarity index 92% rename from nxdn/lc/LC.cpp rename to nxdn/lc/RTCH.cpp index d1d2bfda..ce0ff89c 100644 --- a/nxdn/lc/LC.cpp +++ b/nxdn/lc/RTCH.cpp @@ -29,7 +29,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "nxdn/NXDNDefines.h" -#include "nxdn/lc/LC.h" +#include "nxdn/lc/RTCH.h" #include "Log.h" #include "Utils.h" @@ -45,9 +45,9 @@ using namespace nxdn::lc; // --------------------------------------------------------------------------- /// -/// Initializes a new instance of the LC class. +/// Initializes a new instance of the RTCH class. /// -LC::LC() : +RTCH::RTCH() : m_verbose(false), m_messageType(MESSAGE_TYPE_IDLE), m_callType(CALL_TYPE_UNSPECIFIED), @@ -69,18 +69,18 @@ LC::LC() : m_causeRsp(NXDN_CAUSE_VD_NORMAL_1), m_data(NULL) { - m_data = new uint8_t[22U]; - ::memset(m_data, 0x00U, 22U); + m_data = new uint8_t[NXDN_RTCH_LC_LENGTH_BYTES]; + ::memset(m_data, 0x00U, NXDN_RTCH_LC_LENGTH_BYTES); m_mi = new uint8_t[NXDN_MI_LENGTH_BYTES]; ::memset(m_mi, 0x00U, NXDN_MI_LENGTH_BYTES); } /// -/// Initializes a copy instance of the LC class. +/// Initializes a copy instance of the RTCH class. /// /// -LC::LC(const LC& data) : +RTCH::RTCH(const RTCH& data) : m_verbose(false), m_messageType(MESSAGE_TYPE_IDLE), m_callType(CALL_TYPE_UNSPECIFIED), @@ -106,9 +106,9 @@ LC::LC(const LC& data) : } /// -/// Finalizes a instance of LC class. +/// Finalizes a instance of RTCH class. /// -LC::~LC() +RTCH::~RTCH() { delete[] m_data; delete[] m_mi; @@ -119,10 +119,10 @@ LC::~LC() /// /// /// -LC& LC::operator=(const LC& data) +RTCH& RTCH::operator=(const RTCH& data) { if (&data != this) { - ::memcpy(m_data, data.m_data, 22U); + ::memcpy(m_data, data.m_data, NXDN_RTCH_LC_LENGTH_BYTES); decodeLC(m_data); } @@ -133,8 +133,8 @@ LC& LC::operator=(const LC& data) /// Decode call link control data. /// /// -/// True, if LC was decoded, otherwise false. -void LC::decode(const uint8_t* data, uint32_t length, uint32_t offset) +/// True, if RTCH was decoded, otherwise false. +void RTCH::decode(const uint8_t* data, uint32_t length, uint32_t offset) { assert(data != NULL); @@ -144,7 +144,7 @@ void LC::decode(const uint8_t* data, uint32_t length, uint32_t offset) } if (m_verbose) { - Utils::dump(2U, "Decoded LC Data", m_data, 22U); + Utils::dump(2U, "Decoded RTCH Data", m_data, NXDN_RTCH_LC_LENGTH_BYTES); } decodeLC(m_data); @@ -156,7 +156,7 @@ void LC::decode(const uint8_t* data, uint32_t length, uint32_t offset) /// /// /// -void LC::encode(uint8_t* data, uint32_t length, uint32_t offset) +void RTCH::encode(uint8_t* data, uint32_t length, uint32_t offset) { assert(data != NULL); @@ -168,16 +168,16 @@ void LC::encode(uint8_t* data, uint32_t length, uint32_t offset) } if (m_verbose) { - Utils::dump(2U, "Encoded LC Data", data, length); + Utils::dump(2U, "Encoded RTCH Data", data, length); } } /// /// /// -void LC::reset() +void RTCH::reset() { - ::memset(m_data, 0x00U, 22U); + ::memset(m_data, 0x00U, NXDN_RTCH_LC_LENGTH_BYTES); m_messageType = MESSAGE_TYPE_IDLE; m_callType = CALL_TYPE_UNSPECIFIED; @@ -209,9 +209,9 @@ void LC::reset() /// Gets the raw layer 3 data. /// /// -void LC::getData(uint8_t* data) const +void RTCH::getData(uint8_t* data) const { - ::memcpy(data, m_data, 22U); + ::memcpy(data, m_data, NXDN_RTCH_LC_LENGTH_BYTES); } /// @@ -219,9 +219,9 @@ void LC::getData(uint8_t* data) const /// /// /// -void LC::setData(const uint8_t* data, uint32_t length) +void RTCH::setData(const uint8_t* data, uint32_t length) { - ::memset(m_data, 0x00U, 22U); + ::memset(m_data, 0x00U, NXDN_RTCH_LC_LENGTH_BYTES); ::memcpy(m_data, data, length); decodeLC(m_data); @@ -237,7 +237,7 @@ void LC::setData(const uint8_t* data, uint32_t length) /// /// /// -bool LC::decodeLC(const uint8_t* data) +bool RTCH::decodeLC(const uint8_t* data) { m_messageType = data[0U] & 0x3FU; // Message Type @@ -340,7 +340,7 @@ bool LC::decodeLC(const uint8_t* data) m_causeRsp = data[7U]; // Cause (SS) break; default: - LogError(LOG_NXDN, "LC::decodeLC(), unknown LC value, messageType = $%02X", m_messageType); + LogError(LOG_NXDN, "RTCH::decodeRTCH(), unknown RTCH value, messageType = $%02X", m_messageType); return false; } @@ -351,7 +351,7 @@ bool LC::decodeLC(const uint8_t* data) /// Encode link control. /// /// -void LC::encodeLC(uint8_t* data) +void RTCH::encodeLC(uint8_t* data) { m_messageType = m_data[0U] & 0x3FU; // Message Type @@ -460,7 +460,7 @@ void LC::encodeLC(uint8_t* data) m_packetInfo.encode(m_messageType, data + 8U); // Packet Information break; default: - LogError(LOG_NXDN, "LC::encodeLC(), unknown LC value, messageType = $%02X", m_messageType); + LogError(LOG_NXDN, "RTCH::encodeRTCH(), unknown RTCH value, messageType = $%02X", m_messageType); return; } } @@ -469,10 +469,10 @@ void LC::encodeLC(uint8_t* data) /// Internal helper to copy the the class. /// /// -void LC::copy(const LC& data) +void RTCH::copy(const RTCH& data) { - m_data = new uint8_t[22U]; - ::memcpy(m_data, data.m_data, 22U); + m_data = new uint8_t[NXDN_RTCH_LC_LENGTH_BYTES]; + ::memcpy(m_data, data.m_data, NXDN_RTCH_LC_LENGTH_BYTES); m_verbose = data.m_verbose; diff --git a/nxdn/lc/LC.h b/nxdn/lc/RTCH.h similarity index 89% rename from nxdn/lc/LC.h rename to nxdn/lc/RTCH.h index 1dd1a9e7..1448da82 100644 --- a/nxdn/lc/LC.h +++ b/nxdn/lc/RTCH.h @@ -28,8 +28,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#if !defined(__NXDN_LC__LC_H__) -#define __NXDN_LC__LC_H__ +#if !defined(__NXDN_LC__RTCH_H__) +#define __NXDN_LC__RTCH_H__ #include "Defines.h" #include "nxdn/lc/PacketInformation.h" @@ -40,20 +40,20 @@ namespace nxdn { // --------------------------------------------------------------------------- // Class Declaration - // Represents link control data for NXDN calls. + // Represents link control data for traffic channel NXDN calls. // --------------------------------------------------------------------------- - class HOST_SW_API LC { + class HOST_SW_API RTCH { public: - /// Initializes a new instance of the LC class. - LC(); - /// Initializes a copy instance of the LC class. - LC(const LC& data); - /// Finalizes a instance of the LC class. - ~LC(); + /// Initializes a new instance of the RTCH class. + RTCH(); + /// Initializes a copy instance of the RTCH class. + RTCH(const RTCH& data); + /// Finalizes a instance of the RTCH class. + ~RTCH(); /// Equals operator. - LC& operator=(const LC& data); + RTCH& operator=(const RTCH& data); /// Decode layer 3 data. void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); @@ -134,9 +134,9 @@ namespace nxdn void encodeLC(uint8_t* data); /// Internal helper to copy the class. - void copy(const LC& data); + void copy(const RTCH& data); }; } // namespace lc } // namespace nxdn -#endif // __NXDN_LC__LC_H__ +#endif // __NXDN_LC__RTCH_H__ diff --git a/nxdn/packet/Data.cpp b/nxdn/packet/Data.cpp index 77ff0b01..d21b5353 100644 --- a/nxdn/packet/Data.cpp +++ b/nxdn/packet/Data.cpp @@ -173,7 +173,7 @@ void Data::resetNet() /// /// Process a data frame from the RF interface. /// -/// +/// Channel options. /// Buffer containing data frame. /// Length of data frame. /// @@ -192,12 +192,12 @@ bool Data::process(uint8_t option, uint8_t* data, uint32_t len) return false; } - // The layer3 data will only be correct if valid is true - uint8_t buffer[23U]; + // the layer 3 LC data will only be correct if valid is true + uint8_t buffer[NXDN_UDCH_LENGTH_BYTES]; udch.getData(buffer); - lc::LC lc; - lc.decode(buffer, 184U); + lc::RTCH lc; + lc.decode(buffer, NXDH_UDCH_CRC_BITS); uint16_t dstId = lc.getDstId(); uint16_t srcId = lc.getSrcId(); bool group = lc.getGroup(); @@ -279,12 +279,12 @@ bool Data::process(uint8_t option, uint8_t* data, uint32_t len) /// /// Process a data frame from the RF interface. /// -/// +/// Channel options. /// /// Buffer containing data frame. /// Length of data frame. /// -bool Data::processNetwork(uint8_t option, lc::LC& netLC, uint8_t* data, uint32_t len) +bool Data::processNetwork(uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len) { assert(data != NULL); @@ -301,11 +301,11 @@ bool Data::processNetwork(uint8_t option, lc::LC& netLC, uint8_t* data, uint32_t return false; // The layer3 data will only be correct if valid is true - uint8_t buffer[23U]; + uint8_t buffer[NXDN_UDCH_LENGTH_BYTES]; udch.getData(buffer); - lc::LC lc; - lc.decode(buffer, 184U); + lc::RTCH lc; + lc.decode(buffer, NXDH_UDCH_CRC_BITS); uint16_t dstId = lc.getDstId(); uint16_t srcId = lc.getSrcId(); bool group = lc.getGroup(); diff --git a/nxdn/packet/Data.h b/nxdn/packet/Data.h index fbbd596d..d0e9a66f 100644 --- a/nxdn/packet/Data.h +++ b/nxdn/packet/Data.h @@ -12,7 +12,7 @@ // /* * Copyright (C) 2015-2020 by Jonathan Naylor G4KLX -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL +* 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 @@ -63,11 +63,11 @@ namespace nxdn /// Process a data frame from the RF interface. virtual bool process(uint8_t option, uint8_t* data, uint32_t len); /// Process a data frame from the network. - virtual bool processNetwork(uint8_t option, lc::LC& netLC, uint8_t* data, uint32_t len); + virtual bool processNetwork(uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len); protected: friend class nxdn::Control; - Control *m_nxdn; + Control* m_nxdn; network::BaseNetwork* m_network; diff --git a/nxdn/packet/Trunk.cpp b/nxdn/packet/Trunk.cpp new file mode 100644 index 00000000..f50c0f92 --- /dev/null +++ b/nxdn/packet/Trunk.cpp @@ -0,0 +1,313 @@ +/** +* 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 "nxdn/NXDNDefines.h" +#include "nxdn/channel/CAC.h" +#include "nxdn/packet/Trunk.h" +#include "nxdn/acl/AccessControl.h" +#include "nxdn/Sync.h" +#include "edac/CRC.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace nxdn; +using namespace nxdn::packet; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Resets the data states for the RF interface. +/// +void Trunk::resetRF() +{ + lc::RCCH lc = lc::RCCH(m_nxdn->m_siteData, m_nxdn->m_idenEntry, m_dumpRCCH); + m_rfLC = lc; +} + +/// +/// Resets the data states for the network. +/// +void Trunk::resetNet() +{ + lc::RCCH lc = lc::RCCH(m_nxdn->m_siteData, m_nxdn->m_idenEntry, m_dumpRCCH); + m_netLC = lc; +} + +/// +/// Process a data frame from the RF interface. +/// +/// Functional channel type. +/// Channel options. +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Trunk::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + channel::CAC cac; + bool validCAC = cac.decode(data + 2U); + if (m_nxdn->m_rfState == RS_RF_LISTENING && !validCAC) + return false; + + if (validCAC) { + uint8_t ran = cac.getRAN(); + if (ran != m_nxdn->m_ran && ran != 0U) + return false; + } + + // The layer3 data will only be correct if valid is true + uint8_t buffer[NXDN_CAC_LENGTH_BYTES]; + cac.getData(buffer); + + m_rfLC.decode(buffer, NXDN_CAC_SHORT_IN_CRC_BITS); + + // TODO TODO -- process incoming data + + return true; +} + +/// +/// Process a data frame from the RF interface. +/// +/// Functional channel type. +/// Channel options. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Trunk::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + if (m_nxdn->m_netState == RS_NET_IDLE) { + m_nxdn->m_queue.clear(); + + resetRF(); + resetNet(); + } + + return true; +} + +/// +/// Updates the processor by the passed number of milliseconds. +/// +/// +void Trunk::clock(uint32_t ms) +{ + return; +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the Trunk class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether RCCH data is dumped to the log. +/// Flag indicating whether NXDN debug is enabled. +/// Flag indicating whether NXDN verbose logging is enabled. +Trunk::Trunk(Control* nxdn, network::BaseNetwork* network, bool dumpRCCHData, bool debug, bool verbose) : + m_nxdn(nxdn), + m_network(network), + m_rfLC(SiteData(), lookups::IdenTable()), + m_netLC(SiteData(), lookups::IdenTable()), + m_lastRejectId(0U), + m_dumpRCCH(dumpRCCHData), + m_verbose(verbose), + m_debug(debug) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Trunk class. +/// +Trunk::~Trunk() +{ + /* stub */ +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +void Trunk::writeNetwork(const uint8_t *data, uint32_t len) +{ + assert(data != NULL); + + if (m_network == NULL) + return; + + if (m_nxdn->m_rfTimeout.isRunning() && m_nxdn->m_rfTimeout.hasExpired()) + return; + + m_network->writeNXDN(m_nxdn->m_rfLC, data, len); +} + +/// +/// Helper to write control channel packet data. +/// +/// +/// +/// +void Trunk::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS) +{ + uint8_t i = 0U, seqCnt = 0U; + + if (!m_nxdn->m_control) + return; + + // don't add any frames if the queue is full + uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; + uint32_t space = m_nxdn->m_queue.freeSpace(); + if (space < (len + 1U)) { + return; + } + + do + { + if (m_debug) { + LogDebug(LOG_DMR, "writeRF_ControlData, frameCnt = %u, seq = %u", frameCnt, n); + } + + switch (n) + { + case 6: + writeRF_CC_Site_Info(); + break; + case 0: + default: + writeRF_CC_Service_Info(); + break; + } + + if (seqCnt > 0U) + n++; + i++; + } while (i <= seqCnt); +} + +/// +/// Helper to write a CC SITE_INFO broadcast packet on the RF interface. +/// +void Trunk::writeRF_CC_Site_Info() +{ + if (m_debug) { + LogMessage(LOG_RF, "NXDN, RCCH_MESSAGE_TYPE_SITE_INFO (Site Information)"); + } + + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); + + Sync::addNXDNSync(data + 2U); + + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RCCH); + lich.setFCT(NXDN_LICH_CAC_OUTBOUND); + lich.setOption(NXDN_LICH_DATA_NORMAL); + lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + m_rfLC.setMessageType(RCCH_MESSAGE_TYPE_SITE_INFO); + m_rfLC.encode(buffer, NXDN_CAC_OUT_CRC_BITS); + + channel::CAC cac; + cac.setRAN(m_nxdn->m_ran); + cac.setData(buffer); + cac.encode(data + 2U); + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->scrambler(data + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + } +} + +/// +/// Helper to write a CC SRV_INFO broadcast packet on the RF interface. +/// +void Trunk::writeRF_CC_Service_Info() +{ + if (m_debug) { + LogMessage(LOG_RF, "NXDN, MESSAGE_TYPE_SRV_INFO (Service Information)"); + } + + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); + + Sync::addNXDNSync(data + 2U); + + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RCCH); + lich.setFCT(NXDN_LICH_CAC_OUTBOUND); + lich.setOption(NXDN_LICH_DATA_NORMAL); + lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + m_rfLC.setMessageType(MESSAGE_TYPE_SRV_INFO); + m_rfLC.encode(buffer, NXDN_CAC_OUT_CRC_BITS); + + channel::CAC cac; + cac.setRAN(m_nxdn->m_ran); + cac.setData(buffer); + cac.encode(data + 2U); + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->scrambler(data + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + } +} diff --git a/nxdn/packet/Trunk.h b/nxdn/packet/Trunk.h new file mode 100644 index 00000000..db2da67a --- /dev/null +++ b/nxdn/packet/Trunk.h @@ -0,0 +1,106 @@ +/** +* 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(__NXDN_PACKET_TRUNK_H__) +#define __NXDN_PACKET_TRUNK_H__ + +#include "Defines.h" +#include "nxdn/Control.h" +#include "nxdn/lc/RCCH.h" +#include "network/BaseNetwork.h" + +#include +#include + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + class HOST_SW_API Control; + + namespace packet + { + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for NXDN control signalling packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API Trunk { + 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 fct, uint8_t option, uint8_t* data, uint32_t len); + /// Process a data frame from the network. + virtual bool processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len); + + /// Updates the processor by the passed number of milliseconds. + void clock(uint32_t ms); + + protected: + friend class nxdn::Control; + Control* m_nxdn; + + network::BaseNetwork* m_network; + + lc::RCCH m_rfLC; + lc::RCCH m_netLC; + + uint16_t m_lastRejectId; + + bool m_dumpRCCH; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the Trunk class. + Trunk(Control* nxdn, network::BaseNetwork* network, bool dumpRCCHData, bool debug, bool verbose); + /// Finalizes a instance of the Trunk class. + virtual ~Trunk(); + + /// Write data processed from RF to the network. + void writeNetwork(const uint8_t* data, uint32_t len); + + /// Helper to write control channel packet data. + void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS); + + /// Helper to write a CC SITE_INFO broadcast packet on the RF interface. + void writeRF_CC_Site_Info(); + /// Helper to write a CC SRV_INFO broadcast packet on the RF interface. + void writeRF_CC_Service_Info(); + }; + } // namespace packet +} // namespace nxdn + +#endif // __NXDN_PACKET_TRUNK_H__ diff --git a/nxdn/packet/Voice.cpp b/nxdn/packet/Voice.cpp index 2cb19ee6..25485b18 100644 --- a/nxdn/packet/Voice.cpp +++ b/nxdn/packet/Voice.cpp @@ -179,12 +179,12 @@ void Voice::resetNet() /// /// Process a data frame from the RF interface. /// -/// -/// +/// Functional channel type. +/// Channel options. /// Buffer containing data frame. /// Length of data frame. /// -bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) +bool Voice::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len) { assert(data != NULL); @@ -198,7 +198,7 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) return false; } - if (usc == NXDN_LICH_USC_SACCH_NS) { + if (fct == NXDN_LICH_USC_SACCH_NS) { // the SACCH on a non-superblock frame is usually an idle and not interesting apart from the RAN. channel::FACCH1 facch; bool valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); @@ -210,7 +210,7 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) uint8_t buffer[10U]; facch.getData(buffer); - lc::LC lc; + lc::RTCH lc; lc.decode(buffer, NXDN_FACCH1_LENGTH_BITS); uint16_t dstId = lc.getDstId(); uint16_t srcId = lc.getSrcId(); @@ -332,7 +332,7 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) uint8_t buffer[10U]; facch.getData(buffer); - lc::LC lc; + lc::RTCH lc; lc.decode(buffer, NXDN_FACCH1_LENGTH_BITS); hasInfo = lc.getMessageType() == RTCH_MESSAGE_TYPE_VCALL; @@ -604,13 +604,13 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) /// /// Process a data frame from the network. /// -/// -/// +/// Functional channel type. +/// Channel options. /// /// Buffer containing data frame. /// Length of data frame. /// -bool Voice::processNetwork(uint8_t usc, uint8_t option, lc::LC& netLC, uint8_t *data, uint32_t len) +bool Voice::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t *data, uint32_t len) { assert(data != NULL); @@ -624,7 +624,7 @@ bool Voice::processNetwork(uint8_t usc, uint8_t option, lc::LC& netLC, uint8_t * channel::SACCH sacch; sacch.decode(data + 2U); - if (usc == NXDN_LICH_USC_SACCH_NS) { + if (fct == NXDN_LICH_USC_SACCH_NS) { // the SACCH on a non-superblock frame is usually an idle and not interesting apart from the RAN. channel::FACCH1 facch; bool valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); @@ -636,7 +636,7 @@ bool Voice::processNetwork(uint8_t usc, uint8_t option, lc::LC& netLC, uint8_t * uint8_t buffer[10U]; facch.getData(buffer); - lc::LC lc; + lc::RTCH lc; lc.decode(buffer, NXDN_FACCH1_LENGTH_BITS); uint16_t dstId = lc.getDstId(); uint16_t srcId = lc.getSrcId(); @@ -742,7 +742,7 @@ bool Voice::processNetwork(uint8_t usc, uint8_t option, lc::LC& netLC, uint8_t * uint8_t buffer[10U]; facch.getData(buffer); - lc::LC lc; + lc::RTCH lc; lc.decode(buffer, NXDN_FACCH1_LENGTH_BITS); hasInfo = lc.getMessageType() == RTCH_MESSAGE_TYPE_VCALL; diff --git a/nxdn/packet/Voice.h b/nxdn/packet/Voice.h index ca6c9cc3..e12e3689 100644 --- a/nxdn/packet/Voice.h +++ b/nxdn/packet/Voice.h @@ -12,7 +12,7 @@ // /* * Copyright (C) 2015-2020 by Jonathan Naylor G4KLX -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL +* 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 @@ -62,9 +62,9 @@ namespace nxdn virtual void resetNet(); /// Process a data frame from the RF interface. - virtual bool process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len); + virtual bool process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len); /// Process a data frame from the network. - virtual bool processNetwork(uint8_t usc, uint8_t option, lc::LC& netLC, uint8_t* data, uint32_t len); + virtual bool processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len); protected: friend class packet::Data;