[EXPERIMENTAL] implement experimental NXDN CC transmit stream (no incoming data processing, yet)...;

pull/12/head
Bryan Biedenkapp 4 years ago
parent 99d1de340e
commit b8200f8a2b

@ -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;

@ -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;

@ -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 \

@ -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

@ -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();

@ -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<bool>(false);
bool nxdnCtrlChannel = nxdnProtocol["control"]["dedicated"].as<bool>(false);
bool nxdnCtrlBroadcast = nxdnProtocol["control"]["broadcast"].as<bool>(true);
bool nxdnDumpRcchData = nxdnProtocol["dumpRcchData"].as<bool>(false);
uint32_t callHang = nxdnProtocol["callHang"].as<uint32_t>(3U);
uint32_t queueSize = nxdnProtocol["queueSize"].as<uint32_t>(31U);
bool nxdnVerbose = nxdnProtocol["verbose"].as<bool>(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<uint32_t>(300U);
uint32_t nxdnControlBcstDuration = nxdnProtocol["control"]["duration"].as<uint32_t>(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();
}
}
@ -913,6 +1014,14 @@ int Host::run()
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;

@ -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;

@ -261,7 +261,7 @@ uint8_t* BaseNetwork::readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpe
/// <param name="lc"></param>
/// <param name="len"></param>
/// <returns></returns>
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
/// <param name="data"></param>
/// <param name="len"></param>
/// <returns></returns>
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
/// <param name="data"></param>
/// <param name="len"></param>
/// <returns></returns>
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;

@ -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
/// <summary>Reads P25 frame data from the P25 ring buffer.</summary>
virtual uint8_t* readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpeedData& lsd, uint8_t& duid, uint32_t& len);
/// <summary>Reads NXDN frame data from the NXDN ring buffer.</summary>
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);
/// <summary>Reads a channel grant request from the network.</summary>
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);
/// <summary>Writes NXDN frame data to the network.</summary>
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);
/// <summary>Writes a channel grant request to the network.</summary>
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);
/// <summary>Writes NXDN frame data to the network.</summary>
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);
/// <summary>Writes data to the network.</summary>
virtual bool write(const uint8_t* data, uint32_t length);

@ -76,15 +76,13 @@ const uint8_t SCRAMBLER[] = {
/// <param name="tidLookup">Instance of the TalkgroupIdLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
/// <param name="rssi">Instance of the RSSIInterpolator class.</param>
/// <param name="dumpPDUData"></param>
/// <param name="repeatPDU"></param>
/// <param name="dumpTSBKData">Flag indicating whether TSBK data is dumped to the log.</param>
/// <param name="dumpRCCHData">Flag indicating whether RCCH data is dumped to the log.</param>
/// <param name="debug">Flag indicating whether P25 debug is enabled.</param>
/// <param name="verbose">Flag indicating whether P25 verbose logging is enabled.</param>
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();
}
}
/// <summary>
@ -325,19 +337,50 @@ 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) {
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:
ret = m_voice->process(usc, option, data, len);
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);
}
}
/// <summary>
@ -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;
}
/// <summary>
/// Helper to write control channel frame data.
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Helper to write RF end of frame data.
/// </summary>

@ -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:
/// <summary>Initializes a new instance of the Control class.</summary>
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);
/// <summary>Finalizes a instance of the Control class.</summary>
~Control();
@ -83,6 +85,15 @@ namespace nxdn
void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector<uint32_t> voiceChNo,
uint16_t locId, uint8_t channelId, uint32_t channelNo, bool printOptions);
/// <summary>Gets a flag indicating whether the NXDN control channel is running.</summary>
bool getCCRunning() { return m_ccRunning; }
/// <summary>Sets a flag indicating whether the NXDN control channel is running.</summary>
void setCCRunning(bool ccRunning) { m_ccPrevRunning = m_ccRunning; m_ccRunning = ccRunning; }
/// <summary>Gets a flag indicating whether the NXDN control channel is running.</summary>
bool getCCHalted() { return m_ccHalted; }
/// <summary>Sets a flag indicating whether the NXDN control channel is halted.</summary>
void setCCHalted(bool ccHalted) { m_ccHalted = ccHalted; }
/// <summary>Process a data frame from the RF interface.</summary>
bool processFrame(uint8_t* data, uint32_t len);
/// <summary>Get frame data from data ring buffer.</summary>
@ -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
/// <summary>Process a data frames from the network.</summary>
void processNetwork();
/// <summary>Helper to write control channel frame data.</summary>
bool writeRF_ControlData();
/// <summary></summary>
void scrambler(uint8_t* data) const;

@ -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

@ -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
/// <param name="channelNo">Channel Number.</param>
/// <param name="serviceClass">Service class.</param>
/// <param name="requireReg"></param>
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
/// <param name="serviceClass">Service class.</param>
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:
/// <summary>NXDN location ID.</summary>
__READONLY_PROPERTY_PLAIN(uint16_t, locId, locId);
__READONLY_PROPERTY_PLAIN(uint32_t, locId, locId);
/// <summary>Channel ID.</summary>
__READONLY_PROPERTY_PLAIN(uint8_t, channelId, channelId);
/// <summary>Channel number.</summary>

@ -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;
}
// ---------------------------------------------------------------------------

@ -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;
}
// ---------------------------------------------------------------------------

@ -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;
}
// ---------------------------------------------------------------------------

@ -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 <cstdio>
#include <cassert>
#include <cstring>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a copy instance of the RCCH class.
/// </summary>
/// <param name="data"></param>
RCCH::RCCH(const RCCH& data) : RCCH(SiteData())
{
copy(data);
}
/// <summary>
/// Initializes a new instance of the RCCH class.
/// </summary>
/// <param name="siteData"></param>
/// <param name="entry"></param>
RCCH::RCCH(SiteData siteData, lookups::IdenTable entry) : RCCH(siteData)
{
m_siteIdenEntry = entry;
}
/// <summary>
/// Initializes a new instance of the RCCH class.
/// </summary>
/// <param name="siteData"></param>
/// <param name="entry"></param>
/// <param name="verbose"></param>
RCCH::RCCH(SiteData siteData, lookups::IdenTable entry, bool verbose) : RCCH(siteData)
{
m_verbose = verbose;
m_siteIdenEntry = entry;
}
/// <summary>
/// Finalizes a instance of RCCH class.
/// </summary>
RCCH::~RCCH()
{
delete[] m_data;
}
/// <summary>
/// Equals operator.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Decode call link control data.
/// </summary>
/// <param name="data"></param>
/// <returns>True, if RCCH was decoded, otherwise false.</returns>
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);
}
/// <summary>
/// Encode call link control data.
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <param name="offset"></param>
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);
}
}
/// <summary>
///
/// </summary>
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;
}
/// <summary>
/// Gets the raw layer 3 data.
/// </summary>
/// <param name="data"></param>
void RCCH::getData(uint8_t* data) const
{
::memcpy(data, m_data, NXDN_RCCH_LC_LENGTH_BYTES);
}
/// <summary>
/// Sets the raw layer 3 data.
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
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);
}
/// <summary>
/// Sets the callsign.
/// </summary>
/// <param name="callsign">Callsign.</param>
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
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the RCCH class.
/// </summary>
RCCH::RCCH() : RCCH(SiteData())
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the RCCH class.
/// </summary>
/// <param name="siteData"></param>
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());
}
/// <summary>
/// Decode link control.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Encode link control.
/// </summary>
/// <param name="rs"></param>
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);
}
}
// <summary>
/// Internal helper to copy the the class.
/// </summary>
/// <param name="data"></param>
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);
}

@ -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:
/// <summary>Initializes a copy instance of the RCCH class.</summary>
RCCH(const RCCH& data);
/// <summary>Initializes a new instance of the TSBK class.</summary>
RCCH(SiteData siteData, lookups::IdenTable entry);
/// <summary>Initializes a new instance of the TSBK class.</summary>
RCCH(SiteData siteData, lookups::IdenTable entry, bool verbose);
/// <summary>Finalizes a instance of the RCCH class.</summary>
~RCCH();
/// <summary>Equals operator.</summary>
RCCH& operator=(const RCCH& data);
/// <summary>Decode layer 3 data.</summary>
void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U);
/// <summary>Encode layer 3 data.</summary>
void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U);
/// <summary></summary>
void reset();
/// <summary>Gets the raw layer 3 data.</summary>
void getData(uint8_t* data) const;
/// <summary>Sets the raw layer 3 data.</summary>
void setData(const uint8_t* data, uint32_t length);
/// <summary>Sets the callsign.</summary>
void setCallsign(std::string callsign);
public:
/// <summary>Flag indicating verbose log output.</summary>
__PROPERTY(bool, verbose, Verbose);
/** Common Data */
/// <summary>Message Type</summary>
__PROPERTY(uint8_t, messageType, MessageType);
/// <summary>Source ID.</summary>
__PROPERTY(uint16_t, srcId, SrcId);
/// <summary>Destination ID.</summary>
__PROPERTY(uint16_t, dstId, DstId);
/// <summary>Location ID.</summary>
__PROPERTY(uint32_t, locId, LocId);
/// <summary>Registration Option.</summary>
__PROPERTY(uint8_t, regOption, RegOption);
/// <summary>Version Number.</summary>
__PROPERTY(uint8_t, version, Version);
/// <summary>Cause Response.</summary>
__PROPERTY(uint8_t, causeRsp, CauseResponse);
/** Local Site data */
/// <summary>Local Site Data.</summary>
__PROPERTY_PLAIN(SiteData, siteData, siteData);
/// <summary>Local Site Identity Entry.</summary>
__PROPERTY_PLAIN(lookups::IdenTable, siteIdenEntry, siteIdenEntry);
private:
/// <summary>Initializes a new instance of the RCCH class.</summary>
RCCH();
/// <summary>Initializes a new instance of the RCCH class.</summary>
RCCH(SiteData siteData);
uint8_t* m_data;
/** Local Site data */
uint8_t* m_siteCallsign;
/// <summary>Decode link control.</summary>
bool decodeLC(const uint8_t* data);
/// <summary>Encode link control.</summary>
void encodeLC(uint8_t* data);
/// <summary>Internal helper to copy the class.</summary>
void copy(const RCCH& data);
};
} // namespace lc
} // namespace nxdn
#endif // __NXDN_LC__RCCH_H__

@ -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;
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the LC class.
/// Initializes a new instance of the RTCH class.
/// </summary>
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);
}
/// <summary>
/// Initializes a copy instance of the LC class.
/// Initializes a copy instance of the RTCH class.
/// </summary>
/// <param name="data"></param>
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) :
}
/// <summary>
/// Finalizes a instance of LC class.
/// Finalizes a instance of RTCH class.
/// </summary>
LC::~LC()
RTCH::~RTCH()
{
delete[] m_data;
delete[] m_mi;
@ -119,10 +119,10 @@ LC::~LC()
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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.
/// </summary>
/// <param name="data"></param>
/// <returns>True, if LC was decoded, otherwise false.</returns>
void LC::decode(const uint8_t* data, uint32_t length, uint32_t offset)
/// <returns>True, if RTCH was decoded, otherwise false.</returns>
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)
/// <param name="data"></param>
/// <param name="length"></param>
/// <param name="offset"></param>
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);
}
}
/// <summary>
///
/// </summary>
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.
/// </summary>
/// <param name="data"></param>
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);
}
/// <summary>
@ -219,9 +219,9 @@ void LC::getData(uint8_t* data) const
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
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)
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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.
/// </summary>
/// <param name="rs"></param>
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.
/// </summary>
/// <param name="data"></param>
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;

@ -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:
/// <summary>Initializes a new instance of the LC class.</summary>
LC();
/// <summary>Initializes a copy instance of the LC class.</summary>
LC(const LC& data);
/// <summary>Finalizes a instance of the LC class.</summary>
~LC();
/// <summary>Initializes a new instance of the RTCH class.</summary>
RTCH();
/// <summary>Initializes a copy instance of the RTCH class.</summary>
RTCH(const RTCH& data);
/// <summary>Finalizes a instance of the RTCH class.</summary>
~RTCH();
/// <summary>Equals operator.</summary>
LC& operator=(const LC& data);
RTCH& operator=(const RTCH& data);
/// <summary>Decode layer 3 data.</summary>
void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U);
@ -134,9 +134,9 @@ namespace nxdn
void encodeLC(uint8_t* data);
/// <summary>Internal helper to copy the class.</summary>
void copy(const LC& data);
void copy(const RTCH& data);
};
} // namespace lc
} // namespace nxdn
#endif // __NXDN_LC__LC_H__
#endif // __NXDN_LC__RTCH_H__

@ -173,7 +173,7 @@ void Data::resetNet()
/// <summary>
/// Process a data frame from the RF interface.
/// </summary>
/// <param name="option"></param>
/// <param name="option">Channel options.</param>
/// <param name="data">Buffer containing data frame.</param>
/// <param name="len">Length of data frame.</param>
/// <returns></returns>
@ -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)
/// <summary>
/// Process a data frame from the RF interface.
/// </summary>
/// <param name="option"></param>
/// <param name="option">Channel options.</param>
/// <param name="netLC"></param>
/// <param name="data">Buffer containing data frame.</param>
/// <param name="len">Length of data frame.</param>
/// <returns></returns>
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();

@ -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,7 +63,7 @@ namespace nxdn
/// <summary>Process a data frame from the RF interface.</summary>
virtual bool process(uint8_t option, uint8_t* data, uint32_t len);
/// <summary>Process a data frame from the network.</summary>
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;

@ -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 <cassert>
#include <cstdio>
#include <cstring>
#include <ctime>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Resets the data states for the RF interface.
/// </summary>
void Trunk::resetRF()
{
lc::RCCH lc = lc::RCCH(m_nxdn->m_siteData, m_nxdn->m_idenEntry, m_dumpRCCH);
m_rfLC = lc;
}
/// <summary>
/// Resets the data states for the network.
/// </summary>
void Trunk::resetNet()
{
lc::RCCH lc = lc::RCCH(m_nxdn->m_siteData, m_nxdn->m_idenEntry, m_dumpRCCH);
m_netLC = lc;
}
/// <summary>
/// Process a data frame from the RF interface.
/// </summary>
/// <param name="fct">Functional channel type.</param>
/// <param name="option">Channel options.</param>
/// <param name="data">Buffer containing data frame.</param>
/// <param name="len">Length of data frame.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Process a data frame from the RF interface.
/// </summary>
/// <param name="fct">Functional channel type.</param>
/// <param name="option">Channel options.</param>
/// <param name="netLC"></param>
/// <param name="data">Buffer containing data frame.</param>
/// <param name="len">Length of data frame.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Updates the processor by the passed number of milliseconds.
/// </summary>
/// <param name="ms"></param>
void Trunk::clock(uint32_t ms)
{
return;
}
// ---------------------------------------------------------------------------
// Protected Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the Trunk class.
/// </summary>
/// <param name="nxdn">Instance of the Control class.</param>
/// <param name="network">Instance of the BaseNetwork class.</param>
/// <param name="dumpRCCHData">Flag indicating whether RCCH data is dumped to the log.</param>
/// <param name="debug">Flag indicating whether NXDN debug is enabled.</param>
/// <param name="verbose">Flag indicating whether NXDN verbose logging is enabled.</param>
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 */
}
/// <summary>
/// Finalizes a instance of the Trunk class.
/// </summary>
Trunk::~Trunk()
{
/* stub */
}
/// <summary>
/// Write data processed from RF to the network.
/// </summary>
/// <param name="data"></param>
/// <param name="len"></param>
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);
}
/// <summary>
/// Helper to write control channel packet data.
/// </summary>
/// <param name="frameCnt"></param>
/// <param name="n"></param>
/// <param name="adjSS"></param>
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);
}
/// <summary>
/// Helper to write a CC SITE_INFO broadcast packet on the RF interface.
/// </summary>
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);
}
}
/// <summary>
/// Helper to write a CC SRV_INFO broadcast packet on the RF interface.
/// </summary>
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);
}
}

@ -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 <cstdio>
#include <string>
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:
/// <summary>Resets the data states for the RF interface.</summary>
virtual void resetRF();
/// <summary>Resets the data states for the network.</summary>
virtual void resetNet();
/// <summary>Process a data frame from the RF interface.</summary>
virtual bool process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len);
/// <summary>Process a data frame from the network.</summary>
virtual bool processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len);
/// <summary>Updates the processor by the passed number of milliseconds.</summary>
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;
/// <summary>Initializes a new instance of the Trunk class.</summary>
Trunk(Control* nxdn, network::BaseNetwork* network, bool dumpRCCHData, bool debug, bool verbose);
/// <summary>Finalizes a instance of the Trunk class.</summary>
virtual ~Trunk();
/// <summary>Write data processed from RF to the network.</summary>
void writeNetwork(const uint8_t* data, uint32_t len);
/// <summary>Helper to write control channel packet data.</summary>
void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS);
/// <summary>Helper to write a CC SITE_INFO broadcast packet on the RF interface.</summary>
void writeRF_CC_Site_Info();
/// <summary>Helper to write a CC SRV_INFO broadcast packet on the RF interface.</summary>
void writeRF_CC_Service_Info();
};
} // namespace packet
} // namespace nxdn
#endif // __NXDN_PACKET_TRUNK_H__

@ -179,12 +179,12 @@ void Voice::resetNet()
/// <summary>
/// Process a data frame from the RF interface.
/// </summary>
/// <param name="usc"></param>
/// <param name="option"></param>
/// <param name="fct">Functional channel type.</param>
/// <param name="option">Channel options.</param>
/// <param name="data">Buffer containing data frame.</param>
/// <param name="len">Length of data frame.</param>
/// <returns></returns>
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)
/// <summary>
/// Process a data frame from the network.
/// </summary>
/// <param name="usc"></param>
/// <param name="option"></param>
/// <param name="fct">Functional channel type.</param>
/// <param name="option">Channel options.</param>
/// <param name="netLC"></param>
/// <param name="data">Buffer containing data frame.</param>
/// <param name="len">Length of data frame.</param>
/// <returns></returns>
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;

@ -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();
/// <summary>Process a data frame from the RF interface.</summary>
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);
/// <summary>Process a data frame from the network.</summary>
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;

Loading…
Cancel
Save

Powered by TurnKey Linux.