diff --git a/Log.h b/Log.h index 7d40b8b4..d4062ff3 100644 --- a/Log.h +++ b/Log.h @@ -45,6 +45,7 @@ #define LOG_RF "RF" #define LOG_NET "NET" #define LOG_P25 "P25" +#define LOG_NXDN "NXDN" #define LOG_DMR "DMR" #define LOG_CAL "CAL" #define LOG_SETUP "SETUP" diff --git a/Makefile b/Makefile index ebb1b91d..ecb40bdc 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,18 @@ HOST_OBJECTS = \ lookups/RadioIdLookup.o \ lookups/RSSIInterpolator.o \ lookups/TalkgroupIdLookup.o \ + nxdn/acl/AccessControl.o \ + nxdn/channel/FACCH1.o \ + nxdn/channel/LICH.o \ + nxdn/channel/SACCH.o \ + nxdn/channel/UDCH.o \ + nxdn/data/Layer3.o \ + nxdn/packet/Data.o \ + nxdn/packet/Voice.o \ + nxdn/Audio.o \ + nxdn/Control.o \ + nxdn/Convolution.o \ + nxdn/Sync.o \ p25/acl/AccessControl.o \ p25/data/DataBlock.o \ p25/data/DataHeader.o \ diff --git a/config.example.yml b/config.example.yml index 312b83a1..2d60782d 100644 --- a/config.example.yml +++ b/config.example.yml @@ -77,6 +77,13 @@ protocols: queueSize: 12 verbose: true debug: false + nxdn: + enable: false + callHang: 5 + silenceThreshold: 14 + queueSize: 12 + verbose: true + debug: false system: identity: ABCD123 timeout: 180 @@ -104,6 +111,7 @@ system: colorCode: 1 nac: 293 # txNAC: 293 + ran: 1 pSuperGroup: FFFF netId: BB800 sysId: 001 @@ -127,15 +135,21 @@ system: fdmaPreamble: 80 dmrRxDelay: 7 p25CorrCount: 8 - dmrDiscBWAdj: 0 # Valid values between -128 and 128 - dmrPostBWAdj: 0 # Valid values between -128 and 128 - p25DiscBWAdj: 0 # Valid values between -128 and 128 - p25PostBWAdj: 0 # Valid values between -128 and 128 - adfGainMode: 0 # 0 - Auto, 1 - Auto High Lin, 2 - Low, 3 - High - dmrSymLvl3Adj: 0 # Valid values between -128 and 128 - dmrSymLvl1Adj: 0 # Valid values between -128 and 128 - p25SymLvl3Adj: 0 # Valid values between -128 and 128 - p25SymLvl1Adj: 0 # Valid values between -128 and 128 + hotspot: + dmrDiscBWAdj: 0 # Valid values between -128 and 128 + dmrPostBWAdj: 0 # Valid values between -128 and 128 + p25DiscBWAdj: 0 # Valid values between -128 and 128 + p25PostBWAdj: 0 # Valid values between -128 and 128 + nxdnDiscBWAdj: 0 # Valid values between -128 and 128 + nxdnPostBWAdj: 0 # Valid values between -128 and 128 + adfGainMode: 0 # 0 - Auto, 1 - Auto High Lin, 2 - Low, 3 - High + repeater: + dmrSymLvl3Adj: 0 # Valid values between -128 and 128 + dmrSymLvl1Adj: 0 # Valid values between -128 and 128 + p25SymLvl3Adj: 0 # Valid values between -128 and 128 + p25SymLvl1Adj: 0 # Valid values between -128 and 128 + nxdnSymLvl3Adj: 0 # Valid values between -128 and 128 + nxdnSymLvl1Adj: 0 # Valid values between -128 and 128 rxDCOffset: 0 # Valid values between -128 and 128 txDCOffset: 0 # Valid values between -128 and 128 txTuning: 0 # Freq offset for the hotspot, in hz @@ -145,6 +159,7 @@ system: # cwIdTxLevel: 50 # dmrTxLevel: 50 # p25TxLevel: 50 +# nxdnTxLevel: 50 rssiMappingFile: RSSI.dat packetPlayoutTime: 10 disableOFlowReset: false diff --git a/dmr/Control.cpp b/dmr/Control.cpp index d6193a9a..027a9757 100644 --- a/dmr/Control.cpp +++ b/dmr/Control.cpp @@ -349,6 +349,16 @@ void Control::writeRF_Call_Alrt(uint32_t slotNo, uint32_t srcId, uint32_t dstId) } } +/// +/// Flag indicating whether the processor or is busy or not. +/// +/// True, if processor is busy, otherwise false. +bool Control::isBusy() const +{ + return (m_slot1->m_rfState != RS_RF_LISTENING || m_slot1->m_netState != RS_NET_IDLE) && + (m_slot2->m_rfState != RS_RF_LISTENING || m_slot2->m_netState != RS_NET_IDLE); +} + /// /// Helper to change the debug and verbose state. /// diff --git a/dmr/Control.h b/dmr/Control.h index f484d62e..e09a1ca7 100644 --- a/dmr/Control.h +++ b/dmr/Control.h @@ -94,6 +94,9 @@ namespace dmr /// Helper to write a DMR call alert packet on the RF interface. void writeRF_Call_Alrt(uint32_t slotNo, uint32_t srcId, uint32_t dstId); + /// Flag indicating whether the processor or is busy or not. + bool isBusy() const; + /// Helper to change the debug and verbose state. void setDebugVerbose(bool debug, bool verbose); diff --git a/dmr/Slot.h b/dmr/Slot.h index a35e1f55..45481cd3 100644 --- a/dmr/Slot.h +++ b/dmr/Slot.h @@ -111,6 +111,7 @@ namespace dmr static void setSiteData(uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo); private: + friend class Control; friend class packet::Voice; packet::Voice* m_voice; friend class packet::Data; diff --git a/edac/AMBEFEC.cpp b/edac/AMBEFEC.cpp index 47c0e58b..e8cb371c 100644 --- a/edac/AMBEFEC.cpp +++ b/edac/AMBEFEC.cpp @@ -487,6 +487,62 @@ uint32_t AMBEFEC::measureP25BER(const uint8_t* bytes) const return errors; } +/// +/// Regenerates the NXDN AMBE FEC for the input bytes. +/// +/// +/// Count of errors. +uint32_t AMBEFEC::regenerateNXDN(uint8_t* bytes) const +{ + assert(bytes != NULL); + + uint32_t a = 0U; + uint32_t MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++, MASK >>= 1) { + uint32_t aPos = DMR_A_TABLE[i]; + if (READ_BIT(bytes, aPos)) + a |= MASK; + } + + uint32_t b = 0U; + MASK = 0x400000U; + for (uint32_t i = 0U; i < 23U; i++, MASK >>= 1) { + uint32_t bPos = DMR_B_TABLE[i]; + if (READ_BIT(bytes, bPos)) + b |= MASK; + } + + uint32_t c = 0U; + MASK = 0x1000000U; + for (uint32_t i = 0U; i < 25U; i++, MASK >>= 1) { + uint32_t cPos = DMR_C_TABLE[i]; + if (READ_BIT(bytes, cPos)) + c |= MASK; + } + + uint32_t errors = regenerate(a, b, c, true); + + MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++, MASK >>= 1) { + uint32_t aPos = DMR_A_TABLE[i]; + WRITE_BIT(bytes, aPos, a & MASK); + } + + MASK = 0x400000U; + for (uint32_t i = 0U; i < 23U; i++, MASK >>= 1) { + uint32_t bPos = DMR_B_TABLE[i]; + WRITE_BIT(bytes, bPos, b & MASK); + } + + MASK = 0x1000000U; + for (uint32_t i = 0U; i < 25U; i++, MASK >>= 1) { + uint32_t cPos = DMR_C_TABLE[i]; + WRITE_BIT(bytes, cPos, c & MASK); + } + + return errors; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/edac/AMBEFEC.h b/edac/AMBEFEC.h index 0c602a1c..8ce82b7d 100644 --- a/edac/AMBEFEC.h +++ b/edac/AMBEFEC.h @@ -491,6 +491,9 @@ namespace edac /// Returns the number of errors on the P25 BER input bytes. uint32_t measureP25BER(const uint8_t* bytes) const; + /// Regenerates the NXDN AMBE FEC for the input bytes. + uint32_t regenerateNXDN(uint8_t* bytes) const; + private: /// uint32_t regenerate(uint32_t& a, uint32_t& b, uint32_t& c, bool b23) const; diff --git a/edac/CRC.cpp b/edac/CRC.cpp index 9f3d4bf7..15d46fda 100644 --- a/edac/CRC.cpp +++ b/edac/CRC.cpp @@ -253,7 +253,7 @@ bool CRC::checkCCITT162(const uint8_t *in, uint32_t length) #if DEBUG_CRC uint16_t inCrc = (in[length - 2U] << 8) | (in[length - 1U] << 0); - LogDebug(LOG_HOST, "CRC:checkCCITT161(), crc = $%04X, in = $%04X, len = %u", crc16, inCrc, length); + LogDebug(LOG_HOST, "CRC::checkCCITT161(), crc = $%04X, in = $%04X, len = %u", crc16, inCrc, length); #endif return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U]; @@ -283,7 +283,7 @@ void CRC::addCCITT162(uint8_t* in, uint32_t length) crc16 = ~crc16; #if DEBUG_CRC - LogDebug(LOG_HOST, "CRC:addCCITT162(), crc = $%04X, len = %u", crc16, length); + LogDebug(LOG_HOST, "CRC::addCCITT162(), crc = $%04X, len = %u", crc16, length); #endif in[length - 1U] = crc8[0U]; @@ -316,7 +316,7 @@ bool CRC::checkCCITT161(const uint8_t *in, uint32_t length) #if DEBUG_CRC uint16_t inCrc = (in[length - 2U] << 8) | (in[length - 1U] << 0); - LogDebug(LOG_HOST, "CRC:checkCCITT161(), crc = $%04X, in = $%04X, len = %u", crc16, inCrc, length); + LogDebug(LOG_HOST, "CRC::checkCCITT161(), crc = $%04X, in = $%04X, len = %u", crc16, inCrc, length); #endif return crc8[0U] == in[length - 2U] && crc8[1U] == in[length - 1U]; @@ -346,7 +346,7 @@ void CRC::addCCITT161(uint8_t* in, uint32_t length) crc16 = ~crc16; #if DEBUG_CRC - LogDebug(LOG_HOST, "CRC:addCCITT161(), crc = $%04X, len = %u", crc16, length); + LogDebug(LOG_HOST, "CRC::addCCITT161(), crc = $%04X, len = %u", crc16, length); #endif in[length - 2U] = crc8[0U]; @@ -382,7 +382,7 @@ bool CRC::checkCRC32(const uint8_t *in, uint32_t length) #if DEBUG_CRC uint32_t inCrc = (in[length - 4U] << 24) | (in[length - 3U] << 16) | (in[length - 2U] << 8) | (in[length - 1U] << 0); - LogDebug(LOG_HOST, "CRC:checkCRC32(), crc = $%08X, in = $%08X, len = %u", crc32, inCrc, length); + LogDebug(LOG_HOST, "CRC::checkCRC32(), crc = $%08X, in = $%08X, len = %u", crc32, inCrc, length); #endif return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U] && crc8[2U] == in[length - 3U] && crc8[3U] == in[length - 4U]; @@ -415,7 +415,7 @@ void CRC::addCRC32(uint8_t* in, uint32_t length) crc32 &= 0xFFFFFFFFU; #if DEBUG_CRC - LogDebug(LOG_HOST, "CRC:addCRC32(), crc = $%08X, len = %u", crc32, length); + LogDebug(LOG_HOST, "CRC::addCRC32(), crc = $%08X, len = %u", crc32, length); #endif in[length - 1U] = crc8[0U]; @@ -440,7 +440,7 @@ uint8_t CRC::crc8(const uint8_t *in, uint32_t length) crc = CRC8_TABLE[crc ^ in[i]]; #if DEBUG_CRC - LogDebug(LOG_HOST, "CRC:crc8(), crc = $%02X, len = %u", crc, length); + LogDebug(LOG_HOST, "CRC::crc8(), crc = $%02X, len = %u", crc, length); #endif return crc; @@ -474,8 +474,251 @@ uint16_t CRC::crc9(const uint8_t* in, uint32_t bitLength) crc ^= 0x1FFU; #if DEBUG_CRC - LogDebug(LOG_HOST, "CRC:crc9(), crc = $%03X, bitlen = %u", crc, bitLength); + LogDebug(LOG_HOST, "CRC::crc9(), crc = $%03X, bitlen = %u", crc, bitLength); #endif return crc; } + +/// +/// Check 6-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +bool CRC::checkCRC6(const uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint8_t crc = createCRC6(in, length); + + uint8_t temp[1U]; + temp[0U] = 0x00U; + uint32_t j = length; + for (uint32_t i = 2U; i < 8U; i++, j++) { + bool b = READ_BIT(in, j); + WRITE_BIT(temp, i, b); + } + +#if DEBUG_CRC + uint32_t inCrc = temp[0U]; + LogDebug(LOG_HOST, "CRC::checkCRC6(), crc = $%08X, in = $%08X, len = %u", crc32, inCrc, length); +#endif + + return crc == temp[0U]; +} + +/// +/// Encode 6-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +void CRC::addCRC6(uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint8_t crc[1U]; + crc[0U] = createCRC6(in, length); + + uint32_t n = length; + for (uint32_t i = 2U; i < 8U; i++, n++) { + bool b = READ_BIT(crc, i); + WRITE_BIT(in, n, b); + } + +#if DEBUG_CRC + LogDebug(LOG_HOST, "CRC::addCRC6(), crc = $%04X, len = %u", crc[0U], length); +#endif +} + +/// +/// Check 12-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +bool CRC::checkCRC12(const uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint16_t crc = createCRC12(in, length); + uint8_t temp1[2U]; + temp1[0U] = (crc >> 8) & 0xFFU; + temp1[1U] = (crc >> 0) & 0xFFU; + + uint8_t temp2[2U]; + temp2[0U] = 0x00U; + temp2[1U] = 0x00U; + uint32_t j = length; + for (uint32_t i = 4U; i < 16U; i++, j++) { + bool b = READ_BIT(in, j); + WRITE_BIT(temp2, i, b); + } + +#if DEBUG_CRC + uint16_t inCrc = (temp2[length - 2U] << 8) | (temp2[length - 1U] << 0); + LogDebug(LOG_HOST, "CRC:checkCRC12(), crc = $%04X, in = $%04X, len = %u", crc, inCrc, length); +#endif + + return temp1[0U] == temp2[0U] && temp1[1U] == temp2[1U]; +} + +/// +/// Encode 12-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +void CRC::addCRC12(uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint16_t crc = createCRC12(in, length); + + uint8_t temp[2U]; + temp[0U] = (crc >> 8) & 0xFFU; + temp[1U] = (crc >> 0) & 0xFFU; + + uint32_t n = length; + for (uint32_t i = 4U; i < 16U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(in, n, b); + } + +#if DEBUG_CRC + LogDebug(LOG_HOST, "CRC::addCRC12(), crc = $%04X, len = %u", crc, length); +#endif +} + +/// +/// Check 15-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +bool CRC::checkCRC15(const uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint16_t crc = createCRC15(in, length); + uint8_t temp1[2U]; + temp1[0U] = (crc >> 8) & 0xFFU; + temp1[1U] = (crc >> 0) & 0xFFU; + + uint8_t temp2[2U]; + temp2[0U] = 0x00U; + temp2[1U] = 0x00U; + uint32_t j = length; + for (uint32_t i = 1U; i < 16U; i++, j++) { + bool b = READ_BIT(in, j); + WRITE_BIT(temp2, i, b); + } + +#if DEBUG_CRC + uint16_t inCrc = (temp2[length - 2U] << 8) | (temp2[length - 1U] << 0); + LogDebug(LOG_HOST, "CRC:checkCRC15(), crc = $%04X, in = $%04X, len = %u", crc, inCrc, length); +#endif + + return temp1[0U] == temp2[0U] && temp1[1U] == temp2[1U]; +} + +/// +/// Encode 15-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +void CRC::addCRC15(uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint16_t crc = createCRC15(in, length); + + uint8_t temp[2U]; + temp[0U] = (crc >> 8) & 0xFFU; + temp[1U] = (crc >> 0) & 0xFFU; + + uint32_t n = length; + for (uint32_t i = 1U; i < 16U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(in, n, b); + } + +#if DEBUG_CRC + LogDebug(LOG_HOST, "CRC::addCRC15(), crc = $%04X, len = %u", crc, length); +#endif +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- + +/// +/// +/// +/// Input byte array. +/// Length of byte array. +/// +uint8_t CRC::createCRC6(const uint8_t* in, uint32_t length) +{ + uint8_t crc = 0x3FU; + + for (uint32_t i = 0U; i < length; i++) { + bool bit1 = READ_BIT(in, i) != 0x00U; + bool bit2 = (crc & 0x20U) == 0x20U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x27U; + } + + return crc & 0x3FU; +} + +/// +/// +/// +/// Input byte array. +/// Length of byte array. +/// +uint16_t CRC::createCRC12(const uint8_t* in, uint32_t length) +{ + uint16_t crc = 0x0FFFU; + + for (uint32_t i = 0U; i < length; i++) { + bool bit1 = READ_BIT(in, i) != 0x00U; + bool bit2 = (crc & 0x0800U) == 0x0800U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x080FU; + } + + return crc & 0x0FFFU; +} + +/// +/// +/// +/// Input byte array. +/// Length of byte array. +/// +uint16_t CRC::createCRC15(const uint8_t* in, uint32_t length) +{ + uint16_t crc = 0x7FFFU; + + for (uint32_t i = 0U; i < length; i++) { + bool bit1 = READ_BIT(in, i) != 0x00U; + bool bit2 = (crc & 0x4000U) == 0x4000U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x4CC5U; + } + + return crc & 0x7FFFU; +} diff --git a/edac/CRC.h b/edac/CRC.h index 660f0c5a..ee88b5ee 100644 --- a/edac/CRC.h +++ b/edac/CRC.h @@ -67,6 +67,29 @@ namespace edac /// Generate 9-bit CRC. static uint16_t crc9(const uint8_t* in, uint32_t bitLength); + + /// Check 6-bit CRC. + static bool checkCRC6(const uint8_t* in, uint32_t length); + /// Encode 6-bit CRC. + static void addCRC6(uint8_t* in, uint32_t length); + + /// Check 12-bit CRC. + static bool checkCRC12(const uint8_t* in, uint32_t length); + /// Encode 12-bit CRC. + static void addCRC12(uint8_t* in, uint32_t length); + + /// Check 15-bit CRC. + static bool checkCRC15(const uint8_t* in, uint32_t length); + /// Encode 15-bit CRC. + static void addCRC15(uint8_t* in, uint32_t length); + + private: + /// + static uint8_t createCRC6(const uint8_t* in, uint32_t length); + /// + static uint16_t createCRC12(const uint8_t* in, uint32_t length); + /// + static uint16_t createCRC15(const uint8_t* in, uint32_t length); }; } // namespace edac diff --git a/host/Host.cpp b/host/Host.cpp index 9379a293..70b28b73 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -34,6 +34,7 @@ #include "dmr/DMRUtils.h" #include "p25/Control.h" #include "p25/P25Utils.h" +#include "nxdn/Control.h" #include "modem/port/ModemNullPort.h" #include "modem/port/UARTPort.h" #include "modem/port/PseudoPTYPort.h" @@ -93,6 +94,7 @@ Host::Host(const std::string& confFile) : m_cwIdTimer(1000U), m_dmrEnabled(false), m_p25Enabled(false), + m_nxdnEnabled(false), m_duplex(false), m_fixedMode(false), m_timeout(180U), @@ -529,13 +531,57 @@ int Host::run() } } - if (!m_dmrEnabled && !m_p25Enabled) { - ::LogError(LOG_HOST, "No modes enabled? DMR and/or P25 must be enabled!"); + // initialize NXDN + nxdn::Control* nxdn = NULL; +#if ENABLE_NXDN_SUPPORT + LogInfo("NXDN Parameters"); + LogInfo(" Enabled: %s", m_nxdnEnabled ? "yes" : "no"); + if (m_nxdnEnabled) { + yaml::Node nxdnProtocol = protocolConf["nxdn"]; + uint32_t callHang = nxdnProtocol["callHang"].as(3U); + uint32_t queueSize = nxdnProtocol["queueSize"].as(31U); + bool nxdnVerbose = nxdnProtocol["verbose"].as(true); + bool nxdnDebug = nxdnProtocol["debug"].as(false); + + // clamp queue size to no less then 24 and no greater the 100 + if (queueSize <= 24U) { + LogWarning(LOG_HOST, "NXDN queue size must be greater then 24 frames, defaulting to 24 frames!"); + queueSize = 24U; + } + if (queueSize > 100U) { + LogWarning(LOG_HOST, "NXDN queue size must be less then 100 frames, defaulting to 100 frames!"); + queueSize = 100U; + } + if (queueSize > 60U) { + LogWarning(LOG_HOST, "NXDN queue size is excessive, >60 frames!"); + } + + uint32_t queueSizeBytes = queueSize * (nxdn::NXDN_FRAME_LENGTH_BYTES * 5U); + + LogInfo(" Call Hang: %us", callHang); + LogInfo(" Queue Size: %u (%u bytes)", queueSize, queueSizeBytes); + + 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); + nxdn->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_channelId, m_channelNo, true); + + if (nxdnVerbose) { + LogInfo(" Verbose: yes"); + } + if (nxdnDebug) { + LogInfo(" Debug: yes"); + } + } +#endif + + if (!m_dmrEnabled && !m_p25Enabled && !m_nxdnEnabled) { + ::LogError(LOG_HOST, "No modes enabled? DMR, P25 and/or NXDN must be enabled!"); g_killed = true; } - if (m_fixedMode && m_dmrEnabled && m_p25Enabled) { - ::LogError(LOG_HOST, "Cannot have DMR enabled and P25 enabled when using fixed state! Choose one protocol for fixed state operation."); + if (m_fixedMode && ((m_dmrEnabled && m_p25Enabled) || (m_dmrEnabled && m_nxdnEnabled) || (m_p25Enabled && m_nxdnEnabled))) { + ::LogError(LOG_HOST, "Cannot have DMR, P25 and NXDN when using fixed state! Choose one protocol for fixed state operation."); g_killed = true; } @@ -545,6 +591,11 @@ int Host::run() ::LogError(LOG_HOST, "Cannot have DMR enabled when using DFSI!"); g_killed = true; } + + if (m_useDFSI && m_nxdnEnabled) { + ::LogError(LOG_HOST, "Cannot have NXDN enabled when using DFSI!"); + g_killed = true; + } #endif // P25 CC checks @@ -553,6 +604,11 @@ int Host::run() g_killed = true; } + if (m_nxdnEnabled && m_p25CtrlChannel) { + ::LogError(LOG_HOST, "Cannot have NXDN enabled when using dedicated P25 control!"); + g_killed = true; + } + if (!m_fixedMode && m_p25CtrlChannel) { ::LogWarning(LOG_HOST, "Fixed mode should be enabled when using dedicated P25 control!"); } @@ -568,6 +624,11 @@ int Host::run() g_killed = true; } + if (m_nxdnEnabled && m_dmrCtrlChannel) { + ::LogError(LOG_HOST, "Cannot have NXDN enabled when using dedicated DMR TSCC control!"); + g_killed = true; + } + if (!m_fixedMode && m_dmrCtrlChannel) { ::LogWarning(LOG_HOST, "Fixed mode should be enabled when using dedicated DMR TSCC control!"); } @@ -601,6 +662,8 @@ int Host::run() setState(STATE_DMR); if (p25 != NULL) setState(STATE_P25); + if (nxdn != NULL) + setState(STATE_NXDN); } else { setState(STATE_IDLE); @@ -630,8 +693,10 @@ int Host::run() // check if the modem is a hotspot (this check must always be done after late init) if (m_modem->isHotspot()) { - if (m_dmrEnabled && m_p25Enabled) { - ::LogError(LOG_HOST, "Dual-mode (DMR and P25) is not supported for hotspots!"); + if ((m_dmrEnabled && m_p25Enabled) || + (m_nxdnEnabled && m_dmrEnabled) || + (m_nxdnEnabled && m_p25Enabled)) { + ::LogError(LOG_HOST, "Multi-mode (DMR, P25 and NXDN) is not supported for hotspots!"); g_killed = true; killed = true; } @@ -714,6 +779,10 @@ int Host::run() LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_P25); setState(STATE_P25); } + if (nxdn != NULL && m_state != STATE_NXDN && !m_modem->hasTX()) { + LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_NXDN); + setState(STATE_NXDN); + } } // ------------------------------------------------------ @@ -729,8 +798,6 @@ int Host::run() if (ret) { len = dmr->getFrame(1U, data); if (len > 0U) { - LogDebug(LOG_HOST, "found DMR frame, len = %u", len); - // if the state is idle; set to DMR, start mode timer and start DMR idle frames if (m_state == STATE_IDLE) { m_modeTimer.setTimeout(m_netModeHang); @@ -865,6 +932,42 @@ int Host::run() } } + /** Next Generation Digital Narrowband */ + // check if there is space on the modem for NXDN frames, + // if there is read frames from the NXDN controller and write it + // to the modem +#if ENABLE_NXDN_SUPPORT + if (nxdn != NULL) { + ret = m_modem->hasNXDNSpace(); + if (ret) { + len = nxdn->getFrame(data); + if (len > 0U) { + // if the state is idle; set to NXDN and start mode timer + if (m_state == STATE_IDLE) { + m_modeTimer.setTimeout(m_netModeHang); + setState(STATE_NXDN); + } + + // if the state is NXDN; write NXDN data + if (m_state == STATE_NXDN) { + m_modem->writeNXDNData(data, len); + + INTERRUPT_DMR_BEACON; + + // if there is a P25 CC running; halt the CC + if (p25 != NULL) { + if (p25->getCCRunning() && !p25->getCCHalted()) { + p25->setCCHalted(true); + INTERRUPT_P25_CONTROL; + } + } + + m_modeTimer.start(); + } + } + } + } +#endif // ------------------------------------------------------ // -- Modem Clocking -- // ------------------------------------------------------ @@ -1066,6 +1169,38 @@ int Host::run() } } + /** NXDN */ + // read NXDN frames from modem, and if there are frames + // write those frames to the NXDN controller +#if ENABLE_NXDN_SUPPORT + if (nxdn != NULL) { + len = m_modem->readNXDNData(data); + if (len > 0U) { + if (m_state == STATE_IDLE) { + // process NXDN frames + bool ret = nxdn->processFrame(data, len); + if (ret) { + m_modeTimer.setTimeout(m_rfModeHang); + setState(STATE_NXDN); + + INTERRUPT_DMR_BEACON; + INTERRUPT_P25_CONTROL; + } + } + else if (m_state == STATE_NXDN) { + // process P25 frames + bool ret = nxdn->processFrame(data, len); + if (ret) { + m_modeTimer.start(); + INTERRUPT_P25_CONTROL; + } + } + else if (m_state != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "NXDN modem data received, state = %u", m_state); + } + } + } +#endif // ------------------------------------------------------ // -- Network, DMR, and P25 Clocking -- // ------------------------------------------------------ @@ -1077,7 +1212,10 @@ int Host::run() dmr->clock(); if (p25 != NULL) p25->clock(ms); - +#if ENABLE_NXDN_SUPPORT + if (nxdn != NULL) + nxdn->clock(ms); +#endif // ------------------------------------------------------ // -- Remote Control Processing -- // ------------------------------------------------------ @@ -1322,7 +1460,11 @@ int Host::run() if (p25 != NULL) { delete p25; } - +#if ENABLE_NXDN_SUPPORT + if (nxdn != NULL) { + delete nxdn; + } +#endif return EXIT_SUCCESS; } @@ -1331,7 +1473,7 @@ int Host::run() // --------------------------------------------------------------------------- /// -/// Reads basic configuration parameters from the INI. +/// Reads basic configuration parameters from the YAML configuration file. /// bool Host::readParams() { @@ -1352,6 +1494,10 @@ bool Host::readParams() yaml::Node protocolConf = m_conf["protocols"]; m_dmrEnabled = protocolConf["dmr"]["enable"].as(false); m_p25Enabled = protocolConf["p25"]["enable"].as(false); + m_nxdnEnabled = protocolConf["nxdn"]["enable"].as(false); +#if !ENABLE_NXDN_SUPPORT + m_nxdnEnabled = false; // hardcode to false when no NXDN support is compiled in +#endif yaml::Node systemConf = m_conf["system"]; m_duplex = systemConf["duplex"].as(true); @@ -1387,6 +1533,7 @@ bool Host::readParams() LogInfo("General Parameters"); LogInfo(" DMR: %s", m_dmrEnabled ? "enabled" : "disabled"); LogInfo(" P25: %s", m_p25Enabled ? "enabled" : "disabled"); + LogInfo(" NXDN: %s", m_nxdnEnabled ? "enabled" : "disabled"); LogInfo(" Duplex: %s", m_duplex ? "yes" : "no"); if (!udpMasterMode) { if (!m_duplex) { @@ -1526,6 +1673,8 @@ bool Host::readParams() m_p25RfssId = (uint8_t)::strtoul(rfssConfig["rfssId"].as("1").c_str(), NULL, 16); m_p25RfssId = p25::P25Utils::rfssId(m_p25RfssId); + m_nxdnRAN = rfssConfig["ran"].as(1U); + LogInfo("System Config Parameters"); LogInfo(" RX Frequency: %uHz", m_rxFrequency); LogInfo(" TX Frequency: %uHz", m_txFrequency); @@ -1545,6 +1694,8 @@ bool Host::readParams() LogInfo(" P25 Tx NAC: $%03X", p25TxNAC); } + LogInfo(" NXDN RAN: %u", m_nxdnRAN); + LogInfo(" P25 Patch Super Group: $%04X", m_p25PatchSuperGroup); LogInfo(" P25 Network Id: $%05X", m_p25NetId); LogInfo(" P25 System Id: $%03X", m_p25SysId); @@ -1566,7 +1717,7 @@ bool Host::createModem() yaml::Node modemProtocol = modemConf["protocol"]; std::string portType = modemProtocol["type"].as("null"); -#if NO_NO_FEATURE +#if ENABLE_DFSI_SUPPORT m_useDFSI = modemProtocol["dfsi"].as(false); #else m_useDFSI = false; @@ -1594,21 +1745,33 @@ bool Host::createModem() int rxTuning = modemConf["rxTuning"].as(0); int txTuning = modemConf["txTuning"].as(0); uint8_t rfPower = (uint8_t)modemConf["rfPower"].as(100U); - int dmrDiscBWAdj = modemConf["dmrDiscBWAdj"].as(0); - int p25DiscBWAdj = modemConf["p25DiscBWAdj"].as(0); - int dmrPostBWAdj = modemConf["dmrPostBWAdj"].as(0); - int p25PostBWAdj = modemConf["p25PostBWAdj"].as(0); - ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)modemConf["adfGainMode"].as(0U); - int dmrSymLevel3Adj = modemConf["dmrSymLvl3Adj"].as(0); - int dmrSymLevel1Adj = modemConf["dmrSymLvl1Adj"].as(0); - int p25SymLevel3Adj = modemConf["p25SymLvl3Adj"].as(0); - int p25SymLevel1Adj = modemConf["p25SymLvl1Adj"].as(0); + + yaml::Node hotspotParams = modemConf["hotspot"]; + + int dmrDiscBWAdj = hotspotParams["dmrDiscBWAdj"].as(0); + int p25DiscBWAdj = hotspotParams["p25DiscBWAdj"].as(0); + int nxdnDiscBWAdj = hotspotParams["nxdnDiscBWAdj"].as(0); + int dmrPostBWAdj = hotspotParams["dmrPostBWAdj"].as(0); + int p25PostBWAdj = hotspotParams["p25PostBWAdj"].as(0); + int nxdnPostBWAdj = hotspotParams["nxdnPostBWAdj"].as(0); + ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)hotspotParams["adfGainMode"].as(0U); + + yaml::Node repeaterParams = modemConf["repeater"]; + + int dmrSymLevel3Adj = repeaterParams["dmrSymLvl3Adj"].as(0); + int dmrSymLevel1Adj = repeaterParams["dmrSymLvl1Adj"].as(0); + int p25SymLevel3Adj = repeaterParams["p25SymLvl3Adj"].as(0); + int p25SymLevel1Adj = repeaterParams["p25SymLvl1Adj"].as(0); + int nxdnSymLevel3Adj = repeaterParams["nxdnSymLvl3Adj"].as(0); + int nxdnSymLevel1Adj = repeaterParams["nxdnSymLvl1Adj"].as(0); + float rxLevel = modemConf["rxLevel"].as(50.0F); float cwIdTXLevel = modemConf["cwIdTxLevel"].as(50.0F); float dmrTXLevel = modemConf["dmrTxLevel"].as(50.0F); float p25TXLevel = modemConf["p25TxLevel"].as(50.0F); + float nxdnTXLevel = modemConf["nxdnTxLevel"].as(50.0F); if (!modemConf["txLevel"].isNone()) { - cwIdTXLevel = dmrTXLevel = p25TXLevel = modemConf["txLevel"].as(50.0F); + cwIdTXLevel = dmrTXLevel = p25TXLevel = nxdnTXLevel = modemConf["txLevel"].as(50.0F); } uint8_t packetPlayoutTime = (uint8_t)modemConf["packetPlayoutTime"].as(10U); bool disableOFlowReset = modemConf["disableOFlowReset"].as(false); @@ -1734,6 +1897,7 @@ bool Host::createModem() LogInfo(" CW Id TX Level: %.1f%%", cwIdTXLevel); LogInfo(" DMR TX Level: %.1f%%", dmrTXLevel); LogInfo(" P25 TX Level: %.1f%%", p25TXLevel); + LogInfo(" NXDN TX Level: %.1f%%", nxdnTXLevel); LogInfo(" Packet Playout Time: %u ms", packetPlayoutTime); LogInfo(" Disable Overflow Reset: %s", disableOFlowReset ? "yes" : "no"); @@ -1755,11 +1919,12 @@ bool Host::createModem() m_modem = new Modem(modemPort, m_duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, fdmaPreamble, dmrRxDelay, p25CorrCount, packetPlayoutTime, disableOFlowReset, ignoreModemConfigArea, dumpModemStatus, trace, debug); - m_modem->setModeParams(m_dmrEnabled, m_p25Enabled); - m_modem->setLevels(rxLevel, cwIdTXLevel, dmrTXLevel, p25TXLevel); - m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj); + m_modem->setModeParams(m_dmrEnabled, m_p25Enabled, m_nxdnEnabled); + m_modem->setLevels(rxLevel, cwIdTXLevel, dmrTXLevel, p25TXLevel, nxdnTXLevel); + m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj); m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); - m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, rfPower, dmrDiscBWAdj, p25DiscBWAdj, dmrPostBWAdj, p25PostBWAdj, adfGainMode); + m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, rfPower, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, + p25PostBWAdj, nxdnPostBWAdj, adfGainMode); m_modem->setDMRColorCode(m_dmrColorCode); m_modem->setP25NAC(m_p25NAC); #if ENABLE_DFSI_SUPPORT @@ -1779,6 +1944,14 @@ bool Host::createModem() return false; } + // are we on a protocol version older then 3? + if (m_modem->getVersion() < 3U) { + if (m_nxdnEnabled) { + ::LogError(LOG_HOST, "NXDN is not supported on legacy firmware."); + return false; + } + } + return true; } @@ -1845,7 +2018,7 @@ bool Host::createNetwork() LogInfo(" Debug: yes"); } - m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, handleChGrants); + m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, handleChGrants); m_network->setLookups(m_ridLookup, m_tidLookup); m_network->setMetadata(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_channelId, m_channelNo, @@ -2003,6 +2176,14 @@ void Host::setState(uint8_t state) createLockFile("P25"); break; + case STATE_NXDN: + m_modem->setState(STATE_NXDN); + m_state = STATE_NXDN; + m_modeTimer.start(); + //m_cwIdTimer.stop(); + createLockFile("NXDN"); + break; + case HOST_STATE_LOCKOUT: LogWarning(LOG_HOST, "Mode change, HOST_STATE_LOCKOUT"); if (m_network != NULL) diff --git a/host/Host.h b/host/Host.h index 456442e8..b43026d8 100644 --- a/host/Host.h +++ b/host/Host.h @@ -82,6 +82,7 @@ private: bool m_dmrEnabled; bool m_p25Enabled; + bool m_nxdnEnabled; bool m_duplex; bool m_fixedMode; @@ -127,6 +128,7 @@ private: uint32_t m_p25NetId; uint32_t m_p25SysId; uint8_t m_p25RfssId; + uint32_t m_nxdnRAN; uint8_t m_activeTickDelay; uint8_t m_idleTickDelay; diff --git a/host/calibrate/HostCal.cpp b/host/calibrate/HostCal.cpp index 52883eed..b7058562 100644 --- a/host/calibrate/HostCal.cpp +++ b/host/calibrate/HostCal.cpp @@ -36,6 +36,8 @@ #include "p25/data/DataHeader.h" #include "p25/lc/LC.h" #include "p25/P25Utils.h" +#include "nxdn/NXDNDefines.h" +#include "nxdn/channel/LICH.h" #include "edac/CRC.h" #include "HostMain.h" #include "Log.h" @@ -62,10 +64,12 @@ using namespace lookups; #define DMR_CAL_1K_STR "[Tx] DMR BS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" #define DMR_DMO_CAL_1K_STR "[Tx] DMR MS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" #define P25_CAL_1K_STR "[Tx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" +#define NXDN_CAL_1K_STR "[Tx] NXDN 1031 Hz Test Pattern (RAN1 ID1 TG1)" #define DMR_FEC_STR "[Rx] DMR MS FEC BER Test Mode" #define DMR_FEC_1K_STR "[Rx] DMR MS 1031 Hz Test Pattern (CC1 ID1 TG9)" #define P25_FEC_STR "[Rx] P25 FEC BER Test Mode" #define P25_FEC_1K_STR "[Rx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" +#define NXDN_FEC_STR "[Rx] NXDN FEC BER Test Mode" #define RSSI_CAL_STR "RSSI Calibration Mode" // Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 @@ -88,7 +92,7 @@ const uint8_t VOICE_1K[] = { // Recommended 1011 Hz test pattern for P25 Phase 1 (ANSI/TIA-102.CAAA) // NAC: 0x293, srcID: 1, dstID: TG1 -unsigned char LDU1_1K[] = { +const uint8_t LDU1_1K[] = { 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x35, 0x54, 0x7B, 0xCB, 0x19, 0x4D, 0x0D, 0xCE, 0x24, 0xA1, 0x24, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4, 0xE2, 0x4A, 0x10, 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, @@ -102,7 +106,7 @@ unsigned char LDU1_1K[] = { 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x0C, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4 }; -unsigned char LDU2_1K[] = { +const uint8_t LDU2_1K[] = { 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x3A, 0xB8, 0xA4, 0xEF, 0xB0, 0x9A, 0x8A, 0xCE, 0x24, 0xA1, 0x24, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC, 0xE2, 0x4A, 0x10, 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, @@ -116,6 +120,11 @@ unsigned char LDU2_1K[] = { 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x0C, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC }; +const uint8_t NXDN_SCRAMBLER[] = { + 0x00U, 0x00U, 0x00U, 0x82U, 0xA0U, 0x88U, 0x8AU, 0x00U, 0xA2U, 0xA8U, 0x82U, 0x8AU, 0x82U, 0x02U, + 0x20U, 0x08U, 0x8AU, 0x20U, 0xAAU, 0xA2U, 0x82U, 0x08U, 0x22U, 0x8AU, 0xAAU, 0x08U, 0x28U, 0x88U, + 0x28U, 0x28U, 0x00U, 0x0AU, 0x02U, 0x82U, 0x20U, 0x28U, 0x82U, 0x2AU, 0xAAU, 0x20U, 0x22U, 0x80U, + 0xA8U, 0x8AU, 0x08U, 0xA0U, 0xAAU, 0x02U }; // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -142,6 +151,7 @@ HostCal::HostCal(const std::string& confFile) : m_dmrRx1K(false), m_p25Enabled(false), m_p25Rx1K(false), + m_nxdnEnabled(false), m_rxDCOffset(0), m_txDCOffset(0), m_isHotspot(false), @@ -302,17 +312,25 @@ int HostCal::run() m_rxLevel = modemConf["rxLevel"].as(50.0F); m_txLevel = modemConf["txLevel"].as(50.0F); - m_dmrDiscBWAdj = modemConf["dmrDiscBWAdj"].as(0); - m_p25DiscBWAdj = modemConf["p25DiscBWAdj"].as(0); - m_dmrPostBWAdj = modemConf["dmrPostBWAdj"].as(0); - m_p25PostBWAdj = modemConf["p25PostBWAdj"].as(0); + yaml::Node hotspotParams = modemConf["hotspot"]; - m_adfGainMode = (ADF_GAIN_MODE)modemConf["adfGainMode"].as(0U); + m_dmrDiscBWAdj = hotspotParams["dmrDiscBWAdj"].as(0); + m_p25DiscBWAdj = hotspotParams["p25DiscBWAdj"].as(0); + m_nxdnDiscBWAdj = hotspotParams["nxdnDiscBWAdj"].as(0); + m_dmrPostBWAdj = hotspotParams["dmrPostBWAdj"].as(0); + m_p25PostBWAdj = hotspotParams["p25PostBWAdj"].as(0); + m_nxdnPostBWAdj = hotspotParams["nxdnPostBWAdj"].as(0); - m_dmrSymLevel3Adj = modemConf["dmrSymLvl3Adj"].as(0); - m_dmrSymLevel1Adj = modemConf["dmrSymLvl1Adj"].as(0); - m_p25SymLevel3Adj = modemConf["p25SymLvl3Adj"].as(0); - m_p25SymLevel1Adj = modemConf["p25SymLvl1Adj"].as(0); + m_adfGainMode = (ADF_GAIN_MODE)hotspotParams["adfGainMode"].as(0U); + + yaml::Node repeaterParams = modemConf["repeater"]; + + m_dmrSymLevel3Adj = repeaterParams["dmrSymLvl3Adj"].as(0); + m_dmrSymLevel1Adj = repeaterParams["dmrSymLvl1Adj"].as(0); + m_p25SymLevel3Adj = repeaterParams["p25SymLvl3Adj"].as(0); + m_p25SymLevel1Adj = repeaterParams["p25SymLvl1Adj"].as(0); + m_nxdnSymLevel3Adj = repeaterParams["nxdnSymLvl3Adj"].as(0); + m_nxdnSymLevel1Adj = repeaterParams["nxdnSymLvl1Adj"].as(0); m_fdmaPreamble = (uint8_t)modemConf["fdmaPreamble"].as(80U); m_dmrRxDelay = (uint8_t)modemConf["dmrRxDelay"].as(7U); @@ -405,10 +423,10 @@ int HostCal::run() } m_modem = new Modem(modemPort, false, m_rxInvert, m_txInvert, m_pttInvert, m_dcBlocker, false, m_fdmaPreamble, m_dmrRxDelay, m_p25CorrCount, 10U, false, ignoreModemConfigArea, false, false, false); - m_modem->setLevels(m_rxLevel, m_txLevel, m_txLevel, m_txLevel); - m_modem->setSymbolAdjust(m_dmrSymLevel3Adj, m_dmrSymLevel1Adj, m_p25SymLevel3Adj, m_p25SymLevel1Adj); + m_modem->setLevels(m_rxLevel, m_txLevel, m_txLevel, m_txLevel, m_txLevel); + m_modem->setSymbolAdjust(m_dmrSymLevel3Adj, m_dmrSymLevel1Adj, m_p25SymLevel3Adj, m_p25SymLevel1Adj, m_nxdnSymLevel3Adj, m_nxdnSymLevel1Adj); m_modem->setDCOffsetParams(m_txDCOffset, m_rxDCOffset); - m_modem->setRFParams(m_rxFrequency, m_txFrequency, m_rxTuning, m_txTuning, 100U, m_dmrDiscBWAdj, m_p25DiscBWAdj, m_dmrPostBWAdj, m_p25PostBWAdj, m_adfGainMode); + m_modem->setRFParams(m_rxFrequency, m_txFrequency, m_rxTuning, m_txTuning, 100U, m_dmrDiscBWAdj, m_p25DiscBWAdj, m_nxdnDiscBWAdj, m_dmrPostBWAdj, m_p25PostBWAdj, m_nxdnPostBWAdj, m_adfGainMode); m_modem->setOpenHandler(MODEM_OC_PORT_HANDLER_BIND(HostCal::portModemOpen, this)); m_modem->setCloseHandler(MODEM_OC_PORT_HANDLER_BIND(HostCal::portModemClose, this)); @@ -529,7 +547,7 @@ int HostCal::run() setTXDCOffset(1); break; - case 'N': + case 'X': { char value[5] = { '\0' }; ::fprintf(stdout, "> FDMA Preambles [%u] ? ", m_fdmaPreamble); @@ -669,6 +687,35 @@ int HostCal::run() setP25SymLevel1Adj(1); break; + case ';': + if (!m_isHotspot && m_modem->getVersion() >= 3U) + setNXDNSymLevel3Adj(-1); + else { + LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); + } + break; + case '\'': + if (!m_isHotspot && m_modem->getVersion() >= 3U) + setNXDNSymLevel3Adj(1); + else { + LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); + } + break; + case ':': + if (!m_isHotspot && m_modem->getVersion() >= 3U) + setNXDNSymLevel1Adj(-1); + else { + LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); + } + break; + case '"': + if (!m_isHotspot && m_modem->getVersion() >= 3U) + setNXDNSymLevel1Adj(1); + else { + LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); + } + break; + case '1': { if (m_isHotspot) { @@ -710,6 +757,29 @@ int HostCal::run() break; case '3': + { + if (m_isHotspot && m_modem->getVersion() >= 3U) { + char value[5] = { '\0' }; + ::fprintf(stdout, "> NXDN Discriminator BW Offset [%d] ? ", m_nxdnDiscBWAdj); + ::fflush(stdout); + + m_console.getLine(value, 5, 0); + if (value[0] != '\0') { + int bwAdj = m_nxdnDiscBWAdj; + sscanf(value, "%d", &bwAdj); + + m_nxdnDiscBWAdj = bwAdj; + + writeRFParams(); + } + } + else { + LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); + } + } + break; + + case '4': { if (m_isHotspot) { char value[5] = { '\0' }; @@ -729,7 +799,7 @@ int HostCal::run() } break; - case '4': + case '5': { if (m_isHotspot) { char value[5] = { '\0' }; @@ -749,7 +819,30 @@ int HostCal::run() } break; - case '5': + case '6': + { + if (m_isHotspot && m_modem->getVersion() >= 3U) { + char value[5] = { '\0' }; + ::fprintf(stdout, "> NXDN Post Demodulation BW Offset [%d] ? ", m_nxdnPostBWAdj); + ::fflush(stdout); + + m_console.getLine(value, 5, 0); + if (value[0] != '\0') { + int bwAdj = m_nxdnPostBWAdj; + sscanf(value, "%d", &bwAdj); + + m_nxdnPostBWAdj = bwAdj; + + writeRFParams(); + } + } + else { + LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); + } + } + break; + + case '7': { if (m_isHotspot) { char value[2] = { '\0' }; @@ -786,6 +879,7 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); @@ -800,6 +894,7 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); @@ -814,6 +909,7 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); @@ -828,6 +924,7 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); @@ -842,6 +939,7 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); @@ -856,6 +954,7 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); @@ -870,11 +969,33 @@ int HostCal::run() m_dmrRx1K = false; m_p25Enabled = false; m_p25Rx1K = false; + m_nxdnEnabled = false; LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; + case 'N': + { + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_mode = STATE_NXDN_CAL; + m_modeStr = NXDN_CAL_1K_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_nxdnEnabled = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + else { + LogWarning(LOG_CAL, "%s test mode is not supported on your firmware!", NXDN_CAL_1K_STR); + } + } + break; case 'B': case 'J': case '0': @@ -920,6 +1041,26 @@ int HostCal::run() writeConfig(); } break; + case 'n': + { + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_mode = STATE_NXDN; + m_modeStr = NXDN_FEC_STR; + m_duplex = false; + m_dmrEnabled = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_nxdnEnabled = true; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + else { + LogWarning(LOG_CAL, "%s test mode is not supported on your firmware!", NXDN_FEC_STR); + } + } + break; case 'x': { m_mode = STATE_RSSI_CAL; @@ -946,6 +1087,8 @@ int HostCal::run() getHostVersion(); break; case 'v': + // bryanb: this actually polls the firmware -- should we do this or just + // call m_modem->getVersion()? m_modem->getFirmwareVersion(); break; case 'H': @@ -1115,6 +1258,24 @@ bool HostCal::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspType, } break; + case CMD_NXDN_DATA: + processNXDNBER(buffer + 3U); + break; + + case CMD_NXDN_LOST: + { + LogMessage(LOG_CAL, "NXDN Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + + if (m_nxdnEnabled) { + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + } + break; + case CMD_GET_STATUS: { m_isHotspot = (buffer[3U] & 0x01U) == 0x01U; @@ -1234,7 +1395,7 @@ void HostCal::displayHelp() LogMessage(LOG_CAL, " C/c Increase/Decrease RX DC offset level"); LogMessage(LOG_CAL, " O/o Increase/Decrease TX DC offset level"); } - LogMessage(LOG_CAL, " N Set FDMA Preambles"); + LogMessage(LOG_CAL, " X Set FDMA Preambles"); LogMessage(LOG_CAL, " W Set DMR Rx Delay"); if (!m_isHotspot) { LogMessage(LOG_CAL, " w Set P25 Correlation Count"); @@ -1251,10 +1412,12 @@ void HostCal::displayHelp() LogMessage(LOG_CAL, " M %s", DMR_CAL_1K_STR); LogMessage(LOG_CAL, " m %s", DMR_DMO_CAL_1K_STR); LogMessage(LOG_CAL, " P %s", P25_CAL_1K_STR); + LogMessage(LOG_CAL, " N %s", NXDN_CAL_1K_STR); LogMessage(LOG_CAL, " B %s", DMR_FEC_STR); LogMessage(LOG_CAL, " J %s", DMR_FEC_1K_STR); LogMessage(LOG_CAL, " b %s", P25_FEC_STR); LogMessage(LOG_CAL, " j %s", P25_FEC_1K_STR); + LogMessage(LOG_CAL, " n %s", NXDN_FEC_STR); LogMessage(LOG_CAL, " x %s", RSSI_CAL_STR); LogMessage(LOG_CAL, "Engineering Commands:"); if (!m_isHotspot) { @@ -1262,13 +1425,17 @@ void HostCal::displayHelp() LogMessage(LOG_CAL, " _/+ Inc/Dec DMR +/- 1 Symbol Level"); LogMessage(LOG_CAL, " [/] Inc/Dec P25 +/- 3 Symbol Level"); LogMessage(LOG_CAL, " {/} Inc/Dec P25 +/- 1 Symbol Level"); + LogMessage(LOG_CAL, " ;/' Inc/Dec NXDN +/- 3 Symbol Level"); + LogMessage(LOG_CAL, " :/\" Inc/Dec NXDN +/- 1 Symbol Level"); } else { LogMessage(LOG_CAL, " 1 Set DMR Disc. Bandwidth Offset"); LogMessage(LOG_CAL, " 2 Set P25 Disc. Bandwidth Offset"); - LogMessage(LOG_CAL, " 3 Set DMR Post Demod Bandwidth Offset"); - LogMessage(LOG_CAL, " 4 Set P25 Post Demod Bandwidth Offset"); - LogMessage(LOG_CAL, " 5 Set ADF7021 Rx Auto. Gain Mode"); + LogMessage(LOG_CAL, " 3 Set NXDN Disc. Bandwidth Offset"); + LogMessage(LOG_CAL, " 4 Set DMR Post Demod Bandwidth Offset"); + LogMessage(LOG_CAL, " 5 Set P25 Post Demod Bandwidth Offset"); + LogMessage(LOG_CAL, " 6 Set NXDN Post Demod Bandwidth Offset"); + LogMessage(LOG_CAL, " 7 Set ADF7021 Rx Auto. Gain Mode"); } if (!m_modem->m_flashDisabled) { LogMessage(LOG_CAL, " E Erase modem configuration area"); @@ -1472,6 +1639,50 @@ bool HostCal::setP25SymLevel1Adj(int incr) return true; } +/// +/// Helper to change the NXDN Symbol Level 3 adjust. +/// +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setNXDNSymLevel3Adj(int incr) +{ + if (incr > 0 && m_nxdnSymLevel3Adj < 127) { + m_nxdnSymLevel3Adj++; + LogMessage(LOG_CAL, " - P25 Symbol Level +/- 3 Adjust: %d", m_nxdnSymLevel3Adj); + return writeSymbolAdjust(); + } + + if (incr < 0 && m_nxdnSymLevel3Adj > -127) { + m_nxdnSymLevel3Adj--; + LogMessage(LOG_CAL, " - P25 Symbol Level +/- 3 Adjust: %d", m_nxdnSymLevel3Adj); + return writeSymbolAdjust(); + } + + return true; +} + +/// +/// Helper to change the NXDN Symbol Level 1 adjust. +/// +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setNXDNSymLevel1Adj(int incr) +{ + if (incr > 0 && m_nxdnSymLevel1Adj < 127) { + m_nxdnSymLevel1Adj++; + LogMessage(LOG_CAL, " - P25 Symbol Level +/- 1 Adjust: %d", m_nxdnSymLevel1Adj); + return writeSymbolAdjust(); + } + + if (incr < 0 && m_nxdnSymLevel1Adj > -127) { + m_nxdnSymLevel1Adj--; + LogMessage(LOG_CAL, " - P25 Symbol Level +/- 1 Adjust: %d", m_nxdnSymLevel1Adj); + return writeSymbolAdjust(); + } + + return true; +} + /// /// Helper to toggle modem transmit mode. /// @@ -1951,6 +2162,64 @@ void HostCal::processP251KBER(const uint8_t* buffer) } } +/// +/// Process NXDN Rx BER. +/// +/// Buffer containing P25 audio frames +void HostCal::processNXDNBER(const uint8_t* buffer) +{ + using namespace nxdn; + + unsigned char data[NXDN_FRAME_LENGTH_BYTES]; + ::memcpy(data, buffer, NXDN_FRAME_LENGTH_BYTES); + nxdnScrambler(data); + + channel::LICH lich; + bool valid = lich.decode(data); + + if (valid) { + uint8_t usc = lich.getFCT(); + uint8_t opt = lich.getOption(); + + if (usc == NXDN_LICH_USC_SACCH_NS) { + if (m_berFrames == 0U) { + LogMessage(LOG_CAL, NXDN_MESSAGE_TYPE_VCALL ", 1031 Test Pattern Start"); + + timerStart(); + m_berErrs = 0U; + m_berBits = 0U; + m_berFrames = 0U; + return; + } else { + float ber = float(m_berErrs * 100U) / float(m_berBits); + LogMessage(LOG_CAL, NXDN_MESSAGE_TYPE_TX_REL ", 1031 Test Pattern BER, frames: %u, errs: %.3f%% (%u/%u)", m_berFrames, ber, m_berErrs, m_berBits); + + timerStop(); + m_berErrs = 0U; + m_berBits = 0U; + m_berFrames = 0U; + return; + } + } else if (opt == NXDN_LICH_STEAL_NONE) { + timerStart(); + + uint32_t errors = 0U; + + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + m_berBits += 188U; + m_berErrs += errors; + m_berFrames++; + + float ber = float(errors) / 1.88F; + LogMessage(LOG_CAL, NXDN_MESSAGE_TYPE_VCALL ", 1031 Test Pattern BER, (errs): %.3f%% (%u/188)", ber, errors); + } + } +} + /// /// Write configuration to the modem DSP. /// @@ -1967,7 +2236,8 @@ bool HostCal::writeConfig() /// True, if configuration is written, otherwise false. bool HostCal::writeConfig(uint8_t modeOverride) { - uint8_t buffer[50U]; + uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 17U; @@ -2034,7 +2304,17 @@ bool HostCal::writeConfig(uint8_t modeOverride) m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_p25CorrCount); buffer[14U] = (uint8_t)m_p25CorrCount; - int ret = m_modem->write(buffer, 17U); + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + buffer[1U] = 18U; + + if (m_nxdnEnabled) + buffer[4U] |= 0x10U; + + buffer[18U] = (uint8_t)(m_txLevel * 2.55F + 0.5F); + } + + int ret = m_modem->write(buffer, buffer[1U]); if (ret <= 0) return false; @@ -2050,7 +2330,8 @@ bool HostCal::writeConfig(uint8_t modeOverride) /// bool HostCal::writeRFParams() { - unsigned char buffer[18U]; + uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 18U; @@ -2070,21 +2351,31 @@ bool HostCal::writeRFParams() buffer[12U] = (unsigned char)(100 * 2.55F + 0.5F); // cal sets power fixed to 100 - m_conf["system"]["modem"]["dmrDiscBWAdj"] = __INT_STR(m_dmrDiscBWAdj); + m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_dmrDiscBWAdj); buffer[13U] = (uint8_t)(m_dmrDiscBWAdj + 128); - m_conf["system"]["modem"]["p25DiscBWAdj"] = __INT_STR(m_p25DiscBWAdj); + m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_p25DiscBWAdj); buffer[14U] = (uint8_t)(m_p25DiscBWAdj + 128); - m_conf["system"]["modem"]["dmrPostBWAdj"] = __INT_STR(m_dmrPostBWAdj); + m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_dmrPostBWAdj); buffer[15U] = (uint8_t)(m_dmrPostBWAdj + 128); - m_conf["system"]["modem"]["p25PostBWAdj"] = __INT_STR(m_p25PostBWAdj); + m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_p25PostBWAdj); buffer[16U] = (uint8_t)(m_p25PostBWAdj + 128); - m_conf["system"]["modem"]["adfGainMode"] = __INT_STR((int)m_adfGainMode); + m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_adfGainMode); buffer[17U] = (uint8_t)m_adfGainMode; - // CUtils::dump(1U, "Written", buffer, len); + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + buffer[1U] = 20U; + + m_conf["system"]["modem"]["hotspot"]["nxdnDiscBWAdj"] = __INT_STR(m_nxdnDiscBWAdj); + buffer[18U] = (uint8_t)(m_nxdnDiscBWAdj + 128); + m_conf["system"]["modem"]["hotspot"]["nxdnPostBWAdj"] = __INT_STR(m_nxdnPostBWAdj); + buffer[19U] = (uint8_t)(m_nxdnPostBWAdj + 128); + } - int ret = m_modem->write(buffer, 18U); + // CUtils::dump(1U, "Written", buffer, buffer[1U]); + + int ret = m_modem->write(buffer, buffer[1U]); if (ret <= 0) return false; @@ -2100,23 +2391,34 @@ bool HostCal::writeRFParams() /// True, if level adjustments are written, otherwise false. bool HostCal::writeSymbolAdjust() { - uint8_t buffer[10U]; + uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 7U; buffer[2U] = CMD_SET_SYMLVLADJ; - m_conf["system"]["modem"]["dmrSymLvl3Adj"] = __INT_STR(m_dmrSymLevel3Adj); + m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_dmrSymLevel3Adj); buffer[3U] = (uint8_t)(m_dmrSymLevel3Adj + 128); - m_conf["system"]["modem"]["dmrSymLvl1Adj"] = __INT_STR(m_dmrSymLevel1Adj); + m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_dmrSymLevel1Adj); buffer[4U] = (uint8_t)(m_dmrSymLevel1Adj + 128); - m_conf["system"]["modem"]["p25SymLvl3Adj"] = __INT_STR(m_p25SymLevel3Adj); + m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_p25SymLevel3Adj); buffer[5U] = (uint8_t)(m_p25SymLevel3Adj + 128); - m_conf["system"]["modem"]["p25SymLvl1Adj"] = __INT_STR(m_p25SymLevel1Adj); + m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_p25SymLevel1Adj); buffer[6U] = (uint8_t)(m_p25SymLevel1Adj + 128); - int ret = m_modem->write(buffer, 7U); + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + buffer[1U] = 9U; + + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_nxdnSymLevel3Adj); + buffer[7U] = (uint8_t)(m_nxdnSymLevel3Adj + 128); + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_nxdnSymLevel1Adj); + buffer[8U] = (uint8_t)(m_nxdnSymLevel1Adj + 128); + } + + int ret = m_modem->write(buffer, buffer[1U]); if (ret <= 0) return false; @@ -2210,30 +2512,46 @@ void HostCal::processFlashConfig(const uint8_t *buffer) // symbol adjust m_dmrSymLevel3Adj = int(buffer[35U]) - 128; - m_conf["system"]["modem"]["dmrSymLvl3Adj"] = __INT_STR(m_dmrSymLevel3Adj); + m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_dmrSymLevel3Adj); m_dmrSymLevel1Adj = int(buffer[36U]) - 128; - m_conf["system"]["modem"]["dmrSymLvl1Adj"] = __INT_STR(m_dmrSymLevel1Adj); + m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_dmrSymLevel1Adj); m_p25SymLevel3Adj = int(buffer[37U]) - 128; - m_conf["system"]["modem"]["p25SymLvl3Adj"] = __INT_STR(m_p25SymLevel3Adj); + m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_p25SymLevel3Adj); m_p25SymLevel1Adj = int(buffer[38U]) - 128; - m_conf["system"]["modem"]["p25SymLvl1Adj"] = __INT_STR(m_p25SymLevel1Adj); + m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_p25SymLevel1Adj); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_nxdnSymLevel3Adj = int(buffer[41U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_nxdnSymLevel3Adj); + m_nxdnSymLevel1Adj = int(buffer[42U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_nxdnSymLevel1Adj); + } writeSymbolAdjust(); sleep(500); // RF parameters m_dmrDiscBWAdj = int8_t(buffer[20U]) - 128; - m_conf["system"]["modem"]["dmrDiscBWAdj"] = __INT_STR(m_dmrDiscBWAdj); + m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_dmrDiscBWAdj); m_p25DiscBWAdj = int8_t(buffer[21U]) - 128; - m_conf["system"]["modem"]["p25DiscBWAdj"] = __INT_STR(m_p25DiscBWAdj); + m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_p25DiscBWAdj); m_dmrPostBWAdj = int8_t(buffer[22U]) - 128; - m_conf["system"]["modem"]["dmrPostBWAdj"] = __INT_STR(m_dmrPostBWAdj); + m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_dmrPostBWAdj); m_p25PostBWAdj = int8_t(buffer[23U]) - 128; - m_conf["system"]["modem"]["p25PostBWAdj"] = __INT_STR(m_p25PostBWAdj); + m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_p25PostBWAdj); m_adfGainMode = (ADF_GAIN_MODE)buffer[24U]; - m_conf["system"]["modem"]["adfGainMode"] = __INT_STR((int)m_adfGainMode); + m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_adfGainMode); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_nxdnDiscBWAdj = int8_t(buffer[39U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnDiscBWAdj"] = __INT_STR(m_nxdnDiscBWAdj); + m_nxdnPostBWAdj = int8_t(buffer[40U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnPostBWAdj"] = __INT_STR(m_nxdnPostBWAdj); + } m_txTuning = int(buffer[25U]) - 128; m_conf["system"]["modem"]["txTuning"] = __INT_STR(m_txTuning); @@ -2344,6 +2662,15 @@ bool HostCal::writeFlash() buffer[37U] = (uint8_t)(m_p25SymLevel3Adj + 128); buffer[38U] = (uint8_t)(m_p25SymLevel1Adj + 128); + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + buffer[39U] = (uint8_t)(m_nxdnDiscBWAdj + 128); + buffer[40U] = (uint8_t)(m_nxdnPostBWAdj + 128); + + buffer[41U] = (uint8_t)(m_nxdnSymLevel3Adj + 128); + buffer[42U] = (uint8_t)(m_nxdnSymLevel1Adj + 128); + } + // software signature std::string software; software.append(__NET_NAME__ " " __VER__ " (built " __BUILD__ ")"); @@ -2441,7 +2768,7 @@ void HostCal::printStatus() std::string modemPort = uartConfig["port"].as(); uint32_t portSpeed = uartConfig["speed"].as(115200U); - LogMessage(LOG_CAL, " - Operating Mode: %s, Port Type: %s, Modem Port: %s, Port Speed: %u", m_modeStr.c_str(), type.c_str(), modemPort.c_str(), portSpeed); + LogMessage(LOG_CAL, " - Operating Mode: %s, Port Type: %s, Modem Port: %s, Port Speed: %u, Proto Ver: %u", m_modeStr.c_str(), type.c_str(), modemPort.c_str(), portSpeed, m_modem->getVersion()); } { @@ -2454,10 +2781,23 @@ void HostCal::printStatus() if (!m_isHotspot) { LogMessage(LOG_CAL, " - DMR Symbol +/- 3 Level Adj.: %d, DMR Symbol +/- 1 Level Adj.: %d, P25 Symbol +/- 3 Level Adj.: %d, P25 Symbol +/- 1 Level Adj.: %d", m_dmrSymLevel3Adj, m_dmrSymLevel1Adj, m_p25SymLevel3Adj, m_p25SymLevel1Adj); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + LogMessage(LOG_CAL, " - NXDN Symbol +/- 3 Level Adj.: %d, NXDN Symbol +/- 1 Level Adj.: %d", + m_nxdnSymLevel3Adj, m_nxdnSymLevel1Adj); + } } if (m_isHotspot) { LogMessage(LOG_CAL, " - DMR Disc. BW: %d, P25 Disc. BW: %d, DMR Post Demod BW: %d, P25 Post Demod BW: %d", m_dmrDiscBWAdj, m_p25DiscBWAdj, m_dmrPostBWAdj, m_p25PostBWAdj); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + LogMessage(LOG_CAL, " - NXDN Disc. BW: %d, NXDN Post Demod BW: %d", + m_nxdnDiscBWAdj, m_nxdnPostBWAdj); + } + switch (m_adfGainMode) { case ADF_GAIN_AUTO_LIN: LogMessage(LOG_CAL, " - ADF7021 Gain Mode: Auto High Linearity"); @@ -2483,6 +2823,18 @@ void HostCal::printStatus() getStatus(); } +/// +/// +/// +/// +void HostCal::nxdnScrambler(uint8_t* data) const +{ + assert(data != NULL); + + for (uint32_t i = 0U; i < nxdn::NXDN_FRAME_LENGTH_BYTES; i++) + data[i] ^= NXDN_SCRAMBLER[i]; +} + /// /// Counts the total number of bit errors between bytes. /// diff --git a/host/calibrate/HostCal.h b/host/calibrate/HostCal.h index 83df0d9f..874c4370 100644 --- a/host/calibrate/HostCal.h +++ b/host/calibrate/HostCal.h @@ -82,6 +82,7 @@ private: bool m_dmrRx1K; bool m_p25Enabled; bool m_p25Rx1K; + bool m_nxdnEnabled; int m_rxDCOffset; // dedicated modem - Rx signal DC offset int m_txDCOffset; // dedicated modem - Tx signal DC offset @@ -89,8 +90,10 @@ private: int8_t m_dmrDiscBWAdj; // hotspot modem - DMR discriminator BW adjustment int8_t m_p25DiscBWAdj; // hotspot modem - P25 discriminator BW adjustment + int8_t m_nxdnDiscBWAdj; // hotspot modem - NXDN discriminator BW adjustment int8_t m_dmrPostBWAdj; // hotspot modem - DMR post demod BW adjustment int8_t m_p25PostBWAdj; // hotspot modem - P25 post demod BW adjustment + int8_t m_nxdnPostBWAdj; // hotspot modem - NXDN post demod BW adjustment modem::ADF_GAIN_MODE m_adfGainMode; // hotspot modem - ADF7021 Rx gain @@ -98,6 +101,8 @@ private: int m_dmrSymLevel1Adj; // dedicated modem - +1/-1 DMR symbol adjustment int m_p25SymLevel3Adj; // dedicated modem - +3/-3 P25 symbol adjustment int m_p25SymLevel1Adj; // dedicated modem - +1/-1 P25 symbol adjustment + int m_nxdnSymLevel3Adj; // dedicated modem - +3/-3 NXDN symbol adjustment + int m_nxdnSymLevel1Adj; // dedicated modem - +1/-1 NXDN symbol adjustment uint8_t m_fdmaPreamble; uint8_t m_dmrRxDelay; @@ -161,6 +166,10 @@ private: bool setP25SymLevel3Adj(int incr); /// Helper to change the P25 Symbol Level 1 adjust. bool setP25SymLevel1Adj(int incr); + /// Helper to change the NXDN Symbol Level 3 adjust. + bool setNXDNSymLevel3Adj(int incr); + /// Helper to change the NXDN Symbol Level 1 adjust. + bool setNXDNSymLevel1Adj(int incr); /// Process DMR Rx BER. void processDMRBER(const uint8_t* buffer, uint8_t seq); @@ -170,6 +179,8 @@ private: void processP25BER(const uint8_t* buffer); /// Process P25 Tx 1011hz BER. void processP251KBER(const uint8_t* buffer); + /// Process NXDN Rx BER. + void processNXDNBER(const uint8_t* buffer); /// Write configuration to the modem DSP. bool writeConfig(); @@ -203,6 +214,9 @@ private: /// Prints the current status of the calibration. void printStatus(); + /// + void nxdnScrambler(uint8_t* data) const; + /// Counts the total number of bit errors between bytes. uint8_t countErrs(uint8_t a, uint8_t b); }; diff --git a/host/setup/HostSetup.cpp b/host/setup/HostSetup.cpp index 5ea80f91..e1f690c3 100644 --- a/host/setup/HostSetup.cpp +++ b/host/setup/HostSetup.cpp @@ -278,6 +278,7 @@ int HostSetup::run() bool fixedMode = m_conf["system"]["fixedMode"].as(false); bool dmrEnabled = m_conf["protocols"]["dmr"]["enable"].as(true); bool p25Enabled = m_conf["protocols"]["p25"]["enable"].as(true); + bool nxdnEnabled = m_conf["protocols"]["nxdn"]["enable"].as(true); char value[9] = { '\0' }; ::fprintf(stdout, "> Identity [%s] ? ", identity.c_str()); @@ -366,6 +367,16 @@ int HostSetup::run() m_conf["protocols"]["p25"]["enable"] = __BOOL_STR(p25Enabled); + ::fprintf(stdout, "> NXDN Enabled [%u] (Y/N) ? ", nxdnEnabled); + ::fflush(stdout); + + m_console.getLine(value, 2, 0); + if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { + nxdnEnabled = value[0] == 'Y' ? true : false; + } + + m_conf["protocols"]["nxdn"]["enable"] = __BOOL_STR(nxdnEnabled); + printStatus(); } break; @@ -485,6 +496,7 @@ int HostSetup::run() rfssConfig = m_conf["system"]["config"]; uint32_t dmrColorCode = rfssConfig["colorCode"].as(2U); uint32_t p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as("293").c_str(), NULL, 16); + uint32_t nxdnRAN = rfssConfig["ran"].as(1U); char value[6] = { '\0' }; ::fprintf(stdout, "> DMR Color Code [%u] ? ", dmrColorCode); @@ -509,6 +521,16 @@ int HostSetup::run() m_conf["system"]["config"]["nac"] = __INT_HEX_STR(p25NAC); } + ::fprintf(stdout, "> NXDN RAN [%u] ? ", nxdnRAN); + ::fflush(stdout); + + m_console.getLine(value, 2, 0); + if (value[0] != '\0') { + sscanf(value, "%u", &nxdnRAN); + + m_conf["system"]["config"]["ran"] = __INT_STR(nxdnRAN); + } + printStatus(); } break; @@ -691,7 +713,7 @@ void HostSetup::displayHelp() LogMessage(LOG_SETUP, " s Set system configuration"); LogMessage(LOG_SETUP, " C Set callsign and CW configuration"); LogMessage(LOG_SETUP, " N Set site and network configuration"); - LogMessage(LOG_SETUP, " a Set NAC and Color Code"); + LogMessage(LOG_SETUP, " a Set NAC, Color Code and RAN"); LogMessage(LOG_SETUP, " i Set logical channel ID"); LogMessage(LOG_SETUP, " c Set logical channel number (by channel number)"); LogMessage(LOG_SETUP, " f Set logical channel number (by Tx frequency)"); diff --git a/modem/Modem.cpp b/modem/Modem.cpp index 35a23cfe..b55b8015 100644 --- a/modem/Modem.cpp +++ b/modem/Modem.cpp @@ -32,6 +32,7 @@ #include "Defines.h" #include "dmr/DMRDefines.h" #include "p25/P25Defines.h" +#include "nxdn/NXDNDefines.h" #include "modem/Modem.h" #include "edac/CRC.h" #include "Log.h" @@ -114,6 +115,7 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert, uint8_t fdmaPreamble, uint8_t dmrRxDelay, uint8_t p25CorrCount, uint8_t packetPlayoutTime, bool disableOFlowReset, bool ignoreModemConfigArea, bool dumpModemStatus, bool trace, bool debug) : m_port(port), + m_protoVer(0U), m_dmrColorCode(0U), m_p25NAC(0x293U), m_duplex(duplex), @@ -129,9 +131,11 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert, m_cwIdTXLevel(0U), m_dmrTXLevel(0U), m_p25TXLevel(0U), + m_nxdnTXLevel(0U), m_disableOFlowReset(disableOFlowReset), m_dmrEnabled(false), m_p25Enabled(false), + m_nxdnEnabled(false), m_rxDCOffset(0), m_txDCOffset(0), m_isHotspot(false), @@ -142,13 +146,17 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert, m_rfPower(0U), m_dmrDiscBWAdj(0), m_p25DiscBWAdj(0), + m_nxdnDiscBWAdj(0), m_dmrPostBWAdj(0), m_p25PostBWAdj(0), + m_nxdnPostBWAdj(0), m_adfGainMode(ADF_GAIN_AUTO), m_dmrSymLevel3Adj(0), m_dmrSymLevel1Adj(0), m_p25SymLevel3Adj(0), m_p25SymLevel1Adj(0), + m_nxdnSymLevel3Adj(0), + m_nxdnSymLevel1Adj(0), m_adcOverFlowCount(0U), m_dacOverFlowCount(0U), m_modemState(STATE_IDLE), @@ -167,12 +175,15 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert, m_txDMRData2(1000U, "Modem TX DMR2"), m_rxP25Data(1000U, "Modem RX P25"), m_txP25Data(1000U, "Modem TX P25"), + m_rxNXDNData(1000U, "Modem RX NXDN"), + m_txNXDNData(1000U, "Modem TX NXDN"), m_useDFSI(false), m_statusTimer(1000U, 0U, 250U), m_inactivityTimer(1000U, 4U), m_dmrSpace1(0U), m_dmrSpace2(0U), m_p25Space(0U), + m_nxdnSpace(0U), m_tx(false), m_cd(false), m_lockout(false), @@ -214,7 +225,8 @@ void Modem::setDCOffsetParams(int txDCOffset, int rxDCOffset) /// /// /// -void Modem::setModeParams(bool dmrEnabled, bool p25Enabled) +/// +void Modem::setModeParams(bool dmrEnabled, bool p25Enabled, bool nxdnEnabled) { m_dmrEnabled = dmrEnabled; m_p25Enabled = p25Enabled; @@ -227,12 +239,14 @@ void Modem::setModeParams(bool dmrEnabled, bool p25Enabled) /// /// /// -void Modem::setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel) +/// +void Modem::setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel, float nxdnTXLevel) { m_rxLevel = rxLevel; m_cwIdTXLevel = cwIdTXLevel; m_dmrTXLevel = dmrTXLevel; m_p25TXLevel = p25TXLevel; + m_nxdnTXLevel = nxdnTXLevel; } /// @@ -242,7 +256,9 @@ void Modem::setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float /// /// /// -void Modem::setSymbolAdjust(int dmrSymLevel3Adj, int dmrSymLevel1Adj, int p25SymLevel3Adj, int p25SymLevel1Adj) +/// +/// +void Modem::setSymbolAdjust(int dmrSymLevel3Adj, int dmrSymLevel1Adj, int p25SymLevel3Adj, int p25SymLevel1Adj, int nxdnSymLevel3Adj, int nxdnSymLevel1Adj) { m_dmrSymLevel3Adj = dmrSymLevel3Adj; if (dmrSymLevel3Adj > 128) @@ -267,6 +283,18 @@ void Modem::setSymbolAdjust(int dmrSymLevel3Adj, int dmrSymLevel1Adj, int p25Sym m_p25SymLevel1Adj = 0; if (p25SymLevel1Adj < -128) m_p25SymLevel1Adj = 0; + + m_nxdnSymLevel3Adj = nxdnSymLevel3Adj; + if (nxdnSymLevel3Adj > 128) + m_nxdnSymLevel3Adj = 0; + if (nxdnSymLevel3Adj < -128) + m_nxdnSymLevel3Adj = 0; + + m_nxdnSymLevel1Adj = nxdnSymLevel1Adj; + if (nxdnSymLevel1Adj > 128) + m_nxdnSymLevel1Adj = 0; + if (nxdnSymLevel1Adj < -128) + m_nxdnSymLevel1Adj = 0; } /// @@ -277,11 +305,15 @@ void Modem::setSymbolAdjust(int dmrSymLevel3Adj, int dmrSymLevel1Adj, int p25Sym /// /// /// +/// /// /// +/// /// void Modem::setRFParams(uint32_t rxFreq, uint32_t txFreq, int rxTuning, int txTuning, uint8_t rfPower, - int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t dmrPostBWAdj, int8_t p25PostBWAdj, ADF_GAIN_MODE gainMode) + int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t nxdnDiscBWAdj, + int8_t dmrPostBWAdj, int8_t p25PostBWAdj, int8_t nxdnPostBWAdj, + ADF_GAIN_MODE gainMode) { m_adfGainMode = gainMode; m_rfPower = rfPower; @@ -292,8 +324,10 @@ void Modem::setRFParams(uint32_t rxFreq, uint32_t txFreq, int rxTuning, int txTu m_dmrDiscBWAdj = dmrDiscBWAdj; m_p25DiscBWAdj = p25DiscBWAdj; + m_nxdnDiscBWAdj = nxdnDiscBWAdj; m_dmrPostBWAdj = dmrPostBWAdj; m_p25PostBWAdj = p25PostBWAdj; + m_nxdnPostBWAdj = nxdnPostBWAdj; } /// @@ -647,6 +681,49 @@ void Modem::clock(uint32_t ms) } break; + /** Next Generation Digital Narrowband */ + case CMD_NXDN_DATA: + { +#if ENABLE_NXDN_SUPPORT + //if (m_trace) + // Utils::dump(1U, "RX NXDN Data", m_buffer, m_length); + + if (m_rspDoubleLength) { + LogError(LOG_MODEM, "CMD_NXDN_DATA double length?; len = %u", m_length); + break; + } + + uint8_t data = m_length - 2U; + m_rxNXDNData.addData(&data, 1U); + + data = TAG_DATA; + m_rxNXDNData.addData(&data, 1U); + + m_rxNXDNData.addData(m_buffer + 3U, m_length - 3U); +#endif + } + break; + + case CMD_NXDN_LOST: + { +#if ENABLE_NXDN_SUPPORT + //if (m_trace) + // Utils::dump(1U, "RX NXDN Lost", m_buffer, m_length); + + if (m_rspDoubleLength) { + LogError(LOG_MODEM, "CMD_NXDN_LOST double length?; len = %u", m_length); + break; + } + + uint8_t data = 1U; + m_rxNXDNData.addData(&data, 1U); + + data = TAG_LOST; + m_rxNXDNData.addData(&data, 1U); +#endif + } + break; + /** General */ case CMD_GET_STATUS: { @@ -724,16 +801,17 @@ void Modem::clock(uint32_t ms) m_dmrSpace1 = m_buffer[7U]; m_dmrSpace2 = m_buffer[8U]; m_p25Space = m_buffer[10U]; + m_nxdnSpace = m_buffer[11U]; if (m_dumpModemStatus) { LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, isHotspot = %u, modemState = %u, tx = %u, adcOverflow = %u, rxOverflow = %u, txOverflow = %u, dacOverflow = %u, dmrSpace1 = %u, dmrSpace2 = %u, p25Space = %u", m_isHotspot, m_modemState, m_tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_dmrSpace1, m_dmrSpace2, m_p25Space); - LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, rxDMRData1 size = %u, len = %u, free = %u; rxDMRData2 size = %u, len = %u, free = %u, rxP25Data size = %u, len = %u, free = %u", + LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, rxDMRData1 size = %u, len = %u, free = %u; rxDMRData2 size = %u, len = %u, free = %u, rxP25Data size = %u, len = %u, free = %u, rxNXDNData size = %u, len = %u, free = %u", m_rxDMRData1.length(), m_rxDMRData1.dataSize(), m_rxDMRData1.freeSpace(), m_rxDMRData2.length(), m_rxDMRData2.dataSize(), m_rxDMRData2.freeSpace(), - m_rxP25Data.length(), m_rxP25Data.dataSize(), m_rxP25Data.freeSpace()); - LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, txDMRData1 size = %u, len = %u, free = %u; txDMRData2 size = %u, len = %u, free = %u, txP25Data size = %u, len = %u, free = %u", + m_rxP25Data.length(), m_rxP25Data.dataSize(), m_rxP25Data.freeSpace(), m_rxNXDNData.length(), m_rxNXDNData.dataSize(), m_rxNXDNData.freeSpace()); + LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, txDMRData1 size = %u, len = %u, free = %u; txDMRData2 size = %u, len = %u, free = %u, txP25Data size = %u, len = %u, free = %u, txNXDNData size = %u, len = %u, free = %u", m_txDMRData1.length(), m_txDMRData1.dataSize(), m_txDMRData1.freeSpace(), m_txDMRData2.length(), m_txDMRData2.dataSize(), m_txDMRData2.freeSpace(), - m_txP25Data.length(), m_txP25Data.dataSize(), m_txP25Data.freeSpace()); + m_txP25Data.length(), m_txP25Data.dataSize(), m_txP25Data.freeSpace(), m_txNXDNData.length(), m_txNXDNData.dataSize(), m_txNXDNData.freeSpace()); } m_inactivityTimer.start(); @@ -901,6 +979,25 @@ uint32_t Modem::readP25Data(uint8_t* data) return len; } +/// +/// Reads NXDN frame data from the NXDN ring buffer. +/// +/// Buffer to write frame data to. +/// Length of data read from ring buffer. +uint32_t Modem::readNXDNData(uint8_t* data) +{ + assert(data != NULL); + + if (m_rxNXDNData.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_rxNXDNData.getData(&len, 1U); + m_rxNXDNData.getData(data, len); + + return len; +} + /// /// Helper to test if the DMR Slot 1 ring buffer has free space. /// @@ -931,6 +1028,16 @@ bool Modem::hasP25Space() const return space > 1U; } +/// +/// Helper to test if the NXDN ring buffer has free space. +/// +/// True, if the NXDN ring buffer has free space, otherwise false. +bool Modem::hasNXDNSpace() const +{ + uint32_t space = m_txNXDNData.freeSpace() / (nxdn::NXDN_FRAME_LENGTH_BYTES + 4U); + return space > 1U; +} + /// /// Helper to test if the modem is a hotspot. /// @@ -1025,6 +1132,16 @@ void Modem::clearP25Data() } } +/// +/// Clears any buffered NXDN frame data to be sent to the air interface modem. +/// +void Modem::clearNXDNData() +{ + if (!m_txNXDNData.isEmpty()) { + m_txNXDNData.clear(); + } +} + /// /// Internal helper to inject DMR Slot 1 frame data as if it came from the air interface modem. /// @@ -1107,6 +1224,30 @@ void Modem::injectP25Data(const uint8_t* data, uint32_t length) m_rxP25Data.addData(data, length); } +/// +/// Internal helper to inject NXDN frame data as if it came from the air interface modem. +/// +/// Data to write to ring buffer. +/// Length of data to write. +void Modem::injectNXDNData(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "Injected NXDN Data", data, length); + + uint8_t val = length; + m_rxNXDNData.addData(&val, 1U); + + val = TAG_DATA; + m_rxNXDNData.addData(&val, 1U); + val = 0x01U; // valid sync + m_rxNXDNData.addData(&val, 1U); + + m_rxNXDNData.addData(data, length); +} + /// /// Writes DMR Slot 1 frame data to the DMR Slot 1 ring buffer. /// @@ -1271,6 +1412,61 @@ bool Modem::writeP25Data(const uint8_t* data, uint32_t length, bool immediate) return true; } +/// +/// Writes NXDN frame data to the NXDN ring buffer. +/// +/// Data to write to ring buffer. +/// Length of data to write. +/// Flag indicating data should be immediately written. +/// True, if data is written, otherwise false. +bool Modem::writeNXDNData(const uint8_t* data, uint32_t length, bool immediate) +{ + assert(data != NULL); + assert(length > 0U); + + const uint8_t MAX_LENGTH = 250U; + + if (data[0U] != TAG_DATA && data[0U] != TAG_EOT) + return false; + if (length > MAX_LENGTH) { + LogError(LOG_MODEM, "Modem::writeNXDNData(); request data to write >%u?, len = %u", MAX_LENGTH, length); + Utils::dump(1U, "Modem::writeNXDNData(); Attmpted Data", data, length); + return false; + } + + uint8_t buffer[MAX_LENGTH]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = CMD_NXDN_DATA; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + uint8_t len = length + 2U; + + // write or buffer NXDN data to air interface + if (immediate && m_nxdnSpace > 1U) { + if (m_debug) + LogDebug(LOG_MODEM, "Modem::writeNXDNData(); immediate write (len %u)", length); + //if (m_trace) + // Utils::dump(1U, "Immediate TX NXDN Data", m_buffer, len); + + int ret = write(m_buffer, len); + if (ret != int(len)) + LogError(LOG_MODEM, "Error writing NXDN data"); + + m_playoutTimer.start(); + + m_nxdnSpace--; + } + else { + m_txNXDNData.addData(&len, 1U); + m_txNXDNData.addData(buffer, len); + } + + return true; +} + /// /// Triggers the start of DMR transmit. /// @@ -1415,6 +1611,15 @@ bool Modem::sendCWId(const std::string& callsign) return write(buffer, length + 3U) == int(length + 3U); } +/// +/// Returns the protocol version of the connected modem. +/// +/// +uint8_t Modem::getVersion() const +{ + return m_protoVer; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -1470,10 +1675,14 @@ bool Modem::getFirmwareVersion() if (resp == RTM_OK && m_buffer[2U] == CMD_GET_VERSION) { LogMessage(LOG_MODEM, "Protocol: %02x, CPU: %02X", m_buffer[3U], m_buffer[4U]); - uint8_t protoVer = m_buffer[3U]; - switch (protoVer) { - case PROTOCOL_VERSION: - LogInfoEx(LOG_MODEM, MODEM_VERSION_STR, m_length - 21U, m_buffer + 21U, protoVer); + m_protoVer = m_buffer[3U]; + + if (m_protoVer >= 2U) { + LogInfoEx(LOG_MODEM, MODEM_VERSION_STR, m_length - 21U, m_buffer + 21U, m_protoVer); + if (m_protoVer < 3U) { + LogWarning(LOG_MODEM, "Legacy firmware detected; this version of the firmware will not support NXDN or any future enhancments."); + } + switch (m_buffer[4U]) { case 0U: LogMessage(LOG_MODEM, "Atmel ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U]); @@ -1491,10 +1700,11 @@ bool Modem::getFirmwareVersion() LogMessage(LOG_MODEM, "Unknown CPU type: %u", m_buffer[4U]); break; } - return true; - default: - LogError(LOG_MODEM, MODEM_UNSUPPORTED_STR, protoVer); + return true; + } + else { + LogError(LOG_MODEM, MODEM_UNSUPPORTED_STR, m_protoVer); return false; } } @@ -1530,6 +1740,7 @@ bool Modem::getStatus() bool Modem::writeConfig() { uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 17U; @@ -1585,10 +1796,22 @@ bool Modem::writeConfig() buffer[17U] = (uint8_t)(m_rxDCOffset + 128); buffer[14U] = m_p25CorrCount; + + // are we on a protocol version 3 firmware? + if (m_protoVer >= 3U) { + buffer[1U] = 18U; + + if (m_nxdnEnabled) + buffer[4U] |= 0x10U; + + buffer[18U] = (uint8_t)(m_nxdnTXLevel * 2.55F + 0.5F); + } + #if DEBUG_MODEM - Utils::dump(1U, "Modem::writeConfig(), Written", buffer, 17U); + Utils::dump(1U, "Modem::writeConfig(), Written", buffer, buffer[1U]); #endif - int ret = write(buffer, 17U); + + int ret = write(buffer, buffer[1U]); if (ret != 17) return false; @@ -1625,7 +1848,8 @@ bool Modem::writeConfig() /// True, if level adjustments are written, otherwise false. bool Modem::writeSymbolAdjust() { - uint8_t buffer[10U]; + uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 7U; @@ -1637,7 +1861,15 @@ bool Modem::writeSymbolAdjust() buffer[5U] = (uint8_t)(m_p25SymLevel3Adj + 128); buffer[6U] = (uint8_t)(m_p25SymLevel1Adj + 128); - int ret = write(buffer, 7U); + // are we on a protocol version 3 firmware? + if (m_protoVer >= 3U) { + buffer[1U] = 9U; + + buffer[7U] = (uint8_t)(m_nxdnSymLevel3Adj + 128); + buffer[8U] = (uint8_t)(m_nxdnSymLevel1Adj + 128); + } + + int ret = write(buffer, buffer[1U]); if (ret <= 0) return false; @@ -1672,7 +1904,8 @@ bool Modem::writeSymbolAdjust() /// bool Modem::writeRFParams() { - unsigned char buffer[18U]; + uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 18U; @@ -1701,9 +1934,17 @@ bool Modem::writeRFParams() buffer[17U] = (uint8_t)m_adfGainMode; - // CUtils::dump(1U, "Written", buffer, len); + // are we on a protocol version 3 firmware? + if (m_protoVer >= 3U) { + buffer[1U] = 20U; + + buffer[18U] = (uint8_t)(m_nxdnDiscBWAdj + 128); + buffer[19U] = (uint8_t)(m_nxdnPostBWAdj + 128); + } + + // CUtils::dump(1U, "Written", buffer, buffer[1U]); - int ret = m_port->write(buffer, 18U); + int ret = m_port->write(buffer, buffer[1U]); if (ret <= 0) return false; @@ -1840,6 +2081,7 @@ void Modem::processFlashConfig(const uint8_t *buffer) FLASH_VALUE_CHECK_FLOAT(m_cwIdTXLevel, txLevel, 50.0F, "cwIdTxLevel"); FLASH_VALUE_CHECK_FLOAT(m_dmrTXLevel, txLevel, 50.0F, "dmrTxLevel"); FLASH_VALUE_CHECK_FLOAT(m_p25TXLevel, txLevel, 50.0F, "p25TxLevel"); + FLASH_VALUE_CHECK_FLOAT(m_nxdnTXLevel, txLevel, 50.0F, "nxdnTxLevel"); uint8_t dmrRxDelay = buffer[10U]; FLASH_VALUE_CHECK(m_dmrRxDelay, dmrRxDelay, 7U, "dmrRxDelay"); @@ -1863,6 +2105,14 @@ void Modem::processFlashConfig(const uint8_t *buffer) int8_t p25PostBWAdj = int8_t(buffer[23U]) - 128; FLASH_VALUE_CHECK(m_p25PostBWAdj, p25PostBWAdj, 0, "p25PostBWAdj"); + // are we on a protocol version 3 firmware? + if (m_protoVer >= 3U) { + int8_t nxdnDiscBWAdj = int8_t(buffer[39U]) - 128; + FLASH_VALUE_CHECK(m_nxdnDiscBWAdj, nxdnDiscBWAdj, 0, "nxdnDiscBWAdj"); + int8_t nxdnPostBWAdj = int8_t(buffer[40U]) - 128; + FLASH_VALUE_CHECK(m_nxdnPostBWAdj, nxdnPostBWAdj, 0, "nxdnPostBWAdj"); + } + ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)buffer[24U]; FLASH_VALUE_CHECK(m_adfGainMode, adfGainMode, ADF_GAIN_AUTO, "adfGainMode"); @@ -1883,6 +2133,14 @@ void Modem::processFlashConfig(const uint8_t *buffer) FLASH_VALUE_CHECK(m_p25SymLevel3Adj, p25SymLevel3Adj, 0, "p25SymLevel3Adj"); int p25SymLevel1Adj = int(buffer[38U]) - 128; FLASH_VALUE_CHECK(m_p25SymLevel1Adj, p25SymLevel1Adj, 0, "p25SymLevel1Adj"); + + // are we on a protocol version 3 firmware? + if (m_protoVer >= 3U) { + int nxdnSymLevel3Adj = int(buffer[41U]) - 128; + FLASH_VALUE_CHECK(m_nxdnSymLevel3Adj, nxdnSymLevel3Adj, 0, "nxdnSymLevel3Adj"); + int nxdnSymLevel1Adj = int(buffer[42U]) - 128; + FLASH_VALUE_CHECK(m_nxdnSymLevel1Adj, nxdnSymLevel1Adj, 0, "nxdnSymLevel1Adj"); + } } /// diff --git a/modem/Modem.h b/modem/Modem.h index bf19fa54..b5e75caa 100644 --- a/modem/Modem.h +++ b/modem/Modem.h @@ -50,8 +50,6 @@ #define MODEM_RESP_HANDLER_BIND(funcAddr, classInstance) std::bind(&funcAddr, classInstance, std::placeholders::_1, std::placeholders::_2, \ std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6) -const uint8_t PROTOCOL_VERSION = 2U; - // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- @@ -76,6 +74,8 @@ namespace modem STATE_DMR = 1U, // Project 25 STATE_P25 = 2U, + // NXDN + STATE_NXDN = 3U, // CW STATE_CW = 10U, @@ -91,7 +91,8 @@ namespace modem STATE_RSSI_CAL = 96U, STATE_P25_CAL = 97U, - STATE_DMR_CAL = 98U + STATE_DMR_CAL = 98U, + STATE_NXDN_CAL = 99U }; enum DVM_COMMANDS { @@ -121,6 +122,9 @@ namespace modem CMD_P25_LOST = 0x32U, CMD_P25_CLEAR = 0x33U, + CMD_NXDN_DATA = 0x41U, + CMD_NXDN_LOST = 0x42U, + CMD_ACK = 0x70U, CMD_NAK = 0x7FU, @@ -171,6 +175,7 @@ namespace modem RSN_DMR_DISABLED = 63U, RSN_P25_DISABLED = 64U, + RSN_NXDN_DISABLED = 65U }; enum RESP_STATE { @@ -190,7 +195,7 @@ namespace modem const uint8_t DVM_FRAME_START = 0xFEU; - const uint8_t DVM_CONF_AREA_VER = 0x01U; + const uint8_t DVM_CONF_AREA_VER = 0x02U; const uint8_t DVM_CONF_AREA_LEN = 246U; const uint8_t MAX_FDMA_PREAMBLE = 255U; @@ -218,14 +223,14 @@ namespace modem /// Sets the RF DC offset parameters. void setDCOffsetParams(int txDCOffset, int rxDCOffset); /// Sets the enabled modes. - void setModeParams(bool dmrEnabled, bool p25Enabled); + void setModeParams(bool dmrEnabled, bool p25Enabled, bool nxdnEnabled); /// Sets the RF deviation levels. - void setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel); + void setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel, float nxdnTXLevel); /// Sets the symbol adjustment levels. - void setSymbolAdjust(int dmrSymLevel3Adj, int dmrSymLevel1Adj, int p25SymLevel3Adj, int p25SymLevel1Adj); + void setSymbolAdjust(int dmrSymLevel3Adj, int dmrSymLevel1Adj, int p25SymLevel3Adj, int p25SymLevel1Adj, int nxdnSymLevel3Adj, int ndxnSymLevel1Adj); /// Sets the RF parameters. - void setRFParams(uint32_t rxFreq, uint32_t txFreq, int rxTuning, int txTuning, uint8_t rfPower, int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, - int8_t dmrPostBWAdj, int8_t p25PostBWAdj, ADF_GAIN_MODE gainMode); + void setRFParams(uint32_t rxFreq, uint32_t txFreq, int rxTuning, int txTuning, uint8_t rfPower, int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t nxdnDiscBWAdj, + int8_t dmrPostBWAdj, int8_t p25PostBWAdj, int8_t nxdnPostBWAdj, ADF_GAIN_MODE gainMode); /// Sets the DMR color code. void setDMRColorCode(uint32_t colorCode); /// Sets the P25 NAC. @@ -260,6 +265,8 @@ namespace modem uint32_t readDMRData2(uint8_t* data); /// Reads P25 frame data from the P25 ring buffer. uint32_t readP25Data(uint8_t* data); + /// Reads NXDN frame data from the NXDN ring buffer. + uint32_t readNXDNData(uint8_t* data); /// Helper to test if the DMR Slot 1 ring buffer has free space. bool hasDMRSpace1() const; @@ -267,6 +274,8 @@ namespace modem bool hasDMRSpace2() const; /// Helper to test if the P25 ring buffer has free space. bool hasP25Space() const; + /// Helper to test if the NXDN ring buffer has free space. + bool hasNXDNSpace() const; /// Helper to test if the modem is a hotspot. bool isHotspot() const; @@ -290,6 +299,8 @@ namespace modem void clearDMRData2(); /// Clears any buffered P25 frame data to be sent to the air interface modem. void clearP25Data(); + /// Clears any buffered NXDN frame data to be sent to the air interface modem. + void clearNXDNData(); /// Internal helper to inject DMR Slot 1 frame data as if it came from the air interface modem. void injectDMRData1(const uint8_t* data, uint32_t length); @@ -297,6 +308,8 @@ namespace modem void injectDMRData2(const uint8_t* data, uint32_t length); /// Internal helper to inject P25 frame data as if it came from the air interface modem. void injectP25Data(const uint8_t* data, uint32_t length); + /// Internal helper to inject NXDN frame data as if it came from the air interface modem. + void injectNXDNData(const uint8_t* data, uint32_t length); /// Writes DMR Slot 1 frame data to the DMR Slot 1 ring buffer. bool writeDMRData1(const uint8_t* data, uint32_t length, bool immediate = false); @@ -304,6 +317,8 @@ namespace modem bool writeDMRData2(const uint8_t* data, uint32_t length, bool immediate = false); /// Writes P25 frame data to the P25 ring buffer. bool writeP25Data(const uint8_t* data, uint32_t length, bool immediate = false); + /// Writes NXDN frame data to the NXDN ring buffer. + bool writeNXDNData(const uint8_t* data, uint32_t length, bool immediate = false); /// Triggers the start of DMR transmit. bool writeDMRStart(bool tx); @@ -323,11 +338,16 @@ namespace modem /// Transmits the given string as CW morse. bool sendCWId(const std::string& callsign); + /// Returns the protocol version of the connected modem. + uint8_t getVersion() const; + private: friend class ::HostCal; port::IModemPort* m_port; + uint8_t m_protoVer; + uint32_t m_dmrColorCode; uint32_t m_p25NAC; @@ -348,11 +368,13 @@ namespace modem float m_cwIdTXLevel; // dedicated/hotspot modem - CW ID Tx modulation level float m_dmrTXLevel; // dedicated/hotspot modem - DMR Tx modulation level float m_p25TXLevel; // dedicated/hotspot modem - P25 Tx modulation level + float m_nxdnTXLevel; // dedicated/hotspot modem - P25 Tx modulation level bool m_disableOFlowReset; bool m_dmrEnabled; bool m_p25Enabled; + bool m_nxdnEnabled; int m_rxDCOffset; // dedicated modem - Rx signal DC offset int m_txDCOffset; // dedicated modem - Tx signal DC offset @@ -366,8 +388,10 @@ namespace modem int8_t m_dmrDiscBWAdj; // hotspot modem - DMR discriminator BW adjustment int8_t m_p25DiscBWAdj; // hotspot modem - P25 discriminator BW adjustment + int8_t m_nxdnDiscBWAdj; // hotspot modem - NXDN discriminator BW adjustment int8_t m_dmrPostBWAdj; // hotspot modem - DMR post demod BW adjustment int8_t m_p25PostBWAdj; // hotspot modem - P25 post demod BW adjustment + int8_t m_nxdnPostBWAdj; // hotspot modem - NXDN post demod BW adjustment ADF_GAIN_MODE m_adfGainMode; // hotspot modem - ADF7021 Rx gain @@ -375,6 +399,8 @@ namespace modem int m_dmrSymLevel1Adj; // dedicated modem - +1/-1 DMR symbol adjustment int m_p25SymLevel3Adj; // dedicated modem - +3/-3 P25 symbol adjustment int m_p25SymLevel1Adj; // dedicated modem - +1/-1 P25 symbol adjustment + int m_nxdnSymLevel3Adj; // dedicated modem - +3/-3 NXDN symbol adjustment + int m_nxdnSymLevel1Adj; // dedicated modem - +1/-1 NXDN symbol adjustment uint32_t m_adcOverFlowCount; // dedicated modem - ADC overflow count uint32_t m_dacOverFlowCount; // dedicated modem - DAC overflow count @@ -398,6 +424,8 @@ namespace modem RingBuffer m_txDMRData2; RingBuffer m_rxP25Data; RingBuffer m_txP25Data; + RingBuffer m_rxNXDNData; + RingBuffer m_txNXDNData; bool m_useDFSI; @@ -407,6 +435,7 @@ namespace modem uint32_t m_dmrSpace1; uint32_t m_dmrSpace2; uint32_t m_p25Space; + uint32_t m_nxdnSpace; bool m_tx; bool m_cd; diff --git a/modem/port/ModemNullPort.cpp b/modem/port/ModemNullPort.cpp index 35b20898..df97b6bc 100644 --- a/modem/port/ModemNullPort.cpp +++ b/modem/port/ModemNullPort.cpp @@ -138,7 +138,7 @@ void ModemNullPort::getVersion() reply[1U] = 0U; reply[2U] = CMD_GET_VERSION; - reply[3U] = PROTOCOL_VERSION; + reply[3U] = 3U; reply[4U] = 15U; // Reserve 16 bytes for the UDID diff --git a/network/BaseNetwork.cpp b/network/BaseNetwork.cpp index 056eabc5..0ddc1857 100644 --- a/network/BaseNetwork.cpp +++ b/network/BaseNetwork.cpp @@ -75,8 +75,10 @@ BaseNetwork::BaseNetwork(uint16_t localPort, uint32_t id, bool duplex, bool debu m_salt(NULL), m_streamId(NULL), m_p25StreamId(0U), + m_nxdnStreamId(0U), m_rxDMRData(4000U, "DMR Net Buffer"), m_rxP25Data(4000U, "P25 Net Buffer"), + m_rxNXDNData(4000U, "NXDN Net Buffer"), m_rxGrantData(4000U, "Grant Net Buffer"), m_audio(), m_random() @@ -93,6 +95,7 @@ BaseNetwork::BaseNetwork(uint16_t localPort, uint32_t id, bool duplex, bool debu std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); m_p25StreamId = dist(m_random); + m_nxdnStreamId = dist(m_random); m_streamId[0U] = dist(m_random); m_streamId[1U] = dist(m_random); } @@ -251,6 +254,47 @@ uint8_t* BaseNetwork::readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpe return data; } +/// +/// Reads NXDN frame data from the NXDN ring buffer. +/// +/// +/// +/// +uint8_t* BaseNetwork::readNXDN(bool& ret, uint32_t& len) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) { + ret = false; + return NULL; + } + + if (m_rxNXDNData.isEmpty()) { + ret = false; + return NULL; + } + + uint8_t length = 0U; + m_rxNXDNData.getData(&length, 1U); + m_rxNXDNData.getData(m_buffer, length); + + /* TODO TODO -- process more data out of the raw network frames? */ + + uint8_t* data = NULL; + len = m_buffer[23U]; + + if (len <= 24) { + data = new uint8_t[len]; + ::memset(data, 0x00U, len); + } + else { + data = new uint8_t[len]; + ::memset(data, 0x00U, len); + ::memcpy(data, m_buffer + 24U, len); + } + + ret = true; + return data; +} + /// /// Reads a channel grant request from the network. /// @@ -424,6 +468,26 @@ bool BaseNetwork::writeP25PDU(const p25::data::DataHeader& header, const p25::da return writeP25PDU(m_id, m_p25StreamId, header, secHeader, currentBlock, data, len); } +/// +/// Writes NXDN frame data to the network. +/// +/// +/// +/// +bool BaseNetwork::writeNXDN(const uint8_t* data, const uint32_t len) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); + if (m_nxdnStreamId == 0U) + m_nxdnStreamId = dist(m_random); + + m_streamId[0] = m_nxdnStreamId; + + return writeNXDN(m_id, m_nxdnStreamId, data, len); +} + /// /// Writes a channel grant request to the network. /// @@ -537,6 +601,18 @@ void BaseNetwork::resetP25() m_rxP25Data.clear(); } +/// +/// Resets the NXDN ring buffer. +/// +void BaseNetwork::resetNXDN() +{ + std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); + m_nxdnStreamId = dist(m_random); + m_streamId[0] = m_nxdnStreamId; + + m_rxNXDNData.clear(); +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- @@ -988,6 +1064,48 @@ bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const return true; } +/// +/// Writes NXDN frame data to the network. +/// +/// +/// +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeNXDN(const uint32_t id, const uint32_t streamId, const uint8_t* data, const uint32_t len) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + assert(data != NULL); + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_NXDN_DATA, 4U); + + /* TODO TODO -- pack more data out of the raw network frames? */ + + __SET_UINT32(streamId, buffer, 16U); // Stream ID + + uint32_t count = 24U; + + ::memcpy(buffer + 24U, data, len); + count += len; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, NXDN", buffer, (count + PACKET_PAD)); + + write(buffer, (count + PACKET_PAD)); + + return true; +} + /// /// Writes data to the network. /// diff --git a/network/BaseNetwork.h b/network/BaseNetwork.h index 3e535ad0..4d0c1de5 100644 --- a/network/BaseNetwork.h +++ b/network/BaseNetwork.h @@ -34,6 +34,7 @@ #include "Defines.h" #include "dmr/DMRDefines.h" #include "p25/P25Defines.h" +#include "nxdn/NXDNDefines.h" #include "dmr/data/Data.h" #include "p25/data/LowSpeedData.h" #include "p25/dfsi/DFSIDefines.h" @@ -59,6 +60,7 @@ #define TAG_DMR_DATA "DMRD" #define TAG_P25_DATA "P25D" +#define TAG_NXDN_DATA "NXDD" #define TAG_MASTER_WL_RID "MSTWRID" #define TAG_MASTER_BL_RID "MSTBRID" @@ -137,6 +139,8 @@ namespace network virtual bool readDMR(dmr::data::Data& data); /// 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, 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); @@ -155,6 +159,9 @@ namespace network virtual bool writeP25PDU(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. + virtual bool writeNXDN(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); @@ -177,6 +184,8 @@ namespace network virtual void resetDMR(uint32_t slotNo); /// Resets the P25 ring buffer. virtual void resetP25(); + /// Resets the NXDN ring buffer. + virtual void resetNXDN(); protected: uint32_t m_id; @@ -204,9 +213,11 @@ namespace network uint32_t* m_streamId; uint32_t m_p25StreamId; + uint32_t m_nxdnStreamId; RingBuffer m_rxDMRData; RingBuffer m_rxP25Data; + RingBuffer m_rxNXDNData; RingBuffer m_rxGrantData; @@ -227,6 +238,8 @@ namespace network /// Writes P25 PDU frame data to the 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 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/network/Network.cpp b/network/Network.cpp index b7ef2963..5f02b30c 100644 --- a/network/Network.cpp +++ b/network/Network.cpp @@ -57,6 +57,7 @@ using namespace network; /// Flag indicating full-duplex operation. /// Flag indicating whether DMR is enabled. /// Flag indicating whether P25 is enabled. +/// Flag indicating whether NXDN is enabled. /// Flag indicating whether DMR slot 1 is enabled for network traffic. /// Flag indicating whether DMR slot 2 is enabled for network traffic. /// Flag indicating that the system activity logs will be sent to the network. @@ -64,7 +65,7 @@ using namespace network; /// Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. /// Flag indicating that the system will handle channel grants from the network. Network::Network(const std::string& address, uint16_t port, uint16_t local, uint32_t id, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool handleChGrants) : + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool handleChGrants) : BaseNetwork(local, id, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, handleChGrants), m_address(address), m_port(port), @@ -72,6 +73,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t local, uint m_enabled(false), m_dmrEnabled(dmr), m_p25Enabled(p25), + m_nxdnEnabled(nxdn), m_updateLookup(updateLookup), m_identity(), m_rxFrequency(0U), @@ -230,6 +232,18 @@ void Network::clock(uint32_t ms) m_rxP25Data.addData(m_buffer, len); } } + else if (::memcmp(m_buffer, TAG_NXDN_DATA, 4U) == 0) { +#if ENABLE_NXDN_SUPPORT + if (m_enabled && m_nxdnEnabled) { + if (m_debug) + Utils::dump(1U, "Network Received, NXDN", m_buffer, length); + + uint8_t len = length; + m_rxNXDNData.addData(&len, 1U); + m_rxNXDNData.addData(m_buffer, len); + } +#endif + } else if (::memcmp(m_buffer, TAG_MASTER_WL_RID, 7U) == 0) { if (m_enabled && m_updateLookup) { if (m_debug) diff --git a/network/Network.h b/network/Network.h index eea96817..d9d0d881 100644 --- a/network/Network.h +++ b/network/Network.h @@ -50,7 +50,7 @@ namespace network public: /// Initializes a new instance of the Network class. Network(const std::string& address, uint16_t port, uint16_t local, uint32_t id, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool slot1, bool slot2, bool transferActivityLog, bool transferDiagnosticLog, bool updateLookup, bool handleChGrants); + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool transferActivityLog, bool transferDiagnosticLog, bool updateLookup, bool handleChGrants); /// Finalizes a instance of the Network class. ~Network(); @@ -86,6 +86,7 @@ namespace network bool m_dmrEnabled; bool m_p25Enabled; + bool m_nxdnEnabled; bool m_updateLookup; diff --git a/nxdn/Audio.cpp b/nxdn/Audio.cpp new file mode 100644 index 00000000..832c8cb0 --- /dev/null +++ b/nxdn/Audio.cpp @@ -0,0 +1,206 @@ +/** +* 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) 2018 by Jonathan Naylor G4KLX +* +* 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/Audio.h" +#include "edac/Golay24128.h" + +using namespace nxdn; +using namespace edac; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the Audio class. +/// +Audio::Audio() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Audio class. +/// +Audio::~Audio() +{ + /* stub */ +} + +/// +/// Decode a NXDN AMBE audio frame. +/// +/// +/// +void Audio::decode(const uint8_t* in, uint8_t* out) const +{ + assert(in != NULL); + assert(out != NULL); + + decode(in + 0U, out, 0U); + decode(in + 9U, out, 49U); + } + +/// +/// Encode a NXDN AMBE audio frame. +/// +/// +/// +void Audio::encode(const uint8_t* in, uint8_t* out) const +{ + assert(in != NULL); + assert(out != NULL); + + encode(in, out + 0U, 0U); + encode(in, out + 9U, 49U); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// +/// +/// +/// +/// +void Audio::decode(const uint8_t* in, uint8_t* out, uint32_t offset) const +{ + assert(in != NULL); + assert(out != NULL); + + uint32_t a = 0U; + uint32_t MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++, MASK >>= 1) { + uint32_t aPos = A_TABLE[i]; + if (READ_BIT(in, aPos)) + a |= MASK; + } + + uint32_t b = 0U; + MASK = 0x400000U; + for (uint32_t i = 0U; i < 23U; i++, MASK >>= 1) { + uint32_t bPos = B_TABLE[i]; + if (READ_BIT(in, bPos)) + b |= MASK; + } + + uint32_t c = 0U; + MASK = 0x1000000U; + for (uint32_t i = 0U; i < 25U; i++, MASK >>= 1) { + uint32_t cPos = C_TABLE[i]; + if (READ_BIT(in, cPos)) + c |= MASK; + } + + a >>= 12; + + // The PRNG + b ^= (PRNG_TABLE[a] >> 1); + b >>= 11; + + MASK = 0x000800U; + for (uint32_t i = 0U; i < 12U; i++, MASK >>= 1) { + uint32_t aPos = i + offset + 0U; + uint32_t bPos = i + offset + 12U; + WRITE_BIT(out, aPos, a & MASK); + WRITE_BIT(out, bPos, b & MASK); + } + + MASK = 0x1000000U; + for (uint32_t i = 0U; i < 25U; i++, MASK >>= 1) { + uint32_t cPos = i + offset + 24U; + WRITE_BIT(out, cPos, c & MASK); + } +} + +/// +/// +/// +/// +/// +/// +void Audio::encode(const uint8_t* in, uint8_t* out, uint32_t offset) const +{ + assert(in != NULL); + assert(out != NULL); + + uint32_t aOrig = 0U; + uint32_t bOrig = 0U; + uint32_t cOrig = 0U; + + uint32_t MASK = 0x000800U; + for (uint32_t i = 0U; i < 12U; i++, MASK >>= 1) { + uint32_t n1 = i + offset + 0U; + uint32_t n2 = i + offset + 12U; + if (READ_BIT(in, n1)) + aOrig |= MASK; + if (READ_BIT(in, n2)) + bOrig |= MASK; + } + + MASK = 0x1000000U; + for (uint32_t i = 0U; i < 25U; i++, MASK >>= 1) { + uint32_t n = i + offset + 24U; + if (READ_BIT(in, n)) + cOrig |= MASK; + } + + uint32_t a = Golay24128::encode24128(aOrig); + + // The PRNG + uint32_t p = PRNG_TABLE[aOrig] >> 1; + + uint32_t b = Golay24128::encode23127(bOrig) >> 1; + b ^= p; + + MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++, MASK >>= 1) { + uint32_t aPos = A_TABLE[i]; + WRITE_BIT(out, aPos, a & MASK); + } + + MASK = 0x400000U; + for (uint32_t i = 0U; i < 23U; i++, MASK >>= 1) { + uint32_t bPos = B_TABLE[i]; + WRITE_BIT(out, bPos, b & MASK); + } + + MASK = 0x1000000U; + for (uint32_t i = 0U; i < 25U; i++, MASK >>= 1) { + uint32_t cPos = C_TABLE[i]; + WRITE_BIT(out, cPos, cOrig & MASK); + } +} diff --git a/nxdn/Audio.h b/nxdn/Audio.h new file mode 100644 index 00000000..b32e1c2a --- /dev/null +++ b/nxdn/Audio.h @@ -0,0 +1,488 @@ +/** +* 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) 2018 by Jonathan Naylor G4KLX +* +* 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_AUDIO_H__) +#define __NXDN_AUDIO_H__ + +#include "Defines.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t PRNG_TABLE[] = { + 0x42CC47U, 0x19D6FEU, 0x304729U, 0x6B2CD0U, 0x60BF47U, 0x39650EU, 0x7354F1U, 0xEACF60U, 0x819C9FU, 0xDE25CEU, + 0xD7B745U, 0x8CC8B8U, 0x8D592BU, 0xF71257U, 0xBCA084U, 0xA5B329U, 0xEE6AFAU, 0xF7D9A7U, 0xBCC21CU, 0x4712D9U, + 0x4F2922U, 0x14FA37U, 0x5D43ECU, 0x564115U, 0x299A92U, 0x20A9EBU, 0x7B707DU, 0x3BE3A4U, 0x20D95BU, 0x6B085AU, + 0x5233A5U, 0x99A474U, 0xC0EDCBU, 0xCB5F12U, 0x918455U, 0xF897ECU, 0xE32E3BU, 0xAA7CC2U, 0xB1E7C9U, 0xFC561DU, + 0xA70DE6U, 0x8DBE73U, 0xD4F608U, 0x57658DU, 0x0E5E56U, 0x458DABU, 0x7E15B8U, 0x376645U, 0x2DFD86U, 0x64EC3BU, + 0x3F1F60U, 0x3481B4U, 0x4DA00FU, 0x067BCEU, 0x1B68B1U, 0xD19328U, 0xCA03FFU, 0xA31856U, 0xF8EB81U, 0xF9F2F8U, + 0xA26067U, 0xA91BB6U, 0xF19A59U, 0x9A6148U, 0x8372B6U, 0xC8E86FU, 0x9399DCU, 0x1A0291U, 0x619142U, 0x6DE9FFU, + 0x367A2CU, 0x7D2511U, 0x6484DAU, 0x2F1F0FU, 0x1E6DB4U, 0x55F6E1U, 0x0EA70AU, 0x061C96U, 0xDD0E45U, 0xB4D738U, + 0xAF64ABU, 0xE47F42U, 0xFDBE9DU, 0xB684ACU, 0xFE5773U, 0xC1E4A2U, 0x8AFD0DU, 0x932ED4U, 0xD814E3U, 0x81853AU, + 0x225EECU, 0x7A6945U, 0x31A112U, 0x2AB2EBU, 0x630974U, 0x785AB5U, 0x11E3CEU, 0x4A715BU, 0x402AA0U, 0x199B7DU, + 0x16C05EU, 0x6F5283U, 0xA4FB10U, 0xBFA8ECU, 0xF633B7U, 0xEC4012U, 0xADD8C9U, 0xD6EB1CU, 0xDD3027U, 0x84A1FAU, + 0xCF9E19U, 0xD64C80U, 0xBC4557U, 0xA7B62EU, 0x6E2DA1U, 0x311F50U, 0x38C68EU, 0x63D5BFU, 0x486E60U, 0x10BFE1U, + 0x5BAD1EU, 0x4A4647U, 0x0157F0U, 0x7ACC29U, 0x73BEEAU, 0x2825D7U, 0xA0940CU, 0xFBCFF9U, 0xB05C62U, 0x892426U, + 0xC6B3DDU, 0xDF3840U, 0x9449B3U, 0xCED3BEU, 0xE7804DU, 0xBC3B90U, 0xF5AA0BU, 0xE6D17EU, 0x2D43B5U, 0x345A04U, + 0x5EA9DBU, 0x07A202U, 0x0C7134U, 0x45C9FDU, 0x5EDA0AU, 0x310193U, 0x6830C4U, 0x62AA3DU, 0x3B59B2U, 0xB04043U, + 0xEB975CU, 0x82BCADU, 0x912E62U, 0xD8F7FBU, 0x82C489U, 0x895F54U, 0xF00FE7U, 0xFBBC2AU, 0xA2E771U, 0xE956C4U, + 0xF6CD1FU, 0x3F8FEAU, 0x0534E1U, 0x4C653CU, 0x17FE8FU, 0x1C4C52U, 0x4515A1U, 0x2E86A9U, 0x3FBD56U, 0x756C87U, + 0x6ED218U, 0x279179U, 0x7C0AA6U, 0xD53B17U, 0x8EE0C8U, 0x85F291U, 0xD94B36U, 0x9298EFU, 0xAB8318U, 0xE07301U, + 0xBB68DFU, 0xB2CB7CU, 0xE910A5U, 0xE101D2U, 0x92BB4BU, 0x59E8B4U, 0x407175U, 0x0B026AU, 0x12989BU, 0x792944U, + 0x2376EDU, 0x2EF5BAU, 0x758663U, 0x7C1ED5U, 0x078D0CU, 0x4EF6ABU, 0x5567F2U, 0x9F7C29U, 0xC68E9CU, 0xC51747U, + 0xBC6422U, 0xB7EFB9U, 0xECFD44U, 0xA50497U, 0xAF178AU, 0xD68C69U, 0xD97DB5U, 0x82670EU, 0xCBB45BU, 0x508D90U, + 0x190A25U, 0x63F0FEU, 0x68E3C7U, 0x317A10U, 0x3A09D9U, 0x6B926EU, 0x004237U, 0x1B79C8U, 0x53EA59U, 0x48B3B7U, + 0x811166U, 0xDE4A79U, 0xF5F988U, 0xAC6057U, 0xE733FEU, 0xFF89ADU, 0xB49830U, 0x8F4BC3U, 0xC6F00EU, 0x9DA135U, + 0x942FE0U, 0xC71C3BU, 0x4DC78FU, 0x3476C4U, 0x7F6C39U, 0x66BFAAU, 0x298657U, 0x725504U, 0x5B4E89U, 0x01FE72U, + 0x0835A3U, 0x53269CU, 0x189D4DU, 0x01CDC2U, 0xEA763BU, 0xF3A56DU, 0xB0BCD4U, 0xE80F13U, 0xE355CAU, 0x98C47DU, + 0x91AB24U, 0xCE38DBU, 0x87A35AU, 0x9CD3A5U, 0xD648F4U, 0xAF7B6FU, 0x24A292U, 0x7D3011U, 0x764B6DU, 0x2DDABEU, + 0x44D123U, 0x5E22D8U, 0x1FB09DU, 0x04A926U, 0x4F5AF3U, 0x064128U, 0x3DB105U, 0x70AAD6U, 0xAA392FU, 0xA1C4B8U, + 0xF8C7C0U, 0xD35D0FU, 0x8A2E9EU, 0xC1B761U, 0xDA44F0U, 0x925E8FU, 0x89CF4EU, 0xE8B4D1U, 0xB32728U, 0xB8FE7FU, + 0x61DCC6U, 0x2A4701U, 0x1614D8U, 0x5DADE2U, 0x46BE37U, 0x0F44DCU, 0x54D549U, 0x5D8E32U, 0x263DAFU, 0x2C237CU, + 0x75E291U, 0xBE5982U, 0xA74A7FU, 0xC493A4U, 0xDFA131U, 0x967A5AU, 0xCCCB8EU, 0xC1D835U, 0x9A02ECU, 0xF331BBU, + 0xE8B812U, 0xA3EBC5U, 0xBA507CU, 0x7080ABU, 0x099BC2U, 0x02285DU, 0x59718CU, 0x50C273U, 0x0B1862U, 0x4A1F8CU, + 0x70A655U, 0x3BF5C2U, 0x666FBBU, 0x6DDE68U, 0x3485C5U, 0x9F161EU, 0xC46F4BU, 0x8CFDF0U, 0x97C625U, 0xDE058EU, + 0xC59CD3U, 0xAEAE20U, 0xF775BCU, 0xFC647FU, 0xBD9F02U, 0xE70C91U, 0xCC1468U, 0x11E7B7U, 0x1AFC36U, 0x435B49U, + 0x080398U, 0x139027U, 0x7B63FEU, 0x607AF9U, 0x29E900U, 0x7293D6U, 0x79026FU, 0x00D930U, 0x0BEAF1U, 0xD3614EU, + 0x90119FU, 0x8B8AE4U, 0xC61969U, 0xBD609AU, 0xB4F247U, 0xEFA954U, 0xE518A9U, 0xBC0362U, 0xD7D0D6U, 0xCE7E8DU, + 0x856F18U, 0x1C94E3U, 0x578726U, 0x0D5F1DU, 0x24ECC0U, 0x7FF713U, 0x3E26AAU, 0x251D6DU, 0x6A8F14U, 0x53648BU, + 0x19757AU, 0x40AEB4U, 0xCB9CA5U, 0x90055AU, 0x9956C3U, 0xE2ED34U, 0xAB3C7DU, 0xB126EAU, 0xFA9513U, 0xA3D2C8U, + 0x886BFDU, 0xD9F836U, 0xD2A2E3U, 0x8D1359U, 0x454804U, 0x5EDBF7U, 0x37637AU, 0x2C3089U, 0x67ABD4U, 0x3E8847U, + 0x3551BAU, 0x4D6331U, 0x46B8C4U, 0x1D299FU, 0x54120EU, 0x5FC0E1U, 0x86D93BU, 0xE56A0EU, 0xFBB1D5U, 0xB2B600U, + 0xA94EABU, 0xE05DF6U, 0x9BE605U, 0x90B798U, 0xC92C6BU, 0xC3DE66U, 0x9AC7BDU, 0xD15448U, 0x6A3FD3U, 0x23ADA3U, + 0x78346CU, 0x7147F5U, 0x2BDC02U, 0x0EAD5BU, 0x553FFCU, 0x1EA425U, 0x07D5F2U, 0x4C4ECBU, 0x554C14U, 0x3EB3F5U, + 0xE4A26AU, 0xED799BU, 0xB6CA85U, 0xFFD25CU, 0xC421BFU, 0x8F3A22U, 0x96AB51U, 0xDC518CU, 0x895217U, 0x8289F2U, + 0xF9B8A9U, 0xF0231CU, 0x2BF1C7U, 0x62C80AU, 0x781B39U, 0x1320E5U, 0x4AB156U, 0x41EB8FU, 0x1848E0U, 0x13D771U, + 0x4886AEU, 0x203C5FU, 0x3B6F40U, 0x76F6A1U, 0xE5457EU, 0xAE1EE7U, 0xD7AC10U, 0xDCB549U, 0x8476EFU, 0x8FC536U, + 0xD49DE9U, 0x9D0ED8U, 0xA63513U, 0xEFE4A6U, 0xB4DF7DU, 0x3E0D00U, 0x779693U, 0x4CA75EU, 0x0568ADU, 0x527BB0U, + 0x59C34BU, 0x00109FU, 0x0A0B14U, 0x73FA61U, 0x38E0BAU, 0x23530FU, 0x6A88D4U, 0xB199DDU, 0x98322AU, 0xC260F3U, + 0xCBF944U, 0x908A0DU, 0xDB11F2U, 0xC28163U, 0xADFABDU, 0xBC694CU, 0xF65243U, 0xAD83BAU, 0xA40D6DU, 0x5F7EF4U, + 0x16E787U, 0x0DF44AU, 0x460EF1U, 0x5E1F24U, 0x15CC3FU, 0x6C77CAU, 0x676401U, 0x3C9CBDU, 0x359FEEU, 0x6A0413U, + 0x02F590U, 0x91EE4DU, 0xDA3C3EU, 0xC305A3U, 0x889658U, 0xF14D99U, 0xFA7F86U, 0xA1E677U, 0xE981E8U, 0xF21A10U, + 0xBB4BD7U, 0x80F1CEU, 0xCB6239U, 0x123BE0U, 0x1D885FU, 0x45921EU, 0x6641E1U, 0x3DE870U, 0x74BBAFU, 0x6F00C6U, + 0x261055U, 0x7DCBA8U, 0x57787AU, 0x0E2167U, 0x05B28CU, 0xCC8819U, 0x975BE2U, 0xBC52B7U, 0xE5E52CU, 0xEB37C9U, + 0xB20E12U, 0xF9DD2FU, 0xE8C6FCU, 0x837701U, 0xD8AD82U, 0xD1BE5AU, 0x0B0525U, 0x0244B4U, 0x79FE5BU, 0x322DCAU, + 0x2B3495U, 0x60876CU, 0x79DCFBU, 0x334C12U, 0x4C7745U, 0x45A4DCU, 0x1E3F23U, 0x175FF2U, 0xC4C0D8U, 0xAFF30DU, + 0xB72AF6U, 0xFCB96BU, 0xA5C338U, 0xAE5295U, 0xF54946U, 0xDCBABBU, 0x87A1A8U, 0xCF2165U, 0xD4DA9EU, 0x9FC90BU, + 0x223070U, 0x6922A4U, 0x30B92FU, 0x3348D6U, 0x695B01U, 0x20C038U, 0x1BB2EFU, 0x523B06U, 0x49EC99U, 0x02D7C8U, + 0x5B4777U, 0x713CA6U, 0xA8AF49U, 0xA3B650U, 0xF84586U, 0xB5DF7FU, 0xAE8CF8U, 0xC72581U, 0x9D3652U, 0x9EEDCFU, + 0xC75D34U, 0xCC0671U, 0xB5B5CAU, 0xFEAC1FU, 0x677EA4U, 0x2DC5F9U, 0x26D63AU, 0x7F1F86U, 0x142855U, 0x0DF2A8U, + 0x42E3B3U, 0x195872U, 0x108B8DU, 0x6AB31CU, 0x632063U, 0x307BAAU, 0xFBC83DU, 0xE201C4U, 0xA91393U, 0x90A82AU, + 0xDAF9E4U, 0x816A55U, 0x88D00AU, 0xD383DBU, 0xFA3A64U, 0xA569A5U, 0xEEE2DEU, 0x76D243U, 0x3D0D90U, 0x649E6DU, + 0x47E76EU, 0x1C7491U, 0x156E49U, 0x4E9DDEU, 0x0604B7U, 0x3D3720U, 0x76FDD9U, 0x6FEC06U, 0x2417B7U, 0xFD04F8U, + 0xF29D29U, 0x886F92U, 0xC1744FU, 0xDAC73CU, 0x939EB1U, 0x880C63U, 0xEBE79EU, 0xB2F285U, 0xB86970U, 0xE11ABBU, + 0xEA822EU, 0x311155U, 0x586AC0U, 0x43F92BU, 0x0A81F6U, 0x5412C5U, 0x5D111CU, 0x26E8CBU, 0x2D7B63U, 0x74213CU, + 0x3F90CDU, 0x2E8B52U, 0x645883U, 0xDFE36CU, 0x96F375U, 0xDD0882U, 0xC40B1BU, 0x8FD6CCU, 0xB464A5U, 0xFC7F3EU, + 0xA7AECBU, 0xAA9511U, 0xF10634U, 0xBA5CEFU, 0x83ED32U, 0x483681U, 0x5015DCU, 0x138D3FU, 0x48DEA2U, 0x616571U, + 0x3AF40CU, 0x33AF97U, 0x681D72U, 0x2246E9U, 0x3BD7B9U, 0x506C46U, 0x0D2FDFU, 0x869338U, 0xDDC061U, 0xD45BD6U, + 0xAF6A0FU, 0xE7B8C0U, 0xFC2371U, 0xBF102EU, 0xA6C9DFU, 0xEDDA40U, 0x943089U, 0x9FA1BFU, 0x459A66U, 0x0C4995U, + 0x175108U, 0x7AE243U, 0x6139B6U, 0x2A2A2DU, 0x73D3D8U, 0x79C183U, 0x204A26U, 0x0B3FFDU, 0x5AA420U, 0x111613U, + 0x8A4FDFU, 0xC3DC2CU, 0xF9A7B5U, 0xB034EAU, 0xEBAC5BU, 0xE0CF94U, 0xBD5465U, 0xF605FAU, 0xCFBEA3U, 0x85AC54U, + 0x9E55DDU, 0xD7C62AU, 0x0CDD73U, 0x252FCDU, 0x76361CU, 0x7DF5D3U, 0x3546E2U, 0x6E5B39U, 0x67A98CU, 0x1CB247U, + 0x57231AU, 0x4AD8A9U, 0x01CA74U, 0x191187U, 0xF2208AU, 0xA9AB50U, 0xA0F8A5U, 0xFB403EU, 0xF2D34BU, 0xA9A880U, + 0xCB393DU, 0xD262EEU, 0x99D0B7U, 0xC04B00U, 0xCB1AC9U, 0xB0B176U, 0x39E3A7U, 0x677EF8U, 0x2ECD58U, 0x359687U, + 0x7E277EU, 0x473D69U, 0x0CEEB0U, 0x55D557U, 0x5F04CEU, 0x0C8EBDU, 0x25BD60U, 0x7E64DBU, 0xB7771EU, 0xACCC05U, + 0xE51CF0U, 0xBF2F2AU, 0x90F497U, 0xC9E7D4U, 0xC25F09U, 0x9B9CBAU, 0xD08767U, 0xEB320CU, 0xA36999U, 0x38FB42U, + 0x7180B3U, 0x22112CU, 0x29AA45U, 0x50F9D2U, 0x1B610AU, 0x0202FDU, 0x4899E4U, 0x57080BU, 0x3E72DAU, 0x65E165U, + 0x6CFA34U, 0xB70BEBU, 0xBC104AU, 0xE4E295U, 0x8F7BECU, 0x96787FU, 0xD583B2U, 0x9E9740U, 0x870C5DU, 0xECFFA6U, + 0xF4E433U, 0xBF35F8U, 0xE00F8DU, 0x699C16U, 0x3265EBU, 0x1B6638U, 0x40F515U, 0x0A8DC6U, 0x131E1BU, 0x5845A0U, + 0x21F670U, 0x2A6E1FU, 0x791D8EU, 0x708651U, 0x2AD7E8U, 0xE37CAFU, 0xD8EE56U, 0x97B3C1U, 0x8E0018U, 0xC51B6FU, + 0x9CC9E6U, 0xB67019U, 0xEF23C8U, 0xE498F2U, 0xBF9927U, 0xF643ECU, 0xCD7051U, 0x04E902U, 0x563AFFU, 0x5D006CU, + 0x04D3A1U, 0x0FCA9AU, 0x72794FU, 0x39A2B4U, 0x228231U, 0x6A19EAU, 0x714E96U, 0x18F705U, 0x4324FCU, 0xC83E3BU, + 0x918D02U, 0xDADCD5U, 0xC2470CU, 0xA135B3U, 0xBABCF2U, 0xF30F4DU, 0xA8549EU, 0xA1C543U, 0xDEFF78U, 0xD42CBCU, + 0x0DB747U, 0x46C6D2U, 0x5F5C89U, 0x144F60U, 0x6FA6F7U, 0x66350EU, 0x2C0A59U, 0x35DAE0U, 0x7EC12FU, 0x0D32FEU, + 0x0429C1U, 0x5FB911U, 0xD642AEU, 0x895167U, 0xC3D8B0U, 0xFAAB89U, 0xB1315AU, 0xA8C0A7U, 0xE3DB24U, 0xB84879U, + 0x913382U, 0xCBA317U, 0x82F8FCU, 0x994BA9U, 0x50C213U, 0x4390CEU, 0x282F5DU, 0x713E30U, 0x7FCDE3U, 0x26565EU, + 0x2D0485U, 0x56BDD4U, 0x1FAE7BU, 0x0475AAU, 0x4DD555U, 0x17CE4CU, 0x9C1D9BU, 0xE52473U, 0xEEF7E4U, 0xB7CD1DU, + 0xF45E42U, 0xEF87E3U, 0x87B43CU, 0x986FADU, 0xD16FD2U, 0x8AD403U, 0x8103A8U, 0xD83A75U, 0x33A826U, 0x2BF39BU, + 0x604049U, 0x7B99A4U, 0x328ABFU, 0x49306AU, 0x407191U, 0x1BEA04U, 0x19D96FU, 0x4001F2U, 0x0FB201U, 0x36E9DCU, + 0xFD7ADFU, 0xE64326U, 0xAF91F9U, 0xF51249U, 0xDC2B16U, 0x87F8D7U, 0xCCE668U, 0xC517B1U, 0x9E8C46U, 0x97BF5FU, + 0xED6498U, 0xA67461U, 0x378FF6U, 0x788C8FU, 0x611514U, 0x0AE6F1U, 0x53FC2BU, 0x596F3EU, 0x0216C5U, 0x4B8508U, + 0x507FBBU, 0x396EE6U, 0x22F535U, 0xE99688U, 0xB10F43U, 0xBA1D36U, 0xC3E2ADU, 0xC07178U, 0x9B28C3U, 0xD69A8BU, + 0xCD817CU, 0x8570E5U, 0xFEEB12U, 0xF5E8CBU, 0xAC10C4U, 0x270335U, 0x7ED8EAU, 0x156B5BU, 0x0E7A14U, 0x46A0C5U, + 0x5D937AU, 0x144AA3U, 0x4F79D5U, 0x6CF35CU, 0x31228FU, 0x7A1932U, 0x628E69U, 0xA9D59CU, 0x926517U, 0xDBBEE2U, + 0x80ADB9U, 0x891424U, 0xD246D7U, 0xD8ED1AU, 0xA17C28U, 0xEA27F5U, 0xF3942EU, 0xB8CE8FU, 0xAB5FD0U, 0x466461U, + 0x1CB7BEU, 0x152F6FU, 0x4E1CC0U, 0x05D799U, 0x1CE66EU, 0x773DF7U, 0x7EAB00U, 0x249048U, 0x6D41D7U, 0x765A26U, + 0x1DA9F9U, 0x8431C8U, 0xCF0203U, 0x96C1DEU, 0x90D86DU, 0xCB6A30U, 0xA23193U, 0xB9A24EU, 0xF05B95U, 0xEB48A0U, + 0xA0D27AU, 0xD8A39FU, 0xD33804U, 0x0A9B79U, 0x01C3AAU, 0x5A5437U, 0x132FD4U, 0x28BC0DU, 0x60253AU, 0x3F57E3U, + 0x3CCC7CU, 0x65DD9DU, 0x4E26C2U, 0x172572U, 0xDCDDADU, 0xC64E64U, 0x8F5553U, 0x94A68AU, 0xFDBE7DU, 0xA66DE4U, + 0xADD68BU, 0xF4C75AU, 0xFE0CC1U, 0x873E34U, 0xC8A72FU, 0xDBD0C2U, 0x124B10U, 0x49998DU, 0x40A8FEU, 0x3A3323U, + 0x316088U, 0x68D95DU, 0x235B06U, 0x3A00B3U, 0x51B178U, 0x4AEA89U, 0x025816U, 0x59C36FU, 0xD092B8U, 0x8B2930U, + 0xE43AC7U, 0xF5E2DEU, 0xBEC121U, 0xA71AF0U, 0xED8B7FU, 0x94B40EU, 0x9F66D1U, 0xD45D68U, 0xCD8CBFU, 0x8617F6U, + 0x5F2545U, 0x75FC98U, 0x2EFF62U, 0x674467U, 0x7C959CU, 0x318F09U, 0x0A7CD2U, 0x4967AFU, 0x11D62CU, 0x1A8CD1U, + 0x431F02U, 0x48A69DU, 0xB3E5ECU, 0xFA7623U, 0xE10E9AU, 0xA99948U, 0xB20215U, 0xD971A6U, 0x80E86BU, 0x8BDA90U, + 0xD60185U, 0x9D907EU, 0x8FFBFBU, 0xE66920U, 0x7D705DU, 0x3483CEU, 0x6F9833U, 0x646BF1U, 0x1DF3E8U, 0x17E017U, + 0x4E1BC6U, 0x050A79U, 0x1E8038U, 0x5773E7U, 0x2C685EU, 0xA1BD89U, 0xFB86B0U, 0xF01477U, 0xA16D8EU, 0xCAFE19U, + 0xD365C1U, 0x9815AEU, 0x839E3FU, 0xCBCDC4U, 0x907611U, 0xB9E70AU, 0xE2BDE7U, 0x2B0E34U, 0x301789U, 0x7BE4DAU, + 0x477707U, 0x0C2FACU, 0x558C79U, 0x5E9743U, 0x0D4496U, 0x04786DU, 0x7FABE0U, 0x3730B3U, 0x3C014AU, 0xE7DADDU, + 0xEEE834U, 0x956163U, 0xDCB2FAU, 0xC78905U, 0x8D5BD4U, 0xD0427BU, 0xDBF12BU, 0xA22AB4U, 0xA93B4DU, 0xFA819AU, + 0xB3D2B3U, 0x287B64U, 0x40289DU, 0x5BB206U, 0x100153U, 0x495CB8U, 0x42CF2DU, 0x3BF4D6U, 0x70248BU, 0x6ABF19U, + 0x23CCF4U, 0x3C4527U, 0x75761AU, 0x8EACC1U, 0x853F44U, 0xD44EBFU, 0xDED5EEU, 0x87C751U, 0xEC3E80U, 0xF72D6FU, + 0xBEB676U, 0xE557A1U, 0xEC4D59U, 0xB6BECEU, 0x9DA527U, 0x443078U, 0x0BCAE9U, 0x12D916U, 0x594087U, 0x6033E8U, + 0x22A831U, 0x7948A2U, 0x70535FU, 0x2BC01CU, 0x62BBA1U, 0x592A7BU, 0x92308EU, 0x8AC395U, 0xC15A50U, 0x9809ABU, + 0xB3B336U, 0xECB245U, 0xE54998U, 0xBEDA1BU, 0xF681E6U, 0xED35F5U, 0x8E2E0CU, 0x87FDD3U, 0x5CC453U, 0x1556ACU, + 0x0E85FDU, 0x64AC42U, 0x3D7F8BU, 0x36447CU, 0x6FD665U, 0x640FB2U, 0x3B3C4BU, 0x52A7C4U, 0x48F7B5U, 0x014C2EU, + 0x9A9FFBU, 0xD19601U, 0xA0250CU, 0xAB7FFFU, 0xF2C822U, 0xB8D1B1U, 0xA302CCU, 0xEAB907U, 0xD1E9B2U, 0x987269U, + 0xC3411CU, 0xCC8897U, 0x141A42U, 0x3F61B8U, 0x66F2A1U, 0x2DCB56U, 0x3618DFU, 0x778208U, 0x2CB3F1U, 0x0468EEU, + 0x5F7B1FU, 0x5693D0U, 0x0D8041U, 0x461B3EU, 0xFFECE7U, 0xB4FD50U, 0xA94798U, 0xE314CFU, 0xB88D76U, 0xB17EADU, + 0xCA7508U, 0xC3E553U, 0x989EA6U, 0xDB0D3DU, 0xC396E8U, 0xA8E683U, 0x717D1EU, 0x7A0EEDU, 0x219730U, 0x288422U, + 0x736ECFU, 0x1BFF14U, 0x04A4A1U, 0x4F177AU, 0x56092BU, 0x1DD884U, 0x64635DU, 0xEF70EAU, 0xA589B3U, 0xF49B54U, + 0xFF50CDU, 0xA66312U, 0x8DFA62U, 0xD628FDU, 0x9F131CU, 0x8582C3U, 0xCCF9DAU, 0xF36A29U, 0xB8B2F4U, 0x618157U, + 0x6A020AU, 0x335999U, 0x79E864U, 0x4272BFU, 0x03259AU, 0x189C40U, 0x51CFB5U, 0x0A752EU, 0x216463U, 0x79BF90U, + 0x721C0DU, 0xAB47FEU, 0xE4D727U, 0xFDEC28U, 0x963FD9U, 0x8DA646U, 0xC594B7U, 0x9E4FE8U, 0x977E60U, 0xECA597U, + 0xAF264EU, 0xB61C79U, 0xFDCDA0U, 0x65D64FU, 0x2E61DCU, 0x553881U, 0x5CAA72U, 0x0351FBU, 0x0A400CU, 0x51FB55U, + 0x3BB9CAU, 0x22223AU, 0x6993B5U, 0x30C8C4U, 0x3B5B1BU, 0xE02B82U, 0xC1B075U, 0x9B23BCU, 0xD25A8BU, 0xC9C852U, + 0x82A3A9U, 0xBB303CU, 0xF42977U, 0xADDA82U, 0xA64418U, 0xFC55E5U, 0xB5AEE6U, 0x0EBD3BU, 0x4765C8U, 0x4CD655U, + 0x17DD2EU, 0x562EEBU, 0x6C3770U, 0x25A585U, 0x3E5EDEU, 0x754F6FU, 0x2C94A1U, 0x23A758U, 0x5A3F4FU, 0xD07C96U, + 0x8BC761U, 0xC254E8U, 0xD92C97U, 0xB0BF06U, 0xEBE0D9U, 0xE25138U, 0xB8CAA7U, 0xBB98DEU, 0xE22109U, 0x896291U, + 0x10F172U, 0x5BCB2FU, 0x401A94U, 0x0CA141U, 0x77B2BAU, 0x7E6BBFU, 0x255964U, 0x6E82D9U, 0x77130AU, 0x3C3877U, + 0x04EAF4U, 0x4FD129U, 0x9C40DBU, 0x959BC6U, 0xCEAC2DU, 0xE774FCU, 0xBC6763U, 0xF6DC12U, 0xEB8DCDU, 0xA00664U, + 0xF9F4B3U, 0xD2EF4AU, 0x895E5DU, 0x800584U, 0x5A972BU, 0x132EFBU, 0x287D84U, 0x63E615U, 0x7297CEU, 0x391D23U, + 0x608E30U, 0x6AF5CDU, 0x11641EU, 0x5C5E93U, 0x4789E0U, 0x0E903DU, 0x956386U, 0xFEF053U, 0xB6E879U, 0xAD0BACU, + 0xE41077U, 0xFF83CAU, 0xB47A99U, 0xCD6870U, 0xCE93E7U, 0x96823EU, 0x9D1941U, 0xC4EBD0U, 0x2BF23FU, 0x3031EEU, + 0x790A71U, 0x229909U, 0x2AC1CEU, 0x717677U, 0x5AEDA0U, 0x039C99U, 0x480646U, 0x515587U, 0x1AEC3CU, 0x296F69U, + 0xE13492U, 0xBA8607U, 0xB39FCCU, 0xEC4CB1U, 0xA77723U, 0x9EA7DEU, 0xD51C0DU, 0xCD0F00U, 0x86D4FBU, 0xDDF56EU, + 0xF46F95U, 0x2FBCD4U, 0x268D6BU, 0x7D52B2U, 0x374165U, 0x26F9DCU, 0x4D2A9BU, 0x141163U, 0x1FD2FCU, 0x40CA2DU, + 0x497952U, 0x3322D3U, 0x7AB32CU, 0xE108F5U, 0xAA5AE2U, 0xB3E31BU, 0xF8B098U, 0x812B65U, 0x8B8936U, 0xD0D08AU, + 0xD94341U, 0x8A7894U, 0xE3A9AFU, 0xF8377AU, 0xB74481U, 0x6FDD0CU, 0x64EE5FU, 0x3D35A2U, 0x163731U, 0x5F8ECCU, + 0x045DC7U, 0x0F4616U, 0x57B6E8U, 0x7CAD79U, 0x253E86U, 0x6EC7CFU, 0x7DD478U, 0xB426A1U, 0xCF2D76U, 0xC3BC5FU, + 0x984780U, 0x935571U, 0xCACCEEU, 0x81BBBFU, 0xB82054U, 0xF371C0U, 0xE9CB3BU, 0xA05826U, 0xFB33F5U, 0x52A218U, + 0x09B88BU, 0x424BF6U, 0x53D22DU, 0x198198U, 0x043A53U, 0x6F2A06U, 0x34F1BDU, 0x3DC260U, 0x664982U, 0x6FB81BU, + 0x15A24CU, 0xDE71F5U, 0xC7482AU, 0x8CDFCBU, 0x9505D4U, 0xDE3405U, 0xA5EFFAU, 0xA4FC63U, 0xFE5704U, 0xB387DDU, + 0xA8BC6AU, 0xC32FB2U, 0x5A7EE5U, 0x11C44CU, 0x489797U, 0x420E62U, 0x19BD79U, 0x30E6BCU, 0x6B6407U, 0x225DDAU, + 0x398EA9U, 0x703534U, 0x0A64F7U, 0x09FA0AU, 0xD4C910U, 0xDF10E5U, 0x86833EU, 0xCDB99BU, 0xE67A40U, 0xBE631BU, + 0xB590AEU, 0xEC8B75U, 0xA73BD0U, 0x9CE08BU, 0xD5F35EU, 0x8E0AE5U, 0x061828U, 0x5D835AU, 0x5660C7U, 0x277914U, + 0x68CAE9U, 0x7190E2U, 0x3A0113U, 0x20FECCU, 0x49ED7DU, 0x127522U, 0x1B06ABU, 0x40855CU, 0x8B9E85U, 0x926FB2U, + 0xF8F56AU, 0xE186A5U, 0xAA1F14U, 0xF10CCBU, 0xF0F7BAU, 0x8F6735U, 0x867CECU, 0xDC9F1FU, 0x978402U, 0x8E54F1U, + 0x45EF3CU, 0x7CFC8FU, 0x3705D2U, 0x6C1248U, 0x64C8BDU, 0x3FF976U, 0x566243U, 0x4DA198U, 0x069B45U, 0x1F0AF6U, + 0x5851BBU, 0x00E248U, 0xAB3BD1U, 0xF2090EU, 0xF9926FU, 0xA2C3F1U, 0xEB7800U, 0xD07B9FU, 0x98A1E6U, 0xC31021U, + 0xC84BB8U, 0x91D84FU, 0x9AEC96U, 0x6337A9U, 0x288468U, 0x369FB3U, 0x774E06U, 0x6C645DU, 0x05B7A9U, 0x4E2E22U, + 0x551DFFU, 0x1CC78CU, 0x47D611U, 0x4F2DF2U, 0x343E6FU, 0xBF8514U, 0xE655C1U, 0xAD5E5AU, 0xB4EDBFU, 0xDFB4E4U, + 0xC1265DU, 0x80DD8BU, 0xDBC852U, 0xD25375U, 0x8920ACU, 0xA2BA53U, 0xFB0BC2U, 0x31401DU, 0x28D33CU, 0x63AAE3U, + 0x18381AU, 0x11238DU, 0x4AD2E4U, 0x434933U, 0x195BABU, 0x56A058U, 0x6FB105U, 0x2C5AAEU, 0x35C97BU, 0xFED9A0U, + 0xA52295U, 0x8D314EU, 0xD6ECA3U, 0x9F5E30U, 0x84456DU, 0xCFB6DEU, 0xD6AF03U, 0xBD2CE9U, 0xE556FCU, 0xEEC707U, + 0xB71CD6U, 0x382F59U, 0x43B720U, 0x02E4F7U, 0x195F4EU, 0x51CC99U, 0x0AA550U, 0x013767U, 0x786CBEU, 0x73DD01U, + 0x2AC6D1U, 0x61159EU, 0x7BA92FU, 0x92BAF4U, 0x896109U, 0xC0521AU, 0x9F9AF7U, 0x942924U, 0xC532B9U, 0xEFE3C2U, + 0xA6D807U, 0xFD0ABCU, 0xF69369U, 0xAFA033U, 0x44738EU, 0x5D694DU, 0x17C8F0U, 0x0C93A3U, 0x45207AU, 0x1EF9C5U, + 0x37EB04U, 0x6850FBU, 0x6305EAU, 0x3B9E15U, 0x782DC4U, 0x41774BU, 0x8AF633U, 0xD18DE4U, 0xD81E5DU, 0x83A69AU, + 0x8AF583U, 0xF06E7CU, 0xBB5FADU, 0xA28416U, 0xE99653U, 0xF06D88U, 0x9FEC35U, 0xC4F7E6U, 0x4C059AU, 0x1F1C19U, + 0x56EFC4U, 0x4D743FU, 0x24612AU, 0x3F9BD1U, 0x748814U, 0x2C13AFU, 0x27F276U, 0x5EE861U, 0x553B88U, 0x0E0A5FU, + 0xC791E6U, 0xD8E2B0U, 0x907A69U, 0xABE9C6U, 0xE09217U, 0xB10168U, 0xBA48F9U, 0xE3FA26U, 0x8861CFU, 0x9230D8U, + 0xDB8B21U, 0xC099B2U, 0x09644FU, 0x52F704U, 0x79AC90U, 0x201F6BU, 0x2E17BEU, 0x77C495U, 0x3CFF48U, 0x172E9BU, + 0x4E9426U, 0x0D8775U, 0x145E98U, 0x5E6D03U, 0xC5F6D6U, 0xAC242DU, 0xF70D3CU, 0xFEDED2U, 0xA5C543U, 0xAE74BCU, + 0xD62EE5U, 0x9D9D72U, 0x80029BU, 0xCB534CU, 0x90E175U, 0x19BAAAU, 0x6A3B6BU, 0x6280D4U, 0x39D385U, 0x724B7AU, + 0x6B78E2U, 0x00A321U, 0x19101CU, 0x5248CFU, 0x0ADB30U, 0x01F0A9U, 0x5A21CEU, 0xB73A17U, 0xACC880U, 0xE55179U, + 0xFE42A6U, 0xB4B987U, 0xC5AF58U, 0xCE1688U, 0x97C533U, 0x9CCE76U, 0xC73F8DU, 0x8E2510U, 0xB4B6C3U, 0x7D4FFEU, + 0x665C3DU, 0x2DC7C0U, 0x70B55BU, 0x5B2C2EU, 0x025FF5U, 0x49D470U, 0x53448AU, 0x1A3FD7U, 0x09AC64U, 0x60BDBDU, + 0x3B467AU, 0xB0D043U, 0xE98B9CU, 0xE33A2DU, 0x9A21E2U, 0xD1C3B3U, 0xCA5A0CU, 0x8709DDU, 0xDCB222U, 0xF5A3AAU, + 0xBF79DDU, 0xA44A04U, 0xEDD193U, 0x3E006AU, 0x373B21U, 0x4CF994U, 0x47C04FU, 0x1F53DAU, 0x5488A1U, 0x4DB86CU, + 0x2623DFU, 0x7D7402U, 0x70CF50U, 0x2B9EFDU, 0x232426U, 0xF8A7D3U, 0x91FEC8U, 0x8A4D39U, 0xC117F6U, 0xD0866FU, + 0x9B3D18U, 0xE36EC1U, 0xE8F576U, 0xB3C5BFU, 0xBA1629U, 0xE1BD50U, 0xA8EC8FU, 0x17763EU, 0x5D45F1U, 0x049CA0U, + 0x0F8F1FU, 0x5630C6U, 0x7DE225U, 0x26FB38U, 0x6F08CBU, 0x7D0316U, 0x34B28DU, 0x2F68E9U, 0xC47B72U, 0x9DC287U, + 0x96915CU, 0xCF0B41U, 0x85F8A2U, 0xBAE17FU, 0xF372CCU, 0xE81991U, 0xA1894AU, 0xFAF2EBU, 0xF16134U, 0x89F845U, + 0x0A8ADBU, 0x53153AU, 0x1806E5U, 0x03FF7CU, 0x6A7C0BU, 0x312692U, 0x399775U, 0x628CACU, 0x6D7FB3U, 0x34EE42U, + 0x5FF49DU, 0x56073CU, 0x8D1C67U, 0x87CDBBU, 0xDEE708U, 0xB574D5U, 0xA4ADB6U, 0xEF9E2BU, 0xF605D0U, 0xBD7545U, + 0xE6EE0EU, 0xCE39FBU, 0x950260U, 0xD8929DU, 0x43D9CEU, 0x086A47U, 0x31B3B1U, 0x7AA068U, 0x221ADFU, 0x294B86U, + 0x72F049U, 0x73E3F8U, 0x083927U, 0x418856U, 0x5AC3C9U, 0x105020U, 0xC969B7U, 0xE2BBEEU, 0xBF2019U, 0xB41181U, + 0xEFCA6AU, 0xA6FD3FU, 0xBC27A4U, 0xD53651U, 0xCE9D9AU, 0x854EA7U, 0xDC5E74U, 0xDFE5A9U, 0x26B61AU, 0x6C0D57U, + 0x77DCECU, 0x3EC639U, 0x2575C3U, 0x682CD6U, 0x13AF1DU, 0x1855ECU, 0x404473U, 0x4BDF8AU, 0x12ACDDU, 0xF93754U, + 0xE207A3U, 0xABD87AU, 0xF04B45U, 0xF03284U, 0xABB05BU, 0x80ABEBU, 0xD95AB4U, 0x92C10DU, 0x8FD2CEU, 0xC42833U, + 0xEC3920U, 0x37C2FDU, 0x7C5106U, 0x654883U, 0x2EAAF8U, 0x37B12DU, 0x5C20B6U, 0x065B42U, 0x07C909U, 0x5C12B4U, + 0x152367U, 0x2EB4FAU, 0x65CF19U, 0xFC5F40U, 0xB294FFU, 0xEBA72EU, 0xE03ED1U, 0x9B6CD0U, 0x92D70FU, 0xC944F6U, + 0x801D60U, 0x9AAE19U, 0xF1F4DEU, 0xA85547U, 0xAB4EB8U, 0x729DE9U, 0x792456U, 0x223697U, 0x4BED0CU, 0x55DE71U, + 0x1C03A2U, 0x07910FU, 0x4CAADCU, 0x356BA0U, 0x3E5033U, 0x67C3EEU, 0x2D9B05U, 0xB62810U, 0xFFF3EBU, 0xC4E03EU, + 0x8558A5U, 0xDE0B48U, 0xD5905BU, 0x8D71A2U, 0xA26A75U, 0xFBD8ECU, 0xB08982U, 0xAB1253U, 0xE2A1ECU, 0x79FB3FU, + 0x116E52U, 0x4A15C9U, 0x43861CU, 0x188FE7U, 0x537DF2U, 0x62E619U, 0x29D7C0U, 0x310C57U, 0x7A1F2EU, 0x25E5B8U, + 0xAC7451U, 0xC76F86U, 0xDE9C9FU, 0x959460U, 0xCF27B1U, 0xC6FC1EU, 0xBDEDCFU, 0xF416B0U, 0xEF0429U, 0xA49FEEU, + 0xBDEA17U, 0xFF7104U, 0x06A3F8U, 0x0D8A63U, 0x5219A6U, 0x5B62DDU, 0x00F348U, 0x6969B3U, 0x731A6EU, 0x38816DU, + 0x61D090U, 0x6A6343U, 0x33F9FEU, 0x18B8A5U, 0xC30340U, 0x8B10DAU, 0x98E80BU, 0xD1FB74U, 0xEA20F5U, 0xA5930AU, + 0xFC8E93U, 0xF75CC4U, 0xAF673DU, 0xA4E6BAU, 0xDF3D43U, 0x960F9CU, 0x0DD68DU, 0x44E572U, 0x1F7EB2U, 0x35AD09U, + 0x6C9554U, 0x6746A7U, 0x365D3AU, 0x7DFCF9U, 0x64A6C4U, 0x0B351FU, 0x118CEAU, 0x58DF61U, 0x836434U, 0x8A36CFU, + 0xF1AB5BU, 0xBA18A0U, 0xA343EDU, 0xE8C27EU, 0xF0F887U, 0xBB2B50U, 0xC03A69U, 0xC9C1A6U, 0x9A5317U, 0x9368C8U, + 0x5CB919U, 0x26A226U, 0x2F01EFU, 0x74D919U, 0x3DCA80U, 0x2631D7U, 0x6D223EU, 0x54BAA1U, 0x1E4950U, 0x47520BU, + 0x4CA79EU, 0x97BC75U, 0xBE3EA8U, 0xED479BU, 0xA4D446U, 0xBA4FF5U, 0xF13C39U, 0xE8A46AU, 0x83D7D7U, 0xDA4C0CU, + 0xD1DDF9U, 0x8AA7F2U, 0xC22427U, 0x793DDCU, 0x30CE45U, 0x2B5522U, 0x6007FBU, 0x39BE6CU, 0x32AD95U, 0x42560BU, + 0x4D426AU, 0x16D1B5U, 0x5F3A04U, 0x442BDBU, 0x2DF082U, 0xF6C225U, 0xFE59FCU, 0xA5880FU, 0xAEB312U, 0xF761C9U, + 0x9C582CU, 0x85CBB7U, 0xCE00C3U, 0xD43118U, 0x9DAB9DU, 0xEAF866U, 0xE3437BU, 0x381288U, 0x738955U, 0x6A3BF6U, + 0x2066ABU, 0x19D570U, 0x52DEC1U, 0x090E1EU, 0x00B5FFU, 0x5BE6E1U, 0x727D38U, 0x284CCFU, 0x639656U, 0xFA8531U, + 0xBD3CA8U, 0xD4EF77U, 0xCFC586U, 0x841489U, 0x9C0F78U, 0xD7BCA7U, 0x8E671EU, 0xA5774DU, 0xFE8481U, 0xF79F32U, + 0xAC0AEFU, 0x65F09CU, 0x5FF301U, 0x144ACAU, 0x0D193FU, 0x468224U, 0x13F0D1U, 0x18694AU, 0x63FA87U, 0x2B81F4U, + 0x30106DU, 0x790A9BU, 0xE2E952U, 0x8970CDU, 0xD003BCU, 0xDB9963U, 0x838AD2U, 0x88731DU, 0xD1E064U, 0xBAFFF3U, + 0xA10F2AU, 0xEC049DU, 0xBFD7D4U, 0xB7EE2BU, 0x4C7CBBU, 0x478760U, 0x1E9415U, 0x554D9EU, 0x4C7E6BU, 0x07E4B0U, + 0x3D35ADU, 0x741E4EU, 0x2F8D93U, 0x26FC20U, 0x7D667DU, 0x16B586U, 0x8B8E02U, 0xC91FD9U, 0xD0456CU, 0x9BF237U, + 0xC0EBCEU, 0xE92849U, 0xB29390U, 0xBBC3E7U, 0xE1787EU, 0xAA6B81U, 0x93B040U, 0xD8005FU, 0x411BAEU, 0x0AC870U, + 0x51F1D1U, 0x5D328EU, 0x362837U, 0x6799E0U, 0x6C4239U, 0x37711AU, 0x3EABC7U, 0x45BA3CU, 0x0D01A9U, 0x16D6F2U, + 0xDDCF17U, 0xC46D8CU, 0x8F3670U, 0xF6A723U, 0xFD5CBCU, 0xA74F5DU, 0xEAF582U, 0xF1A43BU, 0x903768U, 0x8B0CC5U, + 0xC0DC16U, 0x9957CBU, 0x1324F0U, 0x4ABD25U, 0x61AECEU, 0x38545AU, 0x73C701U, 0x68FEF4U, 0x212D6FU, 0x5B3382U, + 0x52C2D1U, 0x09494CU, 0x065ABFU, 0xDFA126U, 0x9CB149U, 0xA56A98U, 0xEE5927U, 0xF4C0F6U, 0xBD33B8U, 0xE62901U, + 0xCFB8D6U, 0x94D32FU, 0x9F40B8U, 0xC69AF1U, 0x8CAB0EU, 0x15309FU, 0x7E6360U, 0x21DA31U, 0x2848BAU, 0x733747U, + 0x72A6D4U, 0x08EDA8U, 0x435F7BU, 0x5A4CD6U, 0x119505U, 0x082658U, 0x433DE3U, 0xB8ED26U, 0xB0D6DDU, 0xEB05C8U, + 0xA2BC13U, 0xA9BEEAU, 0xD6656DU, 0xDF5614U, 0x848F82U, 0xC41C5BU, 0xDF26A4U, 0x94F7A5U, 0xADCC5AU, 0x665B8BU, + 0x3F1234U, 0x34A0EDU, 0x6E7BAAU, 0x076813U, 0x1CD1C4U, 0x55833DU, 0x4E1836U, 0x03A9E2U, 0x58F219U, 0x72418CU, + 0x2B09F7U, 0xA89A72U, 0xF1A1A9U, 0xBA7254U, 0x81EA47U, 0xC899BAU, 0xD20279U, 0x9B13C4U, 0xC0E09FU, 0xCB7E4BU, + 0xB25FF0U, 0xF98431U, 0xE4974EU, 0x2E6CD7U, 0x35FC00U, 0x5CE7A9U, 0x07147EU, 0x060D07U, 0x5D9F98U, 0x56E449U, + 0x0E65A6U, 0x659EB7U, 0x7C8D49U, 0x371790U, 0x6C6623U, 0xE5FD6EU, 0x9E6EBDU, 0x921600U, 0xC985D3U, 0x82DAEEU, + 0x9B7B25U, 0xD0E0F0U, 0xE1924BU, 0xAA091EU, 0xF158F5U, 0xF9E369U, 0x22F1BAU, 0x4B28C7U, 0x509B54U, 0x1B80BDU, + 0x024162U, 0x497B53U, 0x01A88CU, 0x3E1B5DU, 0x7502F2U, 0x6CD12BU, 0x27EB1CU, 0x7E7AC5U, 0xDDA113U, 0x8596BAU, + 0xCE5EEDU, 0xD54D14U, 0x9CF68BU, 0x87A54AU, 0xEE1C31U, 0xB58EA4U, 0xBFD55FU, 0xE66482U, 0xE93FA1U, 0x90AD7CU, + 0x5B04EFU, 0x405713U, 0x09CC48U, 0x13BFEDU, 0x522736U, 0x2914E3U, 0x22CFD8U, 0x7B5E05U, 0x3061E6U, 0x29B37FU, + 0x43BAA8U, 0x5849D1U, 0x91D25EU, 0xCEE0AFU, 0xC73971U, 0x9C2A40U, 0xB7919FU, 0xEF401EU, 0xA452E1U, 0xB5B9B8U, + 0xFEA80FU, 0x8533D6U, 0x8C4115U, 0xD7DA28U, 0x5F6BF3U, 0x043006U, 0x4FA39DU, 0x76DBD9U, 0x394C22U, 0x20C7BFU, + 0x6BB64CU, 0x312C41U, 0x187FB2U, 0x43C46FU, 0x0A55F4U, 0x192E81U, 0xD2BC4AU, 0xCBA5FBU, 0xA15624U, 0xF85DFDU, + 0xF38ECBU, 0xBA3602U, 0xA125F5U, 0xCEFE6CU, 0x97CF3BU, 0x9D55C2U, 0xC4A64DU, 0x4FBFBCU, 0x1468A3U, 0x7D4352U, + 0x6ED19DU, 0x270804U, 0x7D3B76U, 0x76A0ABU, 0x0FF018U, 0x0443D5U, 0x5D188EU, 0x16A93BU, 0x0932E0U, 0xC07015U, + 0xFACB1EU, 0xB39AC3U, 0xE80170U, 0xE3B3ADU, 0xBAEA5EU, 0xD17956U, 0xC042A9U, 0x8A9378U, 0x912DE7U, 0xD86E86U, + 0x83F559U, 0x2AC4E8U, 0x711F37U, 0x7A0D6EU, 0x26B4C9U, 0x6D6710U, 0x547CE7U, 0x1F8CFEU, 0x449720U, 0x4D3483U, + 0x16EF5AU, 0x1EFE2DU, 0x6D44B4U, 0xA6174BU, 0xBF8E8AU, 0xF4FD95U, 0xED6764U, 0x86D6BBU, 0xDC8912U, 0xD10A45U, + 0x8A799CU, 0x83E12AU, 0xF872F3U, 0xB10954U, 0xAA980DU, 0x6083D6U, 0x397163U, 0x3AE8B8U, 0x439BDDU, 0x481046U, + 0x1302BBU, 0x5AFB68U, 0x50E875U, 0x297396U, 0x26824AU, 0x7D98F1U, 0x344BA4U, 0xAF726FU, 0xE6F5DAU, 0x9C0F01U, + 0x971C38U, 0xCE85EFU, 0xC5F626U, 0x946D91U, 0xFFBDC8U, 0xE48637U, 0xAC15A6U, 0xB74C48U, 0x7EEE99U, 0x21B586U, + 0x0A0677U, 0x539FA8U, 0x18CC01U, 0x007652U, 0x4B67CFU, 0x70B43CU, 0x390FF1U, 0x625ECAU, 0x6BD01FU, 0x38E3C4U, + 0xB23870U, 0xCB893BU, 0x8093C6U, 0x994055U, 0xD679A8U, 0x8DAAFBU, 0xA4B176U, 0xFE018DU, 0xF7CA5CU, 0xACD963U, + 0xE762B2U, 0xFE323DU, 0x1589C4U, 0x0C5A92U, 0x4F432BU, 0x17F0ECU, 0x1CAA35U, 0x673B82U, 0x6E54DBU, 0x31C724U, + 0x785CA5U, 0x632C5AU, 0x29B70BU, 0x508490U, 0xDB5D6DU, 0x82CFEEU, 0x89B492U, 0xD22541U, 0xBB2EDCU, 0xA1DD27U, + 0xE04F62U, 0xFB56D9U, 0xB0A50CU, 0xF9BED7U, 0xC24EFAU, 0x8F5529U, 0x55C6D0U, 0x5E3B47U, 0x07383FU, 0x2CA2F0U, + 0x75D161U, 0x3E489EU, 0x25BB0FU, 0x6DA170U, 0x7630B1U, 0x174B2EU, 0x4CD8D7U, 0x470180U, 0x9E2339U, 0xD5B8FEU, + 0xE9EB27U, 0xA2521DU, 0xB941C8U, 0xF0BB23U, 0xAB2AB6U, 0xA271CDU, 0xD9C250U, 0xD3DC83U, 0x8A1D6EU, 0x41A67DU, + 0x58B580U, 0x3B6C5BU, 0x205ECEU, 0x6985A5U, 0x333471U, 0x3E27CAU, 0x65FD13U, 0x0CCE44U, 0x1747EDU, 0x5C143AU, + 0x45AF83U, 0x8F7F54U, 0xF6643DU, 0xFDD7A2U, 0xA68E73U, 0xAF3D8CU, 0xF4E79DU, 0xB5E073U, 0x8F59AAU, 0xC40A3DU, + 0x999044U, 0x922197U, 0xCB7A3AU, 0x60E9E1U, 0x3B90B4U, 0x73020FU, 0x6839DAU, 0x21FA71U, 0x3A632CU, 0x5151DFU, + 0x088A43U, 0x039B80U, 0x4260FDU, 0x18F36EU, 0x33EB97U, 0xEE1848U, 0xE503C9U, 0xBCA4B6U, 0xF7FC67U, 0xEC6FD8U, + 0x849C01U, 0x9F8506U, 0xD616FFU, 0x8D6C29U, 0x86FD90U, 0xFF26CFU, 0xF4150EU, 0x2C9EB1U, 0x6FEE60U, 0x74751BU, + 0x39E696U, 0x429F65U, 0x4B0DB8U, 0x1056ABU, 0x1AE756U, 0x43FC9DU, 0x282F29U, 0x318172U, 0x7A90E7U, 0xE36B1CU, + 0xA878D9U, 0xF2A0E2U, 0xDB133FU, 0x8008ECU, 0xC1D955U, 0xDAE292U, 0x9570EBU, 0xAC9B74U, 0xE68A85U, 0xBF514BU, + 0x34635AU, 0x6FFAA5U, 0x66A93CU, 0x1D12CBU, 0x54C382U, 0x4ED915U, 0x056AECU, 0x5C2D37U, 0x779402U, 0x2607C9U, + 0x2D5D1CU, 0x72ECA6U, 0xBAB7FBU, 0xA12408U, 0xC89C85U, 0xD3CF76U, 0x98542BU, 0xC177B8U, 0xCAAE45U, 0xB29CCEU, + 0xB9473BU, 0xE2D660U, 0xABEDF1U, 0xA03F1EU, 0x7926C4U, 0x1A95F1U, 0x044E2AU, 0x4D49FFU, 0x56B154U, 0x1FA209U, + 0x6419FAU, 0x6F4867U, 0x36D394U, 0x3C2199U, 0x653842U, 0x2EABB7U, 0x95C02CU, 0xDC525CU, 0x87CB93U, 0x8EB80AU, + 0xD423FDU, 0xF152A4U, 0xAAC003U, 0xE15BDAU, 0xF82A0DU, 0xB3B134U, 0xAAB3EBU, 0xC14C0AU, 0x1B5D95U, 0x128664U, + 0x49357AU, 0x002DA3U, 0x3BDE40U, 0x70C5DDU, 0x6954AEU, 0x23AE73U, 0x76ADE8U, 0x7D760DU, 0x064756U, 0x0FDCE3U, + 0xD40E38U, 0x9D37F5U, 0x87E4C6U, 0xECDF1AU, 0xB54EA9U, 0xBE1470U, 0xE7B71FU, 0xEC288EU, 0xB77951U, 0xDFC3A0U, + 0xC490BFU, 0x89095EU, 0x1ABA81U, 0x51E118U, 0x2853EFU, 0x234AB6U, 0x7B8910U, 0x703AC9U, 0x2B6216U, 0x62F127U, + 0x59CAECU, 0x101B59U, 0x4B2082U, 0xC1F2FFU, 0x88696CU, 0xB358A1U, 0xFA9752U, 0xAD844FU, 0xA63CB4U, 0xFFEF60U, + 0xF5F4EBU, 0x8C059EU, 0xC71F45U, 0xDCACF0U, 0x95772BU, 0x4E6622U, 0x67CDD5U, 0x3D9F0CU, 0x3406BBU, 0x6F75F2U, + 0x24EE0DU, 0x3D7E9CU, 0x520542U, 0x4396B3U, 0x09ADBCU, 0x527C45U, 0x5BF292U, 0xA0810BU, 0xE91878U, 0xF20BB5U, + 0xB9F10EU, 0xA1E0DBU, 0xEA33C0U, 0x938835U, 0x989BFEU, 0xC36342U, 0xCA6011U, 0x95FBECU, 0xFD0A6FU, 0x6E11B2U, + 0x25C3C1U, 0x3CFA5CU, 0x7769A7U, 0x0EB266U, 0x058079U, 0x5E1988U, 0x167E17U, 0x0DE5EFU, 0x44B428U, 0x7F0E31U, + 0x349DC6U, 0xEDC41FU, 0xE277A0U, 0xBA6DE1U, 0x99BE1EU, 0xC2178FU, 0x8B4450U, 0x90FF39U, 0xD9EFAAU, 0x823457U, + 0xA88785U, 0xF1DE98U, 0xFA4D73U, 0x3377E6U, 0x68A41DU, 0x43AD48U, 0x1A1AD3U, 0x14C836U, 0x4DF1EDU, 0x0622D0U, + 0x173903U, 0x7C88FEU, 0x27527DU, 0x2E41A5U, 0xF4FADAU, 0xFDBB4BU, 0x8601A4U, 0xCDD235U, 0xD4CB6AU, 0x9F7893U, + 0x862304U, 0xCCB3EDU, 0xB388BAU, 0xBA5B23U, 0xE1C0DCU, 0xE8A00DU, 0x3B3F27U, 0x500CF2U, 0x48D509U, 0x034694U, + 0x5A3CC7U, 0x51AD6AU, 0x0AB6B9U, 0x234544U, 0x785E57U, 0x30DE9AU, 0x2B2561U, 0x6036F4U, 0xDDCF8FU, 0x96DD5BU, + 0xCF46D0U, 0xCCB729U, 0x96A4FEU, 0xDF3FC7U, 0xE44D10U, 0xADC4F9U, 0xB61366U, 0xFD2837U, 0xA4B888U, 0x8EC359U, + 0x5750B6U, 0x5C49AFU, 0x07BA79U, 0x4A2080U, 0x517307U, 0x38DA7EU, 0x62C9ADU, 0x611230U, 0x38A2CBU, 0x33F98EU, + 0x4A4A35U, 0x0153E0U, 0x98815BU, 0xD23A06U, 0xD929C5U, 0x80E079U, 0xEBD7AAU, 0xF20D57U, 0xBD1C4CU, 0xE6A78DU, + 0xEF7472U, 0x954CE3U, 0x9CDF9CU, 0xCF8455U, 0x0437C2U, 0x1DFE3BU, 0x56EC6CU, 0x6F57D5U, 0x25061BU, 0x7E95AAU, + 0x772FF5U, 0x2C7C24U, 0x05C59BU, 0x5A965AU, 0x111D21U, 0x892DBCU, 0xC2F26FU, 0x9B6192U, 0xB81891U, 0xE38B6EU, + 0xEA91B6U, 0xB16221U, 0xF9FB48U, 0xC2C8DFU, 0x890226U, 0x9013F9U, 0xDBE848U, 0x02FB07U, 0x0D62D6U, 0x77906DU, + 0x3E8BB0U, 0x2538C3U, 0x6C614EU, 0x77F39CU, 0x141861U, 0x4D0D7AU, 0x47968FU, 0x1EE544U, 0x157DD1U, 0xCEEEAAU, + 0xA7953FU, 0xBC06D4U, 0xF57E09U, 0xABED3AU, 0xA2EEE3U, 0xD91734U, 0xD2849CU, 0x8BDEC3U, 0xC06F32U, 0xD174ADU, + 0x9BA77CU, 0x201C93U, 0x690C8AU, 0x22F77DU, 0x3BF4E4U, 0x702933U, 0x4B9B5AU, 0x0380C1U, 0x585134U, 0x556AEEU, + 0x0EF9CBU, 0x45A310U, 0x7C12CDU, 0xB7C97EU, 0xAFEA23U, 0xEC72C0U, 0xB7215DU, 0x9E9A8EU, 0xC50BF3U, 0xCC5068U, + 0x97E28DU, 0xDDB916U, 0xC42846U, 0xAF93B9U, 0xF2D020U, 0x796CC7U, 0x223F9EU, 0x2BA429U, 0x5095F0U, 0x18473FU, + 0x03DC8EU, 0x40EFD1U, 0x593620U, 0x1225BFU, 0x6BCF76U, 0x605E40U, 0xBA6599U, 0xF3B66AU, 0xE8AEF7U, 0x851DBCU, + 0x9EC649U, 0xD5D5D2U, 0x8C2C27U, 0x863E7CU, 0xDFB5D9U, 0xF4C002U, 0xA55BDFU, 0xEEE9ECU, 0x75B020U, 0x3C23D3U, + 0x06584AU, 0x4FCB15U, 0x1453A4U, 0x1F306BU, 0x42AB9AU, 0x09FA05U, 0x30415CU, 0x7A53ABU, 0x61AA22U, 0x2839D5U, + 0xF3228CU, 0xDAD032U, 0x89C9E3U, 0x820A2CU, 0xCAB91DU, 0x91A4C6U, 0x985673U, 0xE34DB8U, 0xA8DCE5U, 0xB52756U, + 0xFE358BU, 0xE6EE78U, 0x0DDF75U, 0x5654AFU, 0x5F075AU, 0x04BFC1U, 0x0D2CB4U, 0x56577FU, 0x34C6C2U, 0x2D9D11U, + 0x662F48U, 0x3FB4FFU, 0x34E536U, 0x4F4E89U, 0xC61C58U, 0x988107U, 0xD132A7U, 0xCA6978U, 0x81D881U, 0xB8C296U, + 0xF3114FU, 0xAA2AA8U, 0xA0FB31U, 0xF37142U, 0xDA429FU, 0x819B24U, 0x4888E1U, 0x5333FAU, 0x1AE30FU, 0x40D0D5U, + 0x6F0B68U, 0x36182BU, 0x3DA0F6U, 0x646345U, 0x2F7898U, 0x14CDF3U, 0x5C9666U, 0xC704BDU, 0x8E7F4CU, 0xDDEED3U, + 0xD655BAU, 0xAF062DU, 0xE49EF5U, 0xFDFD02U, 0xB7661BU, 0xA8F7F4U, 0xC18D25U, 0x9A1E9AU, 0x9305CBU, 0x48F414U, + 0x43EFB5U, 0x1B1D6AU, 0x708413U, 0x698780U, 0x2A7C4DU, 0x6168BFU, 0x78F3A2U, 0x130059U, 0x0B1BCCU, 0x40CA07U, + 0x1FF072U, 0x9663E9U, 0xCD9A14U, 0xE499C7U, 0xBF0AEAU, 0xF57239U, 0xECE1E4U, 0xA7BA5FU, 0xDE098FU, 0xD591E0U, + 0x86E271U, 0x8F79AEU, 0xD52817U, 0x1C8350U, 0x2711A9U, 0x684C3EU, 0x71FFE7U, 0x3AE490U, 0x633619U, 0x498FE6U, + 0x10DC37U, 0x1B670DU, 0x4066D8U, 0x09BC13U, 0x328FAEU, 0xFB16FDU, 0xA9C500U, 0xA2FF93U, 0xFB2C5EU, 0xF03565U, + 0x8D86B0U, 0xC65D4BU, 0xDD7DCEU, 0x95E615U, 0x8EB169U, 0xE708FAU, 0xBCDB03U, 0x37C1C4U, 0x6E72FDU, 0x25232AU, + 0x3DB8F3U, 0x5ECA4CU, 0x45430DU, 0x0CF0B2U, 0x57AB61U, 0x5E3ABCU, 0x210087U, 0x2BD343U, 0xF248B8U, 0xB9392DU, + 0xA0A376U, 0xEBB09FU, 0x905908U, 0x99CAF1U, 0xD3F5A6U, 0xCA251FU, 0x813ED0U, 0xF2CD01U, 0xFBD63EU, 0xA046EEU, + 0x29BD51U, 0x76AE98U, 0x3C274FU, 0x055476U, 0x4ECEA5U, 0x573F58U, 0x1C24DBU, 0x47B786U, 0x6ECC7DU, 0x345CE8U, + 0x7D0703U, 0x66B456U, 0xAF3DECU, 0xBC6F31U, 0xD7D0A2U, 0x8EC1CFU, 0x80321CU, 0xD9A9A1U, 0xD2FB7AU, 0xA9422BU, + 0xE05184U, 0xFB8A55U, 0xB22AAAU, 0xE831B3U, 0x63E264U, 0x1ADB8CU, 0x11081BU, 0x4832E2U, 0x0BA1BDU, 0x10781CU, + 0x784BC3U, 0x679052U, 0x2E902DU, 0x752BFCU, 0x7EFC57U, 0x27C58AU, 0xCC57D9U, 0xD40C64U, 0x9FBFB6U, 0x84665BU, + 0xCD7540U, 0xB6CF95U, 0xBF8E6EU, 0xE415FBU, 0xE62690U, 0xBFFE0DU, 0xF04DFEU, 0xC91623U, 0x028520U, 0x19BCD9U, + 0x506E06U, 0x0AEDB6U, 0x23D4E9U, 0x780728U, 0x331997U, 0x3AE84EU, 0x6173B9U, 0x6840A0U, 0x129B67U, 0x598B9EU, + 0xC87009U, 0x877370U, 0x9EEAEBU, 0xF5190EU, 0xAC03D4U, 0xA690C1U, 0xFDE93AU, 0xB47AF7U, 0xAF8044U, 0xC69119U, + 0xDD0ACAU, 0x166977U, 0x4EF0BCU, 0x45E2C9U, 0x3C1D52U, 0x3F8E87U, 0x64D73CU, 0x296574U, 0x327E83U, 0x7A8F1AU, + 0x0114EDU, 0x0A1734U, 0x53EF3BU, 0xD8FCCAU, 0x812715U, 0xEA94A4U, 0xF185EBU, 0xB95F3AU, 0xA26C85U, 0xEBB55CU, + 0xB0862AU, 0x930CA3U, 0xCEDD70U, 0x85E6CDU, 0x9D7196U, 0x562A63U, 0x6D9AE8U, 0x24411DU, 0x7F5246U, 0x76EBDBU, + 0x2DB928U, 0x2712E5U, 0x5E83D7U, 0x15D80AU, 0x0C6BD1U, 0x473170U, 0x54A02FU, 0xB99B9EU, 0xE34841U, 0xEAD090U, + 0xB1E33FU, 0xFA2866U, 0xE31991U, 0x88C208U, 0x8154FFU, 0xDB6FB7U, 0x92BE28U, 0x89A5D9U, 0xE25606U, 0x7BCE37U, + 0x30FDFCU, 0x693E21U, 0x6F2792U, 0x3495CFU, 0x5DCE6CU, 0x465DB1U, 0x0FA46AU, 0x14B75FU, 0x5F2D85U, 0x275C60U, + 0x2CC7FBU, 0xF56486U, 0xFE3C55U, 0xA5ABC8U, 0xECD02BU, 0xD743F2U, 0x9FDAC5U, 0xC0A81CU, 0xC33383U, 0x9A2262U, + 0xB1D93DU, 0xE8DA8DU, 0x232252U, 0x39B19BU, 0x70AAACU, 0x6B5975U, 0x024182U, 0x59921BU, 0x522974U, 0x0B38A5U, + 0x01F33EU, 0x78C1CBU, 0x3758D0U, 0x242F3DU, 0xEDB4EFU, 0xB66672U, 0xBF5701U, 0xC5CCDCU, 0xCE9F77U, 0x9726A2U, + 0xDCA4F9U, 0xC5FF4CU, 0xAE4E87U, 0xB51576U, 0xFDA7E9U, 0xA63C90U, 0x2F6D47U, 0x74D6CFU, 0x1BC538U, 0x0A1D21U, + 0x413EDEU, 0x58E50FU, 0x127480U, 0x6B4BF1U, 0x60992EU, 0x2BA297U, 0x327340U, 0x79E809U, 0xA0DABAU, 0x8A0367U, + 0xD1009DU, 0x98BB98U, 0x836A63U, 0xCE70F6U, 0xF5832DU, 0xB69850U, 0xEE29D3U, 0xE5732EU, 0xBCE0FDU, 0xB75962U, + 0x4C1A13U, 0x0589DCU, 0x1EF165U, 0x5666B7U, 0x4DFDEAU, 0x268E59U, 0x7F1794U, 0x74256FU, 0x29FE7AU, 0x626F81U, + 0x700404U, 0x1996DFU, 0x828FA2U, 0xCB7C31U, 0x9067CCU, 0x9B940EU, 0xE20C17U, 0xE81FE8U, 0xB1E439U, 0xFAF586U, + 0xE17FC7U, 0xA88C18U, 0xD397A1U, 0x5E4276U, 0x04794FU, 0x0FEB88U, 0x5E9271U, 0x3501E6U, 0x2C9A3EU, 0x67EA51U, + 0x7C61C0U, 0x34323BU, 0x6F89EEU, 0x4618F5U, 0x1D4218U, 0xD4F1CBU, 0xCFE876U, 0x841B25U, 0xB888F8U, 0xF3D053U, + 0xAA7386U, 0xA168BCU, 0xF2BB69U, 0xFB8792U, 0x80541FU, 0xC8CF4CU, 0xC3FEB5U, 0x182522U, 0x1117CBU, 0x6A9E9CU, + 0x234D05U, 0x3876FAU, 0x72A42BU, 0x2FBD84U, 0x240ED4U, 0x5DD54BU, 0x56C4B2U, 0x057E65U, 0x4C2D4CU, 0xD7849BU, + 0xBFD762U, 0xA44DF9U, 0xEFFEACU, 0xB6A347U, 0xBD30D2U, 0xC40B29U, 0x8FDB74U, 0x9540E6U, 0xDC330BU, 0xC3BAD8U, + 0x8A89E5U, 0x71533EU, 0x7AC0BBU, 0x2BB140U, 0x212A11U, 0x7838AEU, 0x13C17FU, 0x08D290U, 0x414989U, 0x1AA85EU, + 0x13B2A6U, 0x494131U, 0x625AD8U, 0xBBCF87U, 0xF43516U, 0xED26E9U, 0xA6BF78U, 0x9FCC17U, 0xDD57CEU, 0x86B75DU, + 0x8FACA0U, 0xD43FE3U, 0x9D445EU, 0xA6D584U, 0x6DCF71U, 0x753C6AU, 0x3EA5AFU, 0x67F654U, 0x4C4CC9U, 0x134DBAU, + 0x1AB667U, 0x4125E4U, 0x097E19U, 0x12CA0AU, 0x71D1F3U, 0x78022CU, 0xA33BACU, 0xEAA953U, 0xF17A02U, 0x9B53BDU, + 0xC28074U, 0xC9BB83U, 0x90299AU, 0x9BF04DU, 0xC4C3B4U, 0xAD583BU, 0xB7084AU, 0xFEB3D1U, 0x656004U, 0x2E69FEU, + 0x5FDAF3U, 0x548000U, 0x0D37DDU, 0x472E4EU, 0x5CFD33U, 0x1546F8U, 0x2E164DU, 0x678D96U, 0x3CBEE3U, 0x337768U, + 0xEBE5BDU, 0xC09E47U, 0x990D5EU, 0xD234A9U, 0xC9E720U, 0x887DF7U, 0xD34C0EU, 0xFB9711U, 0xA084E0U, 0xA96C2FU, + 0xF27FBEU, 0xB9E4C1U, 0x001318U, 0x4B02AFU, 0x56B867U, 0x1CEB30U, 0x477289U, 0x4E8152U, 0x358AF7U, 0x3C1AACU, + 0x676159U, 0x24F2C2U, 0x3C6917U, 0x57197CU, 0x8E82E1U, 0x85F112U, 0xDE68CFU, 0xD77BDDU, 0x8C9130U, 0xE400EBU, + 0xFB5B5EU, 0xB0E885U, 0xA9F6D4U, 0xE2277BU, 0x9B9CA2U, 0x108F15U, 0x5A764CU, 0x0B64ABU, 0x00AF32U, 0x599CEDU, + 0x72059DU, 0x29D702U, 0x60ECE3U, 0x7A7D3CU, 0x330625U, 0x0C95D6U, 0x474D0BU, 0x9E7EA8U, 0x95FDF5U, 0xCCA666U, + 0x86179BU, 0xBD8D40U, 0xFCDA65U, 0xE763BFU, 0xAE304AU, 0xF58AD1U, 0xDE9B9CU, 0x86406FU, 0x8DE3F2U, 0x54B801U, + 0x1B28D8U, 0x0213D7U, 0x69C026U, 0x7259B9U, 0x3A6B48U, 0x61B017U, 0x68819FU, 0x135A68U, 0x50D9B1U, 0x49E386U, + 0x02325FU, 0x9A29B0U, 0xD19E23U, 0xAAC77EU, 0xA3558DU, 0xFCAE04U, 0xF5BFF3U, 0xAE04AAU, 0xC44635U, 0xDDDDC5U, + 0x966C4AU, 0xCF373BU, 0xC4A4E4U, 0x1FD47DU, 0x3E4F8AU, 0x64DC43U, 0x2DA574U, 0x3637ADU, 0x7D5C56U, 0x44CFC3U, + 0x0BD688U, 0x52257DU, 0x59BBE7U, 0x03AA1AU, 0x4A5119U, 0xF142C4U, 0xB89A37U, 0xB329AAU, 0xE822D1U, 0xA9D114U, + 0x93C88FU, 0xDA5A7AU, 0xC1A121U, 0x8AB090U, 0xD36B5EU, 0xDC58A7U, 0xA5C0B0U, 0x2F8369U, 0x74389EU, 0x3DAB17U, + 0x26D368U, 0x4F40F9U, 0x141F26U, 0x1DAEC7U, 0x473558U, 0x446721U, 0x1DDEF6U, 0x769D6EU, 0xEF0E8DU, 0xA434D0U, + 0xBFE56BU, 0xF35EBEU, 0x884D45U, 0x819440U, 0xDAA69BU, 0x917D26U, 0x88ECF5U, 0xC3C788U, 0xFB150BU, 0xB02ED6U, + 0x63BF24U, 0x6A6439U, 0x3153D2U, 0x188B03U, 0x43989CU, 0x0923EDU, 0x147232U, 0x5FF99BU, 0x060B4CU, 0x2D10B5U, + 0x76A1A2U, 0x7FFA7BU, 0xA568D4U, 0xECD104U, 0xD7827BU, 0x9C19EAU, 0x8D6831U, 0xC6E2DCU, 0x9F71CFU, 0x950A32U, + 0xEE9BE1U, 0xA3A16CU, 0xB8761FU, 0xF16FC2U, 0x6A9C79U, 0x010FACU, 0x491786U, 0x52F453U, 0x1BEF88U, 0x007C35U, + 0x4B8566U, 0x32978FU, 0x316C18U, 0x697DC1U, 0x62E6BEU, 0x3B142FU, 0xD40DC0U, 0xCFCE11U, 0x86F58EU, 0xDD66F6U, + 0xD53E31U, 0x8E8988U, 0xA5125FU, 0xFC6366U, 0xB7F9B9U, 0xAEAA78U, 0xE513C3U, 0xD69096U, 0x1ECB6DU, 0x4579F8U, + 0x4C6033U, 0x13B34EU, 0x5888DCU, 0x615821U, 0x2AE3F2U, 0x32F0FFU, 0x792B04U, 0x220A91U, 0x0B906AU, 0xD0432BU, + 0xD97294U, 0x82AD4DU, 0xC8BE9AU, 0xD90623U, 0xB2D564U, 0xEBEE9CU, 0xE02D03U, 0xBF35D2U, 0xB686ADU, 0xCCDD2CU, + 0x854CD3U, 0x1EF70AU, 0x55A51DU, 0x4C1CE4U, 0x074F67U, 0x7ED49AU, 0x7476C9U, 0x2F2F75U, 0x26BCBEU, 0x75876BU, + 0x1C5650U, 0x07C885U, 0x48BB7EU, 0x9022F3U, 0x9B11A0U, 0xC2CA5DU, 0xE9C8CEU, 0xA07133U, 0xFBA238U, 0xF0B9E9U, + 0xA84917U, 0x835286U, 0xDAC179U, 0x913830U, 0x822B87U, 0x4BD95EU, 0x30D289U, 0x3C43A0U, 0x67B87FU, 0x6CAA8EU, + 0x353311U, 0x7E4440U, 0x47DFABU, 0x0C8E3FU, 0x1634C4U, 0x5FA7D9U, 0x04CC0AU, 0xAD5DE7U, 0xF64774U, 0xBDB409U, + 0xAC2DD2U, 0xE67E67U, 0xFBC5ACU, 0x90D5F9U, 0xCB0E42U, 0xC23D9FU, 0x99B67DU, 0x9047E4U, 0xEA5DB3U, 0x218E0AU, + 0x38B7D5U, 0x732034U, 0x6AFA2BU, 0x21CBFAU, 0x5A1005U, 0x5B039CU, 0x01A8FBU, 0x4C7822U, 0x574395U, 0x3CD04DU, + 0xA5811AU, 0xEE3BB3U, 0xB76868U, 0xBDF19DU, 0xE64286U, 0xCF1943U, 0x949BF8U, 0xDDA225U, 0xC67156U, 0x8FCACBU, + 0xF59B08U, 0xF605F5U, 0x2B36EFU, 0x20EF1AU, 0x797CC1U, 0x324664U, 0x1985BFU, 0x419CE4U, 0x4A6F51U, 0x13748AU, + 0x58C42FU, 0x631F74U, 0x2A0CA1U, 0x71F51AU, 0xF9E7D7U, 0xA27CA5U, 0xA99F38U, 0xD886EBU, 0x973516U, 0x8E6F1DU, + 0xC5FEECU, 0xDF0133U, 0xB61282U, 0xED8ADDU, 0xE4F954U, 0xBF7AA3U, 0x74617AU, 0x6D904DU, 0x070A95U, 0x1E795AU, + 0x55E0EBU, 0x0EF334U, 0x0F0845U, 0x7098CAU, 0x798313U, 0x2360E0U, 0x687BFDU, 0x71AB0EU, 0xBA10C3U, 0x830370U, + 0xC8FA2DU, 0x93EDB7U, 0x9B3742U, 0xC00689U, 0xA99DBCU, 0xB25E67U, 0xF964BAU, 0xE0F509U, 0xA7AE44U, 0xFF1DB7U, + 0x54C42EU, 0x0DF6F1U, 0x066D90U, 0x5D3C0EU, 0x1487FFU, 0x2F8460U, 0x675E19U, 0x3CEFDEU, 0x37B447U, 0x6E27B0U, + 0x651369U, 0x9CC856U, 0xD77B97U, 0xC9604CU, 0x88B1F9U, 0x939BA2U, 0xFA4856U, 0xB1D1DDU, 0xAAE200U, 0xE33873U, + 0xB829EEU, 0xB0D20DU, 0xCBC190U, 0x407AEBU, 0x19AA3EU, 0x52A1A5U, 0x4B1240U, 0x204B1BU, 0x3ED9A2U, 0x7F2274U, + 0x2437ADU, 0x2DAC8AU, 0x76DF53U, 0x5D45ACU, 0x04F43DU, 0xCEBFE2U, 0xD72CC3U, 0x9C551CU, 0xE7C7E5U, 0xEEDC72U, + 0xB52D1BU, 0xBCB6CCU, 0xE6A454U, 0xA95FA7U, 0x904EFAU, 0xD3A551U, 0xCA3684U, 0x01265FU, 0x5ADD6AU, 0x72CEB1U, + 0x29135CU, 0x60A1CFU, 0x7BBA92U, 0x304921U, 0x2950FCU, 0x42D316U, 0x1AA903U, 0x1138F8U, 0x48E329U, 0xC7D0A6U, + 0xBC48DFU, 0xFD1B08U, 0xE6A0B1U, 0xAE3366U, 0xF55AAFU, 0xFEC898U, 0x879341U, 0x8C22FEU, 0xD5392EU, 0x9EEA61U, + 0x8456D0U, 0x6D450BU, 0x769EF6U, 0x3FADE5U, 0x606508U, 0x6BD6DBU, 0x3ACD46U, 0x101C3DU, 0x5927F8U, 0x02F543U, + 0x096C96U, 0x505FCCU, 0xBB8C71U, 0xA296B2U, 0xE8370FU, 0xF36C5CU, 0xBADF85U, 0xE1063AU, 0xC814FBU, 0x97AF04U, + 0x9CFA15U, 0xC461EAU, 0x87D23BU, 0xBE88B4U, 0x7509CCU, 0x2E721BU, 0x27E1A2U, 0x7C5965U, 0x750A7CU, 0x0F9183U, + 0x44A052U, 0x5D7BE9U, 0x1669ACU, 0x0F9277U, 0x6013CAU, 0x3B0819U, 0xB3FA65U, 0xE0E3E6U, 0xA9103BU, 0xB28BC0U, + 0xDB9ED5U, 0xC0642EU, 0x8B77EBU, 0xD3EC50U, 0xD80D89U, 0xA1179EU, 0xAAC477U, 0xF1F5A0U, 0x386E19U, 0x271D4FU, + 0x6F8596U, 0x541639U, 0x1F6DE8U, 0x4EFE97U, 0x45B706U, 0x1C05D9U, 0x779E30U, 0x6DCF27U, 0x2474DEU, 0x3F664DU, + 0xF69BB0U, 0xAD08FBU, 0x86536FU, 0xDFE094U, 0xD1E841U, 0x883B6AU, 0xC300B7U, 0xE8D164U, 0xB16BD9U, 0xF2788AU, + 0xEBA167U, 0xA192FCU, 0x3A0929U, 0x53DBD2U, 0x08F2C3U, 0x01212DU, 0x5A3ABCU, 0x518B43U, 0x29D11AU, 0x62628DU, + 0x7FFD64U, 0x34ACB3U, 0x6F1E8AU, 0xE64555U, 0x95C494U, 0x9D7F2BU, 0xC62C7AU, 0x8DB485U, 0x94871DU, 0xFF5CDEU, + 0xE6EFE3U, 0xADB730U, 0xF524CFU, 0xFE0F56U, 0xA5DE31U, 0x48C5E8U, 0x53377FU, 0x1AAE86U, 0x01BD59U, 0x4B4678U, + 0x3A50A7U, 0x31E977U, 0x683ACCU, 0x633189U, 0x38C072U, 0x71DAEFU, 0x4B493CU, 0x82B001U, 0x99A3C2U, 0xD2383FU, + 0x8F4AA4U, 0xA4D3D1U, 0xFDA00AU, 0xB62B8FU, 0xACBB75U, 0xE5C028U, 0xF6539BU, 0x9F4242U, 0xC4B985U, 0x4F2FBCU, + 0x167463U, 0x1CC5D2U, 0x65DE1DU, 0x2E3C4CU, 0x35A5F3U, 0x78F622U, 0x234DDDU, 0x0A5C55U, 0x408622U, 0x5BB5FBU, + 0x122E6CU, 0xC1FF95U, 0xC8C4DEU, 0xB3066BU, 0xB83FB0U, 0xE0AC25U, 0xAB775EU, 0xB24793U, 0xD9DC20U, 0x828BFDU, + 0x8F30AFU, 0xD46102U, 0xDCDBD9U, 0x07582CU, 0x6E0137U, 0x75B2C6U, 0x3EE809U, 0x2F7990U, 0x64C2E7U, 0x1C913EU, + 0x170A89U, 0x4C3A40U, 0x45E9D6U, 0x1E42AFU, 0x571370U, 0xE889C1U, 0xA2BA0EU, 0xFB635FU, 0xF070E0U, 0xA9CF39U, + 0x821DDAU, 0xD904C7U, 0x90F734U, 0x82FCE9U, 0xCB4D72U, 0xD09716U, 0x3B848DU, 0x623D78U, 0x696EA3U, 0x30F4BEU, + 0x7A075DU, 0x451E80U, 0x0C8D33U, 0x17E66EU, 0x5E76B5U, 0x050D14U, 0x0E9ECBU, 0x7607BAU, 0xF57524U, 0xACEAC5U, + 0xE7F91AU, 0xFC0083U, 0x9583F4U, 0xCED96DU, 0xC6688AU, 0x9D7353U, 0x92804CU, 0xCB11BDU, 0xA00B62U, 0xA9F8C3U, + 0x72E398U, 0x783244U, 0x2118F7U, 0x4A8B2AU, 0x5B5249U, 0x1061D4U, 0x09FA2FU, 0x428ABAU, 0x1911F1U, 0x31C604U, + 0x6AFD9FU, 0x276D62U, 0xBC2631U, 0xF795B8U, 0xCE4C4EU, 0x855F97U, 0xDDE520U, 0xD6B479U, 0x8D0FB6U, 0x8C1C07U, + 0xF7C6D8U, 0xBE77A9U, 0xA53C36U, 0xEFAFDFU, 0x369648U, 0x1D4411U, 0x40DFE6U, 0x4BEE7EU, 0x103595U, 0x5902C0U, + 0x43D85BU, 0x2AC9AEU, 0x316265U, 0x7AB158U, 0x23A18BU, 0x201A56U, 0xD949E5U, 0x93F2A8U, 0x882313U, 0xC139C6U, + 0xDA8A3CU, 0x97D329U, 0xEC50E2U, 0xE7AA13U, 0xBFBB8CU, 0xB42075U, 0xED5322U, 0x06C8ABU, 0x1DF85CU, 0x542785U, + 0x0FB4BAU, 0x0FCD7BU, 0x544FA4U, 0x7F5414U, 0x26A54BU, 0x6D3EF2U, 0x702D31U, 0x3BD7CCU, 0x13C6DFU, 0xC83D02U, + 0x83AEF9U, 0x9AB77CU, 0xD15507U, 0xC84ED2U, 0xA3DF49U, 0xF9A4BDU, 0xF836F6U, 0xA3ED4BU, 0xEADC98U, 0xD14B05U, + 0x9A30E6U, 0x03A0BFU, 0x4D6B00U, 0x1458D1U, 0x1FC12EU, 0x64932FU, 0x6D28F0U, 0x36BB09U, 0x7FE29FU, 0x6551E6U, + 0x0E0B21U, 0x57AAB8U, 0x54B147U, 0x8D6216U, 0x86DBA9U, 0xDDC968U, 0xB412F3U, 0xAA218EU, 0xE3FC5DU, 0xF86EF0U, + 0xB35523U, 0xCA945FU, 0xC1AFCCU, 0x983C11U, 0xD264FAU, 0x49D7EFU, 0x000C14U, 0x3B1FC1U, 0x7AA75AU, 0x21F4B7U, + 0x2A6FA4U, 0x728E5DU, 0x5D958AU, 0x042713U, 0x4F767DU, 0x54EDACU, 0x1D5E13U, 0x8604C0U, 0xEE91ADU, 0xB5EA36U, + 0xBC79E3U, 0xE77018U, 0xAC820DU, 0x9D19E6U, 0xD6283FU, 0xCEF3A8U, 0x85E0D1U, 0xDA1A47U, 0x538BAEU, 0x389079U, + 0x216360U, 0x6A6B9FU, 0x30D84EU, 0x3903E1U, 0x421230U, 0x0BE94FU, 0x10FBD6U, 0x5B6011U, 0x4215E8U, 0x008EFBU, + 0xF95C07U, 0xF2759CU, 0xADE659U, 0xA49D22U, 0xFF0CB7U, 0x96964CU, 0x8CE591U, 0xC77E92U, 0x9E2F6FU, 0x959CBCU, + 0xCC0601U, 0xE7475AU, 0x3CFCBFU, 0x74EF25U, 0x6717F4U, 0x2E048BU, 0x15DF0AU, 0x5A6CF5U, 0x03716CU, 0x08A33BU, + 0x5098C2U, 0x5B1945U, 0x20C2BCU, 0x69F063U, 0xF22972U, 0xBB1A8DU, 0xE0814DU, 0xCA52F6U, 0x936AABU, 0x98B958U, + 0xC9A2C5U, 0x820306U, 0x9B593BU, 0xF4CAE0U, 0xEE7315U, 0xA7209EU, 0x7C9BCBU, 0x75C930U, 0x0E54A4U, 0x45E75FU, + 0x5CBC12U, 0x173D81U, 0x0F0778U, 0x44D4AFU, 0x3FC596U, 0x363E59U, 0x65ACE8U, 0x6C9737U, 0xA346E6U, 0xD95DD9U, + 0xD0FE10U, 0x8B26E6U, 0xC2357FU, 0xD9CE28U, 0x92DDC1U, 0xAB455EU, 0xE1B6AFU, 0xB8ADF4U, 0xB35861U, 0x68438AU, + 0x41C157U, 0x12B864U, 0x5B2BB9U, 0x45B00AU, 0x0EC3C6U, 0x175B95U, 0x7C2828U, 0x25B3F3U, 0x2E2206U, 0x75580DU, + 0x3DDBD8U, 0x86C223U, 0xCF31BAU, 0xD4AADDU, 0x9FF804U, 0xC64193U, 0xCD526AU, 0xBDA9F4U, 0xB2BD95U, 0xE92E4AU, + 0xA0C5FBU, 0xBBD424U, 0xD20F7DU, 0x093DDAU, 0x01A603U, 0x5A77F0U, 0x514CEDU, 0x089E36U, 0x63A7D3U, 0x7A3448U, + 0x31FF3CU, 0x2BCEE7U, 0x625462U, 0x150799U, 0x1CBC84U, 0xC7ED77U, 0x8C76AAU, 0x95C409U, 0xDF9954U, 0xE62A8FU, + 0xAD213EU, 0xF6F1E1U, 0xFF4A00U, 0xA4191EU, 0x8D82C7U, 0xD7B330U, 0x9C69A9U, 0x057ACEU, 0x42C357U, 0x2B1088U, + 0x303A79U, 0x7BEB76U, 0x63F087U, 0x284358U, 0x7198E1U, 0x5A88B2U, 0x017B7EU, 0x0860CDU, 0x53F510U, 0x9A0F63U, + 0xA00CFEU, 0xEBB535U, 0xF2E6C0U, 0xB97DDBU, 0xEC0F2EU, 0xE796B5U, 0x9C0578U, 0xD47E0BU, 0xCFEF92U, 0x86F564U, + 0x1D16ADU, 0x768F32U, 0x2FFC43U, 0x24669CU, 0x7C752DU, 0x778CE2U, 0x2E1F9BU, 0x45000CU, 0x5EF0D5U, 0x13FB62U, + 0x40282BU, 0x4811D4U, 0xB38344U, 0xB8789FU, 0xE16BEAU, 0xAAB261U, 0xB38194U, 0xF81B4FU, 0xC2CA52U, 0x8BE1B1U, + 0xD0726CU, 0xD903DFU, 0x829982U, 0xE94A79U, 0x7471FDU, 0x36E026U, 0x2FBA93U, 0x640DC8U, 0x3F1431U, 0x16D7B6U, + 0x4D6C6FU, 0x443C18U, 0x1E8781U, 0x55947EU, 0x6C4FBFU, 0x27FFA0U, 0xBEE451U, 0xF5378FU, 0xAE0E2EU, 0xA2CD71U, + 0xC9D7C8U, 0x98661FU, 0x93BDC6U, 0xC88EE5U, 0xC15438U, 0xBA45C3U, 0xF2FE56U, 0xE9290DU, 0x2230E8U, 0x3B9273U, + 0x70C98FU, 0x0958DCU, 0x02A343U, 0x58B0A2U, 0x150A7DU, 0x0E5BC4U, 0x6FC897U, 0x74F33AU, 0x3F23E9U, 0x66A834U, + 0xECDB0FU, 0xB542DAU, 0x9E5131U, 0xC7ABA5U, 0x8C38FEU, 0x97010BU, 0xDED290U, 0xA4CC7DU, 0xAD3D2EU, 0xF6B6B3U, + 0xF9A540U, 0x205ED9U, 0x634EB6U, 0x5A9567U, 0x11A6D8U, 0x0B3F09U }; + + const uint32_t A_TABLE[] = { + 0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U, 32U, 36U, 40U, 44U, + 48U, 52U, 56U, 60U, 64U, 68U, 1U, 5U, 9U, 13U, 17U, 21U }; + const uint32_t B_TABLE[] = { + 25U, 29U, 33U, 37U, 41U, 45U, 49U, 53U, 57U, 61U, 65U, 69U, + 2U, 6U, 10U, 14U, 18U, 22U, 26U, 30U, 34U, 38U, 42U }; + const uint32_t C_TABLE[] = { + 46U, 50U, 54U, 58U, 62U, 66U, 70U, 3U, 7U, 11U, 15U, 19U, + 23U, 27U, 31U, 35U, 39U, 43U, 47U, 51U, 55U, 59U, 63U, 67U, 71U }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN audio processing and interleaving. + // --------------------------------------------------------------------------- + + class HOST_SW_API Audio { + public: + /// Initializes a new instance of the Audio class. + Audio(); + /// Finalizes a instance of the Audio class. + ~Audio(); + + /// Decode a NXDN AMBE audio frame. + void decode(const uint8_t* in, uint8_t* out) const; + /// Encode a NXDN AMBE audio frame. + void encode(const uint8_t* in, uint8_t* out) const; + + private: + /// + void decode(const uint8_t* in, uint8_t* out, uint32_t offset) const; + /// + void encode(const uint8_t* in, uint8_t* out, uint32_t offset) const; + }; +} // namespace nxdn + +#endif // __NXDN_AUDIO_H__ diff --git a/nxdn/Control.cpp b/nxdn/Control.cpp new file mode 100644 index 00000000..e3aa4254 --- /dev/null +++ b/nxdn/Control.cpp @@ -0,0 +1,547 @@ +/** +* 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) 2015-2020 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 +* 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/Control.h" +#include "nxdn/acl/AccessControl.h" +#include "nxdn/Sync.h" +#include "edac/AMBEFEC.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace nxdn; +using namespace nxdn::packet; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t SCRAMBLER[] = { + 0x00U, 0x00U, 0x00U, 0x82U, 0xA0U, 0x88U, 0x8AU, 0x00U, 0xA2U, 0xA8U, 0x82U, 0x8AU, 0x82U, 0x02U, + 0x20U, 0x08U, 0x8AU, 0x20U, 0xAAU, 0xA2U, 0x82U, 0x08U, 0x22U, 0x8AU, 0xAAU, 0x08U, 0x28U, 0x88U, + 0x28U, 0x28U, 0x00U, 0x0AU, 0x02U, 0x82U, 0x20U, 0x28U, 0x82U, 0x2AU, 0xAAU, 0x20U, 0x22U, 0x80U, + 0xA8U, 0x8AU, 0x08U, 0xA0U, 0xAAU, 0x02U }; + + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the Control class. +/// +/// NXDN Radio Access Number. +/// Amount of hangtime for a NXDN call. +/// Modem frame buffer queue size (bytes). +/// Transmit timeout. +/// Amount of time to hang on the last talkgroup mode from RF. +/// Instance of the Modem class. +/// Instance of the BaseNetwork class. +/// Flag indicating full-duplex operation. +/// Instance of the RadioIdLookup class. +/// 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 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) : + m_voice(NULL), + m_data(NULL), + m_ran(ran), + m_timeout(timeout), + m_modem(modem), + m_network(network), + m_duplex(duplex), + m_rfLastLICH(), + m_rfLayer3(), + m_netLayer3(), + m_rfMask(0U), + m_netMask(0U), + m_idenTable(idenTable), + m_ridLookup(ridLookup), + m_tidLookup(tidLookup), + m_idenEntry(), + m_queue(queueSize, "NXDN Frame"), + m_rfState(RS_RF_LISTENING), + m_rfLastDstId(0U), + m_netState(RS_NET_IDLE), + m_netLastDstId(0U), + m_rfTimeout(1000U, timeout), + m_rfTGHang(1000U, tgHang), + m_netTimeout(1000U, timeout), + m_networkWatchdog(1000U, 0U, 1500U), + m_rssiMapper(rssiMapper), + m_rssi(0U), + m_maxRSSI(0U), + m_minRSSI(0U), + m_aveRSSI(0U), + m_rssiCount(0U), + m_verbose(verbose), + m_debug(debug) +{ + assert(ridLookup != NULL); + assert(tidLookup != NULL); + assert(idenTable != NULL); + assert(rssiMapper != NULL); + + acl::AccessControl::init(m_ridLookup, m_tidLookup); + + m_voice = new Voice(this, network, debug, verbose); + m_data = new Data(this, network, debug, verbose); +} + +/// +/// Finalizes a instance of the Control class. +/// +Control::~Control() +{ + if (m_voice != NULL) { + delete m_voice; + } + + if (m_data != NULL) { + delete m_data; + } +} + +/// +/// Resets the data states for the RF interface. +/// +void Control::reset() +{ + m_rfState = RS_RF_LISTENING; + + if (m_voice != NULL) { + m_voice->resetRF(); + } + + if (m_data != NULL) { + m_data->resetRF(); + } + + m_queue.clear(); + + m_rfMask = 0x00U; + m_rfLayer3.reset(); + + m_netState = RS_NET_IDLE; + + m_netMask = 0x00U; + m_netLayer3.reset(); +} + +/// +/// Helper to set NXDN configuration options. +/// +/// Instance of the yaml::Node class. +/// +/// +/// +/// +/// +void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, + uint8_t channelId, uint32_t channelNo, bool printOptions) +{ + yaml::Node systemConf = conf["system"]; + yaml::Node nxdnProtocol = conf["protocols"]["nxdn"]; + + m_voice->m_silenceThreshold = nxdnProtocol["silenceThreshold"].as(nxdn::DEFAULT_SILENCE_THRESHOLD); + if (m_voice->m_silenceThreshold > MAX_NXDN_VOICE_ERRORS) { + LogWarning(LOG_NXDN, "Silence threshold > %u, defaulting to %u", nxdn::MAX_NXDN_VOICE_ERRORS, nxdn::DEFAULT_SILENCE_THRESHOLD); + m_voice->m_silenceThreshold = nxdn::DEFAULT_SILENCE_THRESHOLD; + } + + std::vector entries = m_idenTable->list(); + for (auto it = entries.begin(); it != entries.end(); ++it) { + lookups::IdenTable entry = *it; + if (entry.channelId() == channelId) { + m_idenEntry = entry; + break; + } + } + + if (printOptions) { + LogInfo(" Silence Threshold: %u (%.1f%%)", m_voice->m_silenceThreshold, float(m_voice->m_silenceThreshold) / 12.33F); + } + + if (m_voice != NULL) { + m_voice->resetRF(); + m_voice->resetNet(); + } + + if (m_data != NULL) { + m_data->resetRF(); + } +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Control::processFrame(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + uint8_t type = data[0U]; + + if (type == modem::TAG_LOST && m_rfState == RS_RF_AUDIO) { + if (m_rssi != 0U) { + ::ActivityLog("NXDN", true, "transmission lost, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", + float(m_voice->m_rfFrames) / 12.5F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + } + else { + ::ActivityLog("NXDN", true, "transmission lost, %.1f seconds, BER: %.1f%%", + float(m_voice->m_rfFrames) / 12.5F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); + } + + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); + + writeEndRF(); + return false; + } + + if (type == modem::TAG_LOST && m_rfState == RS_RF_DATA) { + writeEndRF(); + return false; + } + + if (type == modem::TAG_LOST) { + m_rfState = RS_RF_LISTENING; + m_rfMask = 0x00U; + m_rfLayer3.reset(); + return false; + } + + // Have we got RSSI bytes on the end? + if (len == (NXDN_FRAME_LENGTH_BYTES + 4U)) { + uint16_t raw = 0U; + raw |= (data[50U] << 8) & 0xFF00U; + raw |= (data[51U] << 0) & 0x00FFU; + + // Convert the raw RSSI to dBm + int rssi = m_rssiMapper->interpolate(raw); + if (m_verbose) { + LogMessage(LOG_RF, "NXDN, raw RSSI = %u, reported RSSI = %d dBm", raw, rssi); + } + + // RSSI is always reported as positive + m_rssi = (rssi >= 0) ? rssi : -rssi; + + if (m_rssi > m_minRSSI) + m_minRSSI = m_rssi; + if (m_rssi < m_maxRSSI) + m_maxRSSI = m_rssi; + + m_aveRSSI += m_rssi; + m_rssiCount++; + } + + scrambler(data + 2U); + + channel::LICH lich; + bool valid = lich.decode(data + 2U); + + if (valid) + m_rfLastLICH = lich; + + uint8_t usc = m_rfLastLICH.getFCT(); + uint8_t option = m_rfLastLICH.getOption(); + + 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; + } + + return ret; +} + +/// +/// Get frame data from data ring buffer. +/// +/// Buffer to store frame data. +/// Length of frame data retreived. +uint32_t Control::getFrame(uint8_t* data) +{ + assert(data != NULL); + + if (m_queue.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_queue.getData(&len, 1U); + m_queue.getData(data, len); + + return len; +} + +/// +/// Updates the processor by the passed number of milliseconds. +/// +/// +void Control::clock(uint32_t ms) +{ + if (m_network != NULL) { + processNetwork(); + } + + // handle timeouts and hang timers + m_rfTimeout.clock(ms); + m_netTimeout.clock(ms); + + if (m_rfTGHang.isRunning()) { + m_rfTGHang.clock(ms); + + if (m_rfTGHang.hasExpired()) { + m_rfTGHang.stop(); + if (m_verbose) { + LogMessage(LOG_RF, "talkgroup hang has expired, lastDstId = %u", m_rfLastDstId); + } + m_rfLastDstId = 0U; + } + } + + if (m_netState == RS_NET_AUDIO) { + m_networkWatchdog.clock(ms); + + if (m_networkWatchdog.hasExpired()) { + if (m_netState == RS_NET_AUDIO) { + ::ActivityLog("NXDN", false, "network watchdog has expired, %.1f seconds, %u%% packet loss", + float(m_voice->m_netFrames) / 50.0F, (m_voice->m_netLost * 100U) / m_voice->m_netFrames); + } + else { + ::ActivityLog("NXDN", false, "network watchdog has expired"); + } + + m_networkWatchdog.stop(); + + if (m_network != NULL) + m_network->resetNXDN(); + + m_netState = RS_NET_IDLE; + + m_netTimeout.stop(); + + writeEndNet(); + } + } + + // reset states if we're in a rejected state + if (m_rfState == RS_RF_REJECTED) { + m_queue.clear(); + + m_voice->resetRF(); + m_voice->resetNet(); + + m_data->resetRF(); + + if (m_network != NULL) + m_network->resetNXDN(); + + m_rfState = RS_RF_LISTENING; + } +} + +/// +/// Flag indicating whether the process or is busy or not. +/// +/// True, if processor is busy, otherwise false. +bool Control::isBusy() const +{ + return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE; +} + +/// +/// Helper to change the debug and verbose state. +/// +/// Flag indicating whether NXDN debug is enabled. +/// Flag indicating whether NXDN verbose logging is enabled. +void Control::setDebugVerbose(bool debug, bool verbose) +{ + m_debug = m_voice->m_debug = m_data->m_debug; + m_verbose = m_voice->m_verbose = m_data->m_verbose; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Add data frame to the data ring buffer. +/// +/// +/// +/// +void Control::addFrame(const uint8_t *data, uint32_t length, bool net) +{ + assert(data != NULL); + + if (!net) { + if (m_rfTimeout.isRunning() && m_rfTimeout.hasExpired()) + return; + } else { + if (m_netTimeout.isRunning() && m_netTimeout.hasExpired()) + return; + } + + uint32_t space = m_queue.freeSpace(); + if (space < (length + 1U)) { + if (!net) { + uint32_t queueLen = m_queue.length(); + m_queue.resize(queueLen + NXDN_FRAME_LENGTH_BYTES); + LogError(LOG_NXDN, "overflow in the NXDN queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_queue.length()); + return; + } + else { + LogError(LOG_NXDN, "overflow in the NXDN queue while writing network data; queue free is %u, needed %u", space, length); + return; + } + } + + if (m_debug) { + Utils::symbols("!!! *Tx NXDN", data + 2U, length - 2U); + } + + uint8_t len = length; + m_queue.addData(&len, 1U); + m_queue.addData(data, len); +} + +/// +/// Process a data frames from the network. +/// +void Control::processNetwork() +{ + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) + return; + + uint32_t length = 100U; + bool ret = false; + uint8_t* data = m_network->readNXDN(ret, length); + if (!ret) + return; + if (length == 0U) + return; + if (data == NULL) { + m_network->resetNXDN(); + return; + } + + m_networkWatchdog.start(); + + if (m_debug) { + Utils::dump(2U, "!!! *NXDN Network Frame", data, length); + } + + scrambler(data + 2U); + + channel::LICH lich; + bool valid = lich.decode(data + 2U); + + if (valid) + m_rfLastLICH = lich; + + uint8_t usc = m_rfLastLICH.getFCT(); + uint8_t option = m_rfLastLICH.getOption(); + + switch (usc) { + case NXDN_LICH_USC_UDCH: + ret = m_data->processNetwork(option, data, length); + break; + default: + ret = m_voice->processNetwork(usc, option, data, length); + break; + } + + delete data; +} + +/// +/// Helper to write RF end of frame data. +/// +void Control::writeEndRF() +{ + m_rfState = RS_RF_LISTENING; + + m_rfMask = 0x00U; + m_rfLayer3.reset(); + + m_rfTimeout.stop(); +} + +/// +/// Helper to write network end of frame data. +/// +void Control::writeEndNet() +{ + m_netState = RS_NET_IDLE; + + m_netMask = 0x00U; + m_netLayer3.reset(); + + m_netTimeout.stop(); + m_networkWatchdog.stop(); + + if (m_network != NULL) + m_network->resetP25(); +} + +/// +/// +/// +/// +void Control::scrambler(uint8_t* data) const +{ + assert(data != NULL); + + for (uint32_t i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) + data[i] ^= SCRAMBLER[i]; +} diff --git a/nxdn/Control.h b/nxdn/Control.h new file mode 100644 index 00000000..70a2c32f --- /dev/null +++ b/nxdn/Control.h @@ -0,0 +1,164 @@ +/** +* 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) 2015-2020 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 +* 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_CONTROL_H__) +#define __NXDN_CONTROL_H__ + +#include "Defines.h" +#include "nxdn/NXDNDefines.h" +#include "nxdn/channel/LICH.h" +#include "nxdn/data/Layer3.h" +#include "nxdn/packet/Voice.h" +#include "nxdn/packet/Data.h" +#include "network/BaseNetwork.h" +#include "network/RemoteControl.h" +#include "lookups/RSSIInterpolator.h" +#include "lookups/IdenTableLookup.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" +#include "modem/Modem.h" +#include "RingBuffer.h" +#include "Timer.h" +#include "yaml/Yaml.h" + +#include +#include + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + namespace packet { class HOST_SW_API Voice; } + namespace packet { class HOST_SW_API Data; } + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements core logic for handling NXDN. + // --------------------------------------------------------------------------- + + class 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); + /// Finalizes a instance of the Control class. + ~Control(); + + /// Resets the data states for the RF interface. + void reset(); + + /// Helper to set NXDN configuration options. + void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, + uint8_t channelId, uint32_t channelNo, bool printOptions); + + /// Process a data frame from the RF interface. + bool processFrame(uint8_t* data, uint32_t len); + /// Get frame data from data ring buffer. + uint32_t getFrame(uint8_t* data); + + /// Updates the processor by the passed number of milliseconds. + void clock(uint32_t ms); + + /// Flag indicating whether the processor or is busy or not. + bool isBusy() const; + + /// Helper to change the debug and verbose state. + void setDebugVerbose(bool debug, bool verbose); + + private: + friend class packet::Voice; + packet::Voice* m_voice; + friend class packet::Data; + packet::Data* m_data; + + uint32_t m_ran; + uint32_t m_timeout; + + modem::Modem* m_modem; + network::BaseNetwork* m_network; + + bool m_duplex; + + channel::LICH m_rfLastLICH; + data::Layer3 m_rfLayer3; + data::Layer3 m_netLayer3; + + uint8_t m_rfMask; + uint8_t m_netMask; + + lookups::IdenTableLookup* m_idenTable; + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupIdLookup* m_tidLookup; + + lookups::IdenTable m_idenEntry; + + RingBuffer m_queue; + + RPT_RF_STATE m_rfState; + uint32_t m_rfLastDstId; + RPT_NET_STATE m_netState; + uint32_t m_netLastDstId; + + Timer m_rfTimeout; + Timer m_rfTGHang; + Timer m_netTimeout; + Timer m_networkWatchdog; + + lookups::RSSIInterpolator* m_rssiMapper; + uint8_t m_rssi; + uint8_t m_maxRSSI; + uint8_t m_minRSSI; + uint32_t m_aveRSSI; + uint32_t m_rssiCount; + + bool m_verbose; + bool m_debug; + + /// Add data frame to the data ring buffer. + void addFrame(const uint8_t* data, uint32_t length, bool net = false); + + /// Process a data frames from the network. + void processNetwork(); + + /// + void scrambler(uint8_t* data) const; + + /// Helper to write RF end of frame data. + void writeEndRF(); + /// Helper to write network end of frame data. + void writeEndNet(); + }; +} // namespace nxdn + +#endif // __NXDN_CONTROL_H__ diff --git a/nxdn/Convolution.cpp b/nxdn/Convolution.cpp new file mode 100644 index 00000000..536f5fd9 --- /dev/null +++ b/nxdn/Convolution.cpp @@ -0,0 +1,192 @@ +/** +* 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) 2009-2016,2018,2021 by Jonathan Naylor G4KLX +* +* 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/Convolution.h" + +using namespace nxdn; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t BRANCH_TABLE1[] = { 0U, 0U, 0U, 0U, 2U, 2U, 2U, 2U }; +const uint8_t BRANCH_TABLE2[] = { 0U, 2U, 2U, 0U, 0U, 2U, 2U, 0U }; + +const uint32_t NUM_OF_STATES_D2 = 8U; +const uint32_t NUM_OF_STATES = 16U; +const uint32_t M = 4U; +const uint32_t K = 5U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the Convolution class. +/// +Convolution::Convolution() : + m_metrics1(NULL), + m_metrics2(NULL), + m_oldMetrics(NULL), + m_newMetrics(NULL), + m_decisions(NULL), + m_dp(NULL) +{ + m_metrics1 = new uint16_t[20U]; + m_metrics2 = new uint16_t[20U]; + m_decisions = new uint64_t[300U]; +} + +/// +/// Finalizes a instance of the Convolution class. +/// +Convolution::~Convolution() +{ + delete[] m_metrics1; + delete[] m_metrics2; + delete[] m_decisions; +} + +/// +/// +/// +void Convolution::start() +{ + ::memset(m_metrics1, 0x00U, NUM_OF_STATES * sizeof(uint16_t)); + ::memset(m_metrics2, 0x00U, NUM_OF_STATES * sizeof(uint16_t)); + + m_oldMetrics = m_metrics1; + m_newMetrics = m_metrics2; + m_dp = m_decisions; +} + +/// +/// +/// +/// +/// +uint32_t Convolution::chainback(uint8_t* out, uint32_t nBits) +{ + assert(out != NULL); + + uint32_t state = 0U; + + while (nBits-- > 0) { + --m_dp; + + uint32_t i = state >> (9 - K); + uint8_t bit = uint8_t(*m_dp >> i) & 1; + state = (bit << 7) | (state >> 1); + + WRITE_BIT(out, nBits, bit != 0U); + } + + uint32_t minCost = m_oldMetrics[0]; + + for (uint32_t i = 0U; i < NUM_OF_STATES; i++) { + if (m_oldMetrics[i] < minCost) + minCost = m_oldMetrics[i]; + } + + return minCost / (M >> 1); +} + +/// +/// +/// +/// +/// +void Convolution::decode(uint8_t s0, uint8_t s1) +{ + *m_dp = 0U; + + for (uint8_t i = 0U; i < NUM_OF_STATES_D2; i++) { + uint8_t j = i * 2U; + + uint16_t metric = std::abs(BRANCH_TABLE1[i] - s0) + std::abs(BRANCH_TABLE2[i] - s1); + + uint16_t m0 = m_oldMetrics[i] + metric; + uint16_t m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + (M - metric); + uint8_t decision0 = (m0 >= m1) ? 1U : 0U; + m_newMetrics[j + 0U] = decision0 != 0U ? m1 : m0; + + m0 = m_oldMetrics[i] + (M - metric); + m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + metric; + uint8_t decision1 = (m0 >= m1) ? 1U : 0U; + m_newMetrics[j + 1U] = decision1 != 0U ? m1 : m0; + + *m_dp |= (uint64_t(decision1) << (j + 1U)) | (uint64_t(decision0) << (j + 0U)); + } + + ++m_dp; + + assert((m_dp - m_decisions) <= 300); + + uint16_t* tmp = m_oldMetrics; + m_oldMetrics = m_newMetrics; + m_newMetrics = tmp; +} + +/// +/// +/// +/// +/// +/// +void Convolution::encode(const uint8_t* in, uint8_t* out, uint32_t nBits) const +{ + assert(in != NULL); + assert(out != NULL); + assert(nBits > 0U); + + uint8_t d1 = 0U, d2 = 0U, d3 = 0U, d4 = 0U; + uint32_t k = 0U; + for (uint32_t i = 0U; i < nBits; i++) { + uint8_t d = READ_BIT(in, i) ? 1U : 0U; + + uint8_t g1 = (d + d3 + d4) & 1; + uint8_t g2 = (d + d1 + d2 + d4) & 1; + + d4 = d3; + d3 = d2; + d2 = d1; + d1 = d; + + WRITE_BIT(out, k, g1 != 0U); + k++; + + WRITE_BIT(out, k, g2 != 0U); + k++; + } +} diff --git a/nxdn/Convolution.h b/nxdn/Convolution.h new file mode 100644 index 00000000..42431cc2 --- /dev/null +++ b/nxdn/Convolution.h @@ -0,0 +1,74 @@ +/** +* 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) 2015,2016,2018,2021 by Jonathan Naylor G4KLX +* +* 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_CONVOLUTION_H__) +#define __NXDN_CONVOLUTION_H__ + +#include "Defines.h" + +#include + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN frame convolution processing. + // --------------------------------------------------------------------------- + + class HOST_SW_API Convolution { + public: + /// Initializes a new instance of the Convolution class. + Convolution(); + /// Finalizes a instance of the Convolution class. + ~Convolution(); + + /// + void start(); + /// + uint32_t chainback(uint8_t* out, uint32_t nBits); + + /// + void decode(uint8_t s0, uint8_t s1); + /// + void encode(const uint8_t* in, uint8_t* out, uint32_t nBits) const; + + private: + uint16_t* m_metrics1; + uint16_t* m_metrics2; + + uint16_t* m_oldMetrics; + uint16_t* m_newMetrics; + + uint64_t* m_decisions; + + uint64_t* m_dp; + }; +} // namespace nxdn + +#endif // __NXDN_CONVOLUTION_H__ diff --git a/nxdn/NXDNDefines.h b/nxdn/NXDNDefines.h new file mode 100644 index 00000000..d24b6ca2 --- /dev/null +++ b/nxdn/NXDNDefines.h @@ -0,0 +1,134 @@ +/** +* 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) 2016,2017,2018 by Jonathan Naylor G4KLX +* +* 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_DEFINES_H__) +#define __NXDN_DEFINES_H__ + +#include "Defines.h" + +// Message Type String(s) +#define NXDN_MESSAGE_TYPE_VCALL "MESSAGE_TYPE_VCALL (NXDN Voice Call)" +#define NXDN_MESSAGE_TYPE_TX_REL "MESSAGE_TYPE_TX_REL (NXDN Transmit Release)" +#define NXDN_MESSAGE_TYPE_DCALL "MESSAGE_TYPE_VCALL (NXDN Data Call)" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t NXDN_FRAME_LENGTH_BITS = 384U; + const uint32_t NXDN_FRAME_LENGTH_BYTES = NXDN_FRAME_LENGTH_BITS / 8U; + const uint32_t NXDN_FRAME_LENGTH_SYMBOLS = NXDN_FRAME_LENGTH_BITS / 2U; + + const uint32_t NXDN_FSW_LENGTH_BITS = 20U; + const uint32_t NXDN_FSW_LENGTH_SYMBOLS = NXDN_FSW_LENGTH_BITS / 2U; + + const uint8_t NXDN_FSW_BYTES[] = { 0xCDU, 0xF5U, 0x90U }; + const uint8_t NXDN_FSW_BYTES_MASK[] = { 0xFFU, 0xFFU, 0xF0U }; + const uint32_t NXDN_FSW_BYTES_LENGTH = 3U; + + const uint32_t NXDN_LICH_LENGTH_BITS = 16U; + const uint32_t NXDN_LICH_LENGTH_BYTES = NXDN_LICH_LENGTH_BITS / 8U; + + const uint32_t NXDN_SACCH_LENGTH_BITS = 60U; + const uint32_t NXDN_SACCH_LENGTH_BYTES = NXDN_SACCH_LENGTH_BITS / 8U; + const uint32_t NXDN_FACCH1_LENGTH_BITS = 144U; + const uint32_t NXDN_FACCH1_LENGTH_BYTES = NXDN_FACCH1_LENGTH_BITS / 8U; + const uint32_t NXDN_FACCH2_LENGTH_BITS = 348U; + const uint32_t NXDN_FACCH2_LENGTH_BYTES = NXDN_FACCH2_LENGTH_BITS / 8U; + + const uint32_t NXDN_FSW_LICH_SACCH_LENGTH_BITS = NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS; + const uint32_t NXDN_FSW_LICH_SACCH_LENGTH_BYTES = NXDN_FSW_LICH_SACCH_LENGTH_BITS / 8U; + + const uint8_t NXDN_LICH_RFCT_RCCH = 0U; + const uint8_t NXDN_LICH_RFCT_RTCH = 1U; + const uint8_t NXDN_LICH_RFCT_RDCH = 2U; + const uint8_t NXDN_LICH_RFCT_RTCH_C = 3U; + + const uint8_t NXDN_LICH_USC_SACCH_NS = 0U; + const uint8_t NXDN_LICH_USC_UDCH = 1U; + const uint8_t NXDN_LICH_USC_SACCH_SS = 2U; + const uint8_t NXDN_LICH_USC_SACCH_SS_IDLE = 3U; + + const uint8_t NXDN_LICH_STEAL_NONE = 3U; + const uint8_t NXDN_LICH_STEAL_FACCH1_2 = 2U; + const uint8_t NXDN_LICH_STEAL_FACCH1_1 = 1U; + const uint8_t NXDN_LICH_STEAL_FACCH = 0U; + + const uint8_t NXDN_LICH_DIRECTION_INBOUND = 0U; + const uint8_t NXDN_LICH_DIRECTION_OUTBOUND = 1U; + + const uint8_t NXDN_SR_SINGLE = 0U; + const uint8_t NXDN_SR_4_4 = 0U; + const uint8_t NXDN_SR_3_4 = 1U; + const uint8_t NXDN_SR_2_4 = 2U; + const uint8_t NXDN_SR_1_4 = 3U; + + const uint32_t DEFAULT_SILENCE_THRESHOLD = 14U; + const uint32_t MAX_NXDN_VOICE_ERRORS = 144U; + const uint32_t MAX_NXDN_VOICE_ERRORS_STEAL = 94U; + + // Message Types + const uint8_t MESSAGE_TYPE_VCALL = 0x01U; + const uint8_t MESSAGE_TYPE_VCALL_IV = 0x03U; + const uint8_t MESSAGE_TYPE_DCALL_HDR = 0x09U; + const uint8_t MESSAGE_TYPE_DCALL_DATA = 0x0BU; + const uint8_t MESSAGE_TYPE_DCALL_ACK = 0x0CU; + const uint8_t MESSAGE_TYPE_TX_REL = 0x08U; + const uint8_t MESSAGE_TYPE_HEAD_DLY = 0x0FU; + const uint8_t MESSAGE_TYPE_SDCALL_REQ_HDR = 0x38U; + const uint8_t MESSAGE_TYPE_SDCALL_REQ_DATA = 0x39U; + const uint8_t MESSAGE_TYPE_SDCALL_RESP = 0x3BU; + const uint8_t MESSAGE_TYPE_SDCALL_IV = 0x3AU; + const uint8_t MESSAGE_TYPE_STAT_INQ_REQ = 0x30U; + const uint8_t MESSAGE_TYPE_STAT_INQ_RESP = 0x31U; + const uint8_t MESSAGE_TYPE_STAT_REQ = 0x32U; + const uint8_t MESSAGE_TYPE_STAT_RESP = 0x33U; + const uint8_t MESSAGE_TYPE_REM_CON_REQ = 0x34U; + const uint8_t MESSAGE_TYPE_REM_CON_RESP = 0x35U; + const uint8_t MESSAGE_TYPE_IDLE = 0x10U; + const uint8_t MESSAGE_TYPE_AUTH_INQ_REQ = 0x28U; + const uint8_t MESSAGE_TYPE_AUTH_INQ_RESP = 0x29U; + const uint8_t MESSAGE_TYPE_PROP_FORM = 0x3FU; + + // Voice Call Options + const uint8_t VOICE_CALL_OPTION_HALF_DUPLEX = 0x00U; + const uint8_t VOICE_CALL_OPTION_DUPLEX = 0x10U; + + // Data Call Options + const uint8_t DATA_CALL_OPTION_HALF_DUPLEX = 0x00U; + const uint8_t DATA_CALL_OPTION_DUPLEX = 0x10U; + const uint8_t DATA_CALL_OPTION_4800 = 0x00U; + const uint8_t DATA_CALL_OPTION_9600 = 0x02U; + + const uint8_t SACCH_IDLE[] = { MESSAGE_TYPE_IDLE, 0x00U, 0x00U }; +} // namespace nxdn + +#endif // __NXDN_DEFINES_H__ diff --git a/nxdn/Sync.cpp b/nxdn/Sync.cpp new file mode 100644 index 00000000..988acf5a --- /dev/null +++ b/nxdn/Sync.cpp @@ -0,0 +1,54 @@ +/** +* 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) 2020 by Jonathan Naylor G4KLX +* +* 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/Sync.h" + +using namespace nxdn; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +/// +/// Helper to append NXDN sync bytes to the passed buffer. +/// +/// +void Sync::addNXDNSync(uint8_t* data) + { + assert(data != NULL); + + for (uint32_t i = 0U; i < NXDN_FSW_BYTES_LENGTH; i++) + data[i] = (data[i] & ~NXDN_FSW_BYTES_MASK[i]) | NXDN_FSW_BYTES[i]; +} diff --git a/nxdn/Sync.h b/nxdn/Sync.h new file mode 100644 index 00000000..0ff682be --- /dev/null +++ b/nxdn/Sync.h @@ -0,0 +1,49 @@ +/** +* 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) 2020 by Jonathan Naylor G4KLX +* +* 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_SYNC_H__) +#define __NXDN_SYNC_H__ + +#include "Defines.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Helper class for generating NXDN sync data. + // --------------------------------------------------------------------------- + + class HOST_SW_API Sync { + public: + /// Helper to append NXDN sync bytes to the passed buffer. + static void addNXDNSync(uint8_t* data); + }; +} // namespace nxdn + +#endif // __NXDN_SYNC_H__ diff --git a/nxdn/acl/AccessControl.cpp b/nxdn/acl/AccessControl.cpp new file mode 100644 index 00000000..3f50ed37 --- /dev/null +++ b/nxdn/acl/AccessControl.cpp @@ -0,0 +1,107 @@ +/** +* 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) 2016 by Simon Rune G7RZU +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2019,2022 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 +*/ +#include "Defines.h" +#include "nxdn/acl/AccessControl.h" +#include "Log.h" + +using namespace nxdn::acl; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +RadioIdLookup* AccessControl::m_ridLookup; +TalkgroupIdLookup* AccessControl::m_tidLookup; + +/// +/// Initializes the P25 access control. +/// +/// Instance of the RadioIdLookup class. +/// Instance of the TalkgroupIdLookup class. +void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup) +{ + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; +} + +/// +/// Helper to validate a source radio ID. +/// +/// Source Radio ID. +/// True, if source radio ID is valid, otherwise false. +bool AccessControl::validateSrcId(uint32_t id) +{ + // check if RID ACLs are enabled + if (m_ridLookup->getACL() == false) { + RadioId rid = m_ridLookup->find(id); + if (!rid.radioDefault() && !rid.radioEnabled()) { + return false; + } + + return true; + } + + // lookup RID and perform test for validity + RadioId rid = m_ridLookup->find(id); + if (!rid.radioEnabled()) + return false; + + return true; +} + +/// +/// Helper to validate a talkgroup ID. +/// +/// DMR slot number. +/// Talkgroup ID. +/// True, if talkgroup ID is valid, otherwise false. +bool AccessControl::validateTGId(uint32_t id) +{ + // TG0 is never valid + if (id == 0U) + return false; + + // check if TID ACLs are enabled + if (m_tidLookup->getACL() == false) { + return true; + } + + // lookup TID and perform test for validity + TalkgroupId tid = m_tidLookup->find(id); + if (!tid.tgEnabled()) + return false; + + return true; +} diff --git a/nxdn/acl/AccessControl.h b/nxdn/acl/AccessControl.h new file mode 100644 index 00000000..eba0b8a9 --- /dev/null +++ b/nxdn/acl/AccessControl.h @@ -0,0 +1,66 @@ +/** +* 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) 2016 by Simon Rune G7RZU +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2019 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 +*/ +#if !defined(__NXDN_ACL__ACCESS_CONTROL_H__) +#define __NXDN_ACL__ACCESS_CONTROL_H__ + +#include "Defines.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +namespace nxdn +{ + namespace acl + { + using namespace ::lookups; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements radio and talkgroup ID access control checking. + // --------------------------------------------------------------------------- + + class HOST_SW_API AccessControl { + public: + /// Initializes the P25 access control. + static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup); + + /// Helper to validate a source radio ID. + static bool validateSrcId(uint32_t id); + /// Helper to validate a talkgroup ID. + static bool validateTGId(uint32_t id); + + private: + static RadioIdLookup* m_ridLookup; + static TalkgroupIdLookup* m_tidLookup; + }; + } // namespace ACL +} // namespace nxdn + +#endif // __NXDN_ACL__ACCESS_CONTROL_H__ diff --git a/nxdn/channel/FACCH1.cpp b/nxdn/channel/FACCH1.cpp new file mode 100644 index 00000000..80a5664c --- /dev/null +++ b/nxdn/channel/FACCH1.cpp @@ -0,0 +1,256 @@ +/** +* 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) 2018 by Jonathan Naylor G4KLX +* +* 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/channel/FACCH1.h" +#include "nxdn/Convolution.h" +#include "nxdn/NXDNDefines.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace edac; +using namespace nxdn; +using namespace nxdn::channel; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t INTERLEAVE_TABLE[] = { + 0U, 9U, 18U, 27U, 36U, 45U, 54U, 63U, 72U, 81U, 90U, 99U, 108U, 117U, 126U, 135U, + 1U, 10U, 19U, 28U, 37U, 46U, 55U, 64U, 73U, 82U, 91U, 100U, 109U, 118U, 127U, 136U, + 2U, 11U, 20U, 29U, 38U, 47U, 56U, 65U, 74U, 83U, 92U, 101U, 110U, 119U, 128U, 137U, + 3U, 12U, 21U, 30U, 39U, 48U, 57U, 66U, 75U, 84U, 93U, 102U, 111U, 120U, 129U, 138U, + 4U, 13U, 22U, 31U, 40U, 49U, 58U, 67U, 76U, 85U, 94U, 103U, 112U, 121U, 130U, 139U, + 5U, 14U, 23U, 32U, 41U, 50U, 59U, 68U, 77U, 86U, 95U, 104U, 113U, 122U, 131U, 140U, + 6U, 15U, 24U, 33U, 42U, 51U, 60U, 69U, 78U, 87U, 96U, 105U, 114U, 123U, 132U, 141U, + 7U, 16U, 25U, 34U, 43U, 52U, 61U, 70U, 79U, 88U, 97U, 106U, 115U, 124U, 133U, 142U, + 8U, 17U, 26U, 35U, 44U, 53U, 62U, 71U, 80U, 89U, 98U, 107U, 116U, 125U, 134U, 143U }; + +const uint32_t PUNCTURE_LIST[] = { + 1U, 5U, 9U, 13U, 17U, 21U, 25U, 29U, 33U, 37U, + 41U, 45U, 49U, 53U, 57U, 61U, 65U, 69U, 73U, 77U, + 81U, 85U, 89U, 93U, 97U, 101U, 105U, 109U, 113U, 117U, + 121U, 125U, 129U, 133U, 137U, 141U, 145U, 149U, 153U, 157U, + 161U, 165U, 169U, 173U, 177U, 181U, 185U, 189U }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the FACCH1 class. +/// +/// +FACCH1::FACCH1(const FACCH1& data) : + m_verbose(false), + m_data(NULL) +{ + m_data = new uint8_t[12U]; + ::memcpy(m_data, data.m_data, 12U); +} + +/// +/// Initializes a new instance of the FACCH1 class. +/// +FACCH1::FACCH1() : + m_verbose(false), + m_data(NULL) +{ + m_data = new uint8_t[12U]; +} + +/// +/// Finalizes a instance of FACCH1 class. +/// +FACCH1::~FACCH1() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +FACCH1& FACCH1::operator=(const FACCH1& data) +{ + if (&data != this) { + ::memcpy(m_data, data.m_data, 12U); + + m_verbose = data.m_verbose; + } + + return *this; +} + +/// +/// Decode a fast associated control channel 1. +/// +/// +/// True, if FACCH1 was decoded, otherwise false. +bool FACCH1::decode(const uint8_t* data, uint32_t offset) +{ + assert(data != NULL); + + uint8_t buffer[NXDN_FACCH1_LENGTH_BYTES]; + for (uint32_t i = 0U; i < NXDN_FACCH1_LENGTH_BITS; i++) { + uint32_t n = INTERLEAVE_TABLE[i] + offset; + bool b = READ_BIT(data, n); + WRITE_BIT(buffer, i, b); + } + +#if DEBUG_NXDN_FACCH1 + Utils::dump(2U, "FACCH1::decode(), FACCH1 Raw", buffer, NXDN_FACCH1_LENGTH_BYTES); +#endif + + // deinterleave + uint8_t interleave[210U]; + uint32_t n = 0U; + uint32_t index = 0U; + for (uint32_t i = 0U; i < NXDN_FACCH1_LENGTH_BITS; i++) { + if (n == PUNCTURE_LIST[index]) { + interleave[n++] = 1U; + index++; + } + + bool b = READ_BIT(buffer, i); + interleave[n++] = b ? 2U : 0U; + } + + for (uint32_t i = 0U; i < 8U; i++) { + interleave[n++] = 0U; + } + + // decode convolution + Convolution conv; + conv.start(); + + n = 0U; + for (uint32_t i = 0U; i < 100U; i++) { + uint8_t s0 = interleave[n++]; + uint8_t s1 = interleave[n++]; + + conv.decode(s0, s1); + } + + conv.chainback(m_data, 96U); + + if (m_verbose) { + Utils::dump(2U, "Decoded FACCH1", m_data, 12U); + } + + // check CRC-12 + bool ret = CRC::checkCRC12(m_data, 80U); + if (!ret) { + LogError(LOG_NXDN, "FACCH1::decode(), failed CRC-12 check"); + return false; + } + + return true; +} + +/// +/// Encode a fast associated control channel 1. +/// +/// +/// True, if LICH was decoded, otherwise false. +void FACCH1::encode(uint8_t* data, uint32_t offset) const +{ + assert(data != NULL); + + if (m_verbose) { + Utils::dump(2U, "Encoded FACCH1", m_data, 12U); + } + + uint8_t buffer[12U]; + ::memset(buffer, 0x00U, 12U); + ::memcpy(buffer, m_data, 10U); + + CRC::addCRC12(buffer, 80U); + + // encode convolution + uint8_t convolution[24U]; + Convolution conv; + conv.encode(buffer, convolution, 96U); + +#if DEBUG_NXDN_FACCH1 + Utils::dump(2U, "FACCH1::encode(), FACCH1 Convolution", convolution, 24U); +#endif + + // interleave and puncture + uint8_t raw[18U]; + uint32_t n = 0U; + uint32_t index = 0U; + for (uint32_t i = 0U; i < 192U; i++) { + if (i != PUNCTURE_LIST[index]) { + bool b = READ_BIT(convolution, i); + WRITE_BIT(raw, n, b); + n++; + } else { + index++; + } + } + + for (uint32_t i = 0U; i < NXDN_FACCH1_LENGTH_BITS; i++) { + uint32_t n = INTERLEAVE_TABLE[i] + offset; + bool b = READ_BIT(raw, i); + WRITE_BIT(data, n, b); + } + +#if DEBUG_NXDN_SACCH + Utils::dump(2U, "FACCH1::encode(), FACCH1 Interleave", raw, 18U); +#endif +} + +/// +/// Gets the raw FACCH1 data. +/// +/// +void FACCH1::getData(uint8_t* data) const +{ + assert(data != NULL); + + ::memcpy(data, m_data, 10U); +} + +/// +/// Sets the raw FACCH1 data. +/// +/// +void FACCH1::setData(const uint8_t* data) +{ + assert(data != NULL); + + ::memcpy(m_data, data, 10U); +} diff --git a/nxdn/channel/FACCH1.h b/nxdn/channel/FACCH1.h new file mode 100644 index 00000000..c2f95a0d --- /dev/null +++ b/nxdn/channel/FACCH1.h @@ -0,0 +1,76 @@ +/** +* 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) 2018 by Jonathan Naylor G4KLX +* +* 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_FACCH1_H__) +#define __NXDN_FACCH1_H__ + +#include "Defines.h" + +namespace nxdn +{ + namespace channel + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN Fast Associated Control Channel 1. + // --------------------------------------------------------------------------- + + class HOST_SW_API FACCH1 { + public: + /// Initializes a copy instance of the FACCH1 class. + FACCH1(const FACCH1& data); + /// Initializes a new instance of the FACCH1 class. + FACCH1(); + /// Finalizes a instance of the FACCH1 class. + ~FACCH1(); + + /// Equals operator. + FACCH1& operator=(const FACCH1& data); + + /// Decode a fast associated control channel 1. + bool decode(const uint8_t* data, uint32_t offset); + /// Encode a fast associated control channel 1. + void encode(uint8_t* data, uint32_t offset) const; + + /// Gets the raw FACCH1 data. + void getData(uint8_t* data) const; + /// Sets the raw FACCH1 data. + void setData(const uint8_t* data); + + public: + /// Flag indicating verbose log output. + __PROPERTY(bool, verbose, Verbose); + + private: + uint8_t* m_data; + }; + } // namespace channel +} // namespace nxdn + +#endif // __NXDN_FACCH1_H__ diff --git a/nxdn/channel/LICH.cpp b/nxdn/channel/LICH.cpp new file mode 100644 index 00000000..cb0b017a --- /dev/null +++ b/nxdn/channel/LICH.cpp @@ -0,0 +1,184 @@ +/** +* 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) 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 +* 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/channel/LICH.h" + +using namespace nxdn; +using namespace nxdn::channel; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the LICH class. +/// +/// +LICH::LICH(const LICH& data) : + m_rfct(NXDN_LICH_RFCT_RCCH), + m_fct(NXDN_LICH_USC_SACCH_NS), + m_option(0U), + m_direction(NXDN_LICH_DIRECTION_OUTBOUND), + m_data(NULL) +{ + m_data = new uint8_t[1U]; + m_data[0U] = data.m_data[0U]; + + m_rfct = (m_data[0U] >> 6) & 0x03U; + m_fct = (m_data[0U] >> 4) & 0x03U; + m_option = (m_data[0U] >> 2) & 0x03U; + m_direction = (m_data[0U] >> 1) & 0x01U; +} + +/// +/// Initializes a new instance of the LICH class. +/// +LICH::LICH() : + m_rfct(NXDN_LICH_RFCT_RCCH), + m_fct(NXDN_LICH_USC_SACCH_NS), + m_option(0U), + m_direction(NXDN_LICH_DIRECTION_OUTBOUND), + m_data(NULL) +{ + m_data = new uint8_t[1U]; +} + +/// +/// Finalizes a instance of LICH class. +/// +LICH::~LICH() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +LICH& LICH::operator=(const LICH& data) +{ + if (&data != this) { + m_data[0U] = data.m_data[0U]; + + m_rfct = data.m_rfct; + m_fct = data.m_fct; + m_option = data.m_option; + m_direction = data.m_direction; + } + + return *this; +} + +/// +/// Decode a link information channel. +/// +/// +/// True, if LICH was decoded, otherwise false. +bool LICH::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint32_t offset = NXDN_FSW_LENGTH_BITS; + for (uint32_t i = 0U; i < (NXDN_LICH_LENGTH_BITS / 2U); i++, offset += 2U) { + bool b = READ_BIT(data, offset); + WRITE_BIT(m_data, i, b); + } + + bool newParity = getParity(); + bool origParity = (m_data[0U] & 0x01U) == 0x01U; + + m_rfct = (m_data[0U] >> 6) & 0x03U; + m_fct = (m_data[0U] >> 4) & 0x03U; + m_option = (m_data[0U] >> 2) & 0x03U; + m_direction = (m_data[0U] >> 1) & 0x01U; + + return origParity == newParity; +} + +/// +/// Encode a link information channel. +/// +/// +void LICH::encode(uint8_t* data) +{ + assert(data != NULL); + + m_data[0U] &= 0x3FU; + m_data[0U] |= (m_rfct << 6) & 0xC0U; + + m_data[0U] &= 0xCFU; + m_data[0U] |= (m_fct << 4) & 0x30U; + + m_data[0U] &= 0xF3U; + m_data[0U] |= (m_option << 2) & 0x0CU; + + m_data[0U] &= 0xFDU; + m_data[0U] |= (m_direction << 1) & 0x02U; + + bool parity = getParity(); + if (parity) + m_data[0U] |= 0x01U; + else + m_data[0U] &= 0xFEU; + + uint32_t offset = NXDN_FSW_LENGTH_BITS; + for (uint32_t i = 0U; i < (NXDN_LICH_LENGTH_BITS / 2U); i++) { + bool b = READ_BIT(m_data, i); + WRITE_BIT(data, offset, b); + offset++; + WRITE_BIT(data, offset, true); + offset++; + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// +/// +/// +bool LICH::getParity() const +{ + switch (m_data[0U] & 0xF0U) { + case 0x80U: + case 0xB0U: + return true; + default: + return false; + } +} diff --git a/nxdn/channel/LICH.h b/nxdn/channel/LICH.h new file mode 100644 index 00000000..5de73146 --- /dev/null +++ b/nxdn/channel/LICH.h @@ -0,0 +1,82 @@ +/** +* 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) 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 +* 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_LICH_H__) +#define __NXDN_LICH_H__ + +#include "Defines.h" + +namespace nxdn +{ + namespace channel + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN Link Information Channel. + // --------------------------------------------------------------------------- + + class HOST_SW_API LICH { + public: + /// Initializes a copy instance of the LICH class. + LICH(const LICH& lich); + /// Initializes a new instance of the LICH class. + LICH(); + /// Finalizes a instance of the LICH class. + ~LICH(); + + /// Equals operator. + LICH& operator=(const LICH& lich); + + /// Decode a link information channel. + bool decode(const uint8_t* data); + /// Encode a link information channel. + void encode(uint8_t* data); + + public: + /** Common Data */ + /// RF Channel Type + __PROPERTY(uint8_t, rfct, RFCT); + /// Functional Channel Type + __PROPERTY(uint8_t, fct, FCT); + /// Channel Options + __PROPERTY(uint8_t, option, Option); + /// Channel Direction + __PROPERTY(uint8_t, direction, Direction); + + private: + uint8_t* m_data; + + /// + bool getParity() const; + }; + } // namespace channel +} // namespace nxdn + +#endif // __NXDN_LICH_H__ diff --git a/nxdn/channel/SACCH.cpp b/nxdn/channel/SACCH.cpp new file mode 100644 index 00000000..c8a75597 --- /dev/null +++ b/nxdn/channel/SACCH.cpp @@ -0,0 +1,282 @@ +/** +* 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) 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 +* 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/channel/SACCH.h" +#include "nxdn/Convolution.h" +#include "nxdn/NXDNDefines.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace edac; +using namespace nxdn; +using namespace nxdn::channel; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t INTERLEAVE_TABLE[] = { + 0U, 5U, 10U, 15U, 20U, 25U, 30U, 35U, 40U, 45U, 50U, 55U, + 1U, 6U, 11U, 16U, 21U, 26U, 31U, 36U, 41U, 46U, 51U, 56U, + 2U, 7U, 12U, 17U, 22U, 27U, 32U, 37U, 42U, 47U, 52U, 57U, + 3U, 8U, 13U, 18U, 23U, 28U, 33U, 38U, 43U, 48U, 53U, 58U, + 4U, 9U, 14U, 19U, 24U, 29U, 34U, 39U, 44U, 49U, 54U, 59U +}; + +const uint32_t PUNCTURE_LIST[] = { 5U, 11U, 17U, 23U, 29U, 35U, 41U, 47U, 53U, 59U, 65U, 71U }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the SACCH class. +/// +/// +SACCH::SACCH(const SACCH& data) : + m_verbose(false), + m_ran(0U), + m_structure(0U), + m_data(NULL) +{ + m_data = new uint8_t[5U]; + ::memcpy(m_data, data.m_data, 5U); + + m_ran = m_data[0U] & 0x3FU; + m_structure = (m_data[0U] >> 6) & 0x03U; +} + +/// +/// Initializes a new instance of the SACCH class. +/// +SACCH::SACCH() : + m_verbose(false), + m_ran(0U), + m_structure(0U), + m_data(NULL) +{ + m_data = new uint8_t[5U]; +} + +/// +/// Finalizes a instance of SACCH class. +/// +SACCH::~SACCH() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +SACCH& SACCH::operator=(const SACCH& data) +{ + if (&data != this) { + ::memcpy(m_data, data.m_data, 5U); + + m_verbose = data.m_verbose; + + m_ran = m_data[0U] & 0x3FU; + m_structure = (m_data[0U] >> 6) & 0x03U; + } + + return *this; +} + +/// +/// Decode a slow associated control channel. +/// +/// +/// True, if SACCH was decoded, otherwise false. +bool SACCH::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint8_t buffer[NXDN_SACCH_LENGTH_BYTES + 1U]; + for (uint32_t i = 0U; i < NXDN_SACCH_LENGTH_BITS; i++) { + uint32_t n = INTERLEAVE_TABLE[i] + NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS; + bool b = READ_BIT(data, n); + WRITE_BIT(buffer, i, b); + } + +#if DEBUG_NXDN_SACCH + Utils::dump(2U, "SACCH::decode(), SACCH Raw", buffer, NXDN_SACCH_LENGTH_BYTES + 1U); +#endif + + // deinterleave + uint8_t interleave[90U]; + uint32_t n = 0U; + uint32_t index = 0U; + for (uint32_t i = 0U; i < NXDN_SACCH_LENGTH_BITS; i++) { + if (n == PUNCTURE_LIST[index]) { + interleave[n++] = 1U; + index++; + } + + bool b = READ_BIT(buffer, i); + interleave[n++] = b ? 2U : 0U; + } + + for (uint32_t i = 0U; i < 8U; i++) { + interleave[n++] = 0U; + } + + // decode convolution + Convolution conv; + conv.start(); + + n = 0U; + for (uint32_t i = 0U; i < 40U; i++) { + uint8_t s0 = interleave[n++]; + uint8_t s1 = interleave[n++]; + + conv.decode(s0, s1); + } + + conv.chainback(m_data, 36U); + + if (m_verbose) { + Utils::dump(2U, "Decoded SACCH", m_data, 5U); + } + + // check CRC-6 + bool ret = CRC::checkCRC6(m_data, 26U); + if (!ret) { + LogError(LOG_NXDN, "SACCH::decode(), failed CRC-6 check"); + return false; + } + + m_ran = m_data[0U] & 0x3FU; + m_structure = (m_data[0U] >> 6) & 0x03U; + + return true; +} + +/// +/// Encode a slow associated control channel. +/// +/// +void SACCH::encode(uint8_t* data) const +{ + assert(data != NULL); + + m_data[0U] &= 0xC0U; + m_data[0U] |= m_ran; + + m_data[0U] &= 0x3FU; + m_data[0U] |= (m_structure << 6) & 0xC0U; + + if (m_verbose) { + Utils::dump(2U, "Encoded SACCH", m_data, 5U); + } + + uint8_t buffer[5U]; + ::memset(buffer, 0x00U, 5U); + + for (uint32_t i = 0U; i < 26U; i++) { + bool b = READ_BIT(m_data, i); + WRITE_BIT(buffer, i, b); + } + + CRC::addCRC6(buffer, 26U); + + // encode convolution + uint8_t convolution[9U]; + Convolution conv; + conv.encode(buffer, convolution, 36U); + +#if DEBUG_NXDN_SACCH + Utils::dump(2U, "SACCH::encode(), SACCH Convolution", convolution, 9U); +#endif + + // interleave and puncture + uint8_t raw[8U]; + uint32_t n = 0U; + uint32_t index = 0U; + for (uint32_t i = 0U; i < 72U; i++) { + if (i != PUNCTURE_LIST[index]) { + bool b = READ_BIT(convolution, i); + WRITE_BIT(raw, n, b); + n++; + } else { + index++; + } + } + + for (uint32_t i = 0U; i < NXDN_SACCH_LENGTH_BITS; i++) { + uint32_t n = INTERLEAVE_TABLE[i] + NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS; + bool b = READ_BIT(raw, i); + WRITE_BIT(data, n, b); + } + +#if DEBUG_NXDN_SACCH + Utils::dump(2U, "SACCH::encode(), SACCH Interleave", raw, 8U); +#endif +} + +/// +/// Gets the raw SACCH data. +/// +/// +void SACCH::getData(uint8_t* data) const +{ + assert(data != NULL); + + uint32_t offset = 8U; + for (uint32_t i = 0U; i < 18U; i++, offset++) { + bool b = READ_BIT(m_data, offset); + WRITE_BIT(data, i, b); + } +} + +/// +/// Sets the raw SACCH data. +/// +/// +void SACCH::setData(const uint8_t* data) +{ + assert(data != NULL); + + uint32_t offset = 8U; + for (uint32_t i = 0U; i < 18U; i++, offset++) { + 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/SACCH.h b/nxdn/channel/SACCH.h new file mode 100644 index 00000000..9212d8e7 --- /dev/null +++ b/nxdn/channel/SACCH.h @@ -0,0 +1,83 @@ +/** +* 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) 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 +* 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_SACCH_H__) +#define __NXDN_SACCH_H__ + +#include "Defines.h" + +namespace nxdn +{ + namespace channel + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN Slow Associated Control Channel. + // --------------------------------------------------------------------------- + + class HOST_SW_API SACCH { + public: + /// Initializes a copy instance of the SACCH class. + SACCH(const SACCH& data); + /// Initializes a new instance of the SACCH class. + SACCH(); + /// Finalizes a instance of the SACCH class. + ~SACCH(); + + /// Equals operator. + SACCH& operator=(const SACCH& data); + + /// Decode a slow associated control channel. + bool decode(const uint8_t* data); + /// Encode a slow associated control channel. + void encode(uint8_t* data) const; + + /// Gets the raw SACCH data. + void getData(uint8_t* data) const; + /// Sets the raw SACCH data. + void setData(const uint8_t* data); + + public: + /// Flag indicating verbose log output. + __PROPERTY(bool, verbose, Verbose); + + /** Common Data */ + /// Radio Access Number + __PROPERTY(uint8_t, ran, RAN); + /// + __PROPERTY(uint8_t, structure, Structure); + + private: + uint8_t* m_data; + }; + } // namespace channel +} // namespace nxdn + +#endif // __NXDN_SACCH_H__ diff --git a/nxdn/channel/UDCH.cpp b/nxdn/channel/UDCH.cpp new file mode 100644 index 00000000..ae89795f --- /dev/null +++ b/nxdn/channel/UDCH.cpp @@ -0,0 +1,289 @@ +/** +* 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) 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 +* 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/channel/UDCH.h" +#include "nxdn/Convolution.h" +#include "nxdn/NXDNDefines.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace edac; +using namespace nxdn; +using namespace nxdn::channel; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t INTERLEAVE_TABLE[] = { + 0U, 29U, 58U, 87U, 116U, 145U, 174U, 203U, 232U, 261U, 290U, 319U, + 1U, 30U, 59U, 88U, 117U, 146U, 175U, 204U, 233U, 262U, 291U, 320U, + 2U, 31U, 60U, 89U, 118U, 147U, 176U, 205U, 234U, 263U, 292U, 321U, + 3U, 32U, 61U, 90U, 119U, 148U, 177U, 206U, 235U, 264U, 293U, 322U, + 4U, 33U, 62U, 91U, 120U, 149U, 178U, 207U, 236U, 265U, 294U, 323U, + 5U, 34U, 63U, 92U, 121U, 150U, 179U, 208U, 237U, 266U, 295U, 324U, + 6U, 35U, 64U, 93U, 122U, 151U, 180U, 209U, 238U, 267U, 296U, 325U, + 7U, 36U, 65U, 94U, 123U, 152U, 181U, 210U, 239U, 268U, 297U, 326U, + 8U, 37U, 66U, 95U, 124U, 153U, 182U, 211U, 240U, 269U, 298U, 327U, + 9U, 38U, 67U, 96U, 125U, 154U, 183U, 212U, 241U, 270U, 299U, 328U, + 10U, 39U, 68U, 97U, 126U, 155U, 184U, 213U, 242U, 271U, 300U, 329U, + 11U, 40U, 69U, 98U, 127U, 156U, 185U, 214U, 243U, 272U, 301U, 330U, + 12U, 41U, 70U, 99U, 128U, 157U, 186U, 215U, 244U, 273U, 302U, 331U, + 13U, 42U, 71U, 100U, 129U, 158U, 187U, 216U, 245U, 274U, 303U, 332U, + 14U, 43U, 72U, 101U, 130U, 159U, 188U, 217U, 246U, 275U, 304U, 333U, + 15U, 44U, 73U, 102U, 131U, 160U, 189U, 218U, 247U, 276U, 305U, 334U, + 16U, 45U, 74U, 103U, 132U, 161U, 190U, 219U, 248U, 277U, 306U, 335U, + 17U, 46U, 75U, 104U, 133U, 162U, 191U, 220U, 249U, 278U, 307U, 336U, + 18U, 47U, 76U, 105U, 134U, 163U, 192U, 221U, 250U, 279U, 308U, 337U, + 19U, 48U, 77U, 106U, 135U, 164U, 193U, 222U, 251U, 280U, 309U, 338U, + 20U, 49U, 78U, 107U, 136U, 165U, 194U, 223U, 252U, 281U, 310U, 339U, + 21U, 50U, 79U, 108U, 137U, 166U, 195U, 224U, 253U, 282U, 311U, 340U, + 22U, 51U, 80U, 109U, 138U, 167U, 196U, 225U, 254U, 283U, 312U, 341U, + 23U, 52U, 81U, 110U, 139U, 168U, 197U, 226U, 255U, 284U, 313U, 342U, + 24U, 53U, 82U, 111U, 140U, 169U, 198U, 227U, 256U, 285U, 314U, 343U, + 25U, 54U, 83U, 112U, 141U, 170U, 199U, 228U, 257U, 286U, 315U, 344U, + 26U, 55U, 84U, 113U, 142U, 171U, 200U, 229U, 258U, 287U, 316U, 345U, + 27U, 56U, 85U, 114U, 143U, 172U, 201U, 230U, 259U, 288U, 317U, 346U, + 28U, 57U, 86U, 115U, 144U, 173U, 202U, 231U, 260U, 289U, 318U, 347U }; + +const uint32_t PUNCTURE_LIST[] = { + 3U, 11U, 17U, 25U, 31U, 39U, 45U, 53U, 59U, 67U, + 73U, 81U, 87U, 95U, 101U, 109U, 115U, 123U, 129U, 137U, + 143U, 151U, 157U, 165U, 171U, 179U, 185U, 193U, 199U, 207U, + 213U, 221U, 227U, 235U, 241U, 249U, 255U, 263U, 269U, 277U, + 283U, 291U, 297U, 305U, 311U, 319U, 325U, 333U, 339U, 347U, + 353U, 361U, 367U, 375U, 381U, 389U, 395U, 403U }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the UDCH class. +/// +/// +UDCH::UDCH(const UDCH& data) : + m_verbose(false), + m_ran(0U), + m_data(NULL) +{ + m_data = new uint8_t[26U]; + ::memcpy(m_data, data.m_data, 26U); + + m_ran = m_data[0U] & 0x3FU; +} + +/// +/// Initializes a new instance of the UDCH class. +/// +UDCH::UDCH() : + m_verbose(false), + m_ran(0U), + m_data(NULL) +{ + m_data = new uint8_t[26U]; +} + +/// +/// Finalizes a instance of UDCH class. +/// +UDCH::~UDCH() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +UDCH& UDCH::operator=(const UDCH& data) +{ + if (&data != this) { + ::memcpy(m_data, data.m_data, 26U); + + m_verbose = data.m_verbose; + + m_ran = m_data[0U] & 0x3FU; + } + + return *this; +} + +/// +/// Decode a user data channel. +/// +/// +/// True, if UDCH was decoded, otherwise false. +bool UDCH::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint8_t buffer[NXDN_FACCH2_LENGTH_BYTES + 1U]; + for (uint32_t i = 0U; i < NXDN_FACCH2_LENGTH_BITS; i++) { + uint32_t n = INTERLEAVE_TABLE[i] + NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS; + bool b = READ_BIT(data, n); + WRITE_BIT(buffer, i, b); + } + +#if DEBUG_NXDN_UDCH + Utils::dump(2U, "UDCH::decode(), UDCH Raw", buffer, NXDN_FACCH2_LENGTH_BYTES + 1U); +#endif + + // deinterleave + uint8_t interleave[420U]; + uint32_t n = 0U; + uint32_t index = 0U; + for (uint32_t i = 0U; i < NXDN_FACCH2_LENGTH_BITS; i++) { + if (n == PUNCTURE_LIST[index]) { + interleave[n++] = 1U; + index++; + } + + bool b = READ_BIT(buffer, i); + interleave[n++] = b ? 2U : 0U; + } + + for (uint32_t i = 0U; i < 8U; i++) { + interleave[n++] = 0U; + } + + // decode convolution + Convolution conv; + conv.start(); + + n = 0U; + for (uint32_t i = 0U; i < 207U; i++) { + uint8_t s0 = interleave[n++]; + uint8_t s1 = interleave[n++]; + + conv.decode(s0, s1); + } + + conv.chainback(m_data, 203U); + + if (m_verbose) { + Utils::dump(2U, "Decoded UDCH", m_data, 26U); + } + + // check CRC-15 + bool ret = CRC::checkCRC15(m_data, 26U); + if (!ret) { + LogError(LOG_NXDN, "UDCH::decode(), failed CRC-15 check"); + return false; + } + + m_ran = m_data[0U] & 0x3FU; + + return true; +} + +/// +/// Encode a user data channel. +/// +/// +void UDCH::encode(uint8_t* data) const +{ + assert(data != NULL); + + m_data[0U] = m_ran; + + if (m_verbose) { + Utils::dump(2U, "Encoded UDCH", m_data, 26U); + } + + uint8_t buffer[25U]; + ::memset(buffer, 0x00U, 25U); + ::memcpy(buffer, m_data, 23U); + + CRC::addCRC15(buffer, 184U); + + // encode convolution + uint8_t convolution[51U]; + Convolution conv; + conv.encode(buffer, convolution, 203U); + +#if DEBUG_NXDN_UDCH + Utils::dump(2U, "UDCH::encode(), UDCH Convolution", convolution, 51U); +#endif + + // interleave and puncture + uint8_t raw[44U]; + uint32_t n = 0U; + uint32_t index = 0U; + for (uint32_t i = 0U; i < 406U; i++) { + if (i != PUNCTURE_LIST[index]) { + bool b = READ_BIT(convolution, i); + WRITE_BIT(raw, n, b); + n++; + } else { + index++; + } + } + + for (uint32_t i = 0U; i < NXDN_FACCH2_LENGTH_BITS; i++) { + uint32_t n = INTERLEAVE_TABLE[i] + NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS; + bool b = READ_BIT(raw, i); + WRITE_BIT(data, n, b); + } + +#if DEBUG_NXDN_UDCH + Utils::dump(2U, "UDCH::encode(), UDCH Interleave", raw, 44U); +#endif +} + +/// +/// Gets the raw UDCH data. +/// +/// +void UDCH::getData(uint8_t* data) const +{ + assert(data != NULL); + + ::memcpy(data, m_data + 1U, 22U); +} + +/// +/// Sets the raw UDCH data. +/// +/// +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/channel/UDCH.h b/nxdn/channel/UDCH.h new file mode 100644 index 00000000..cc65c5e5 --- /dev/null +++ b/nxdn/channel/UDCH.h @@ -0,0 +1,81 @@ +/** +* 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) 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 +* 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_UDCH_H__) +#define __NXDN_UDCH_H__ + +#include "Defines.h" + +namespace nxdn +{ + namespace channel + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN User Data Channel. + // --------------------------------------------------------------------------- + + class HOST_SW_API UDCH { + public: + /// Initializes a copy instance of the UDCH class. + UDCH(const UDCH& data); + /// Initializes a new instance of the UDCH class. + UDCH(); + /// Finalizes a instance of the UDCH class. + ~UDCH(); + + /// Equals operator. + UDCH& operator=(const UDCH& data); + + /// Decode a user data channel. + bool decode(const uint8_t* data); + /// Encode a user data channel. + void encode(uint8_t* data) const; + + /// Gets the raw UDCH data. + void getData(uint8_t* data) const; + /// Sets the raw UDCH data. + void setData(const uint8_t* data); + + public: + /// Flag indicating verbose log output. + __PROPERTY(bool, verbose, Verbose); + + /** Common Data */ + /// Radio Access Number + __PROPERTY(uint8_t, ran, RAN); + + private: + uint8_t* m_data; + }; + } // namespace channel +} // namespace nxdn + +#endif // __NXDN_UDCH_H__ diff --git a/nxdn/data/Layer3.cpp b/nxdn/data/Layer3.cpp new file mode 100644 index 00000000..9a71cc20 --- /dev/null +++ b/nxdn/data/Layer3.cpp @@ -0,0 +1,222 @@ +/** +* 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) 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 +* 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/data/Layer3.h" +#include "Utils.h" + +using namespace nxdn; +using namespace nxdn::data; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a copy instance of the Layer3 class. +/// +/// +Layer3::Layer3(const Layer3& data) : + m_verbose(false), + m_messageType(MESSAGE_TYPE_IDLE), + m_srcId(0U), + m_dstId(0U), + m_group(true), + m_dataBlocks(0U), + m_data(NULL) +{ + m_data = new uint8_t[22U]; + ::memcpy(m_data, data.m_data, 22U); + + m_messageType = m_data[0U] & 0x3FU; // Message Type + + m_group = (m_data[2U] & 0x80U) != 0x80U; + + m_srcId = (m_data[3U] << 8) | m_data[4U]; // Source Radio Address + m_dstId = (m_data[5U] << 8) | m_data[6U]; // Target Radio Address + + m_dataBlocks = (m_data[8U] & 0x0FU) + 1U; // Data Blocks +} + +/// +/// Initializes a new instance of the Layer3 class. +/// +Layer3::Layer3() : + m_verbose(false), + m_messageType(MESSAGE_TYPE_IDLE), + m_srcId(0U), + m_dstId(0U), + m_group(true), + m_dataBlocks(0U), + m_data(NULL) +{ + m_data = new uint8_t[22U]; + ::memset(m_data, 0x00U, 22U); +} + +/// +/// Finalizes a instance of Layer3 class. +/// +Layer3::~Layer3() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +Layer3& Layer3::operator=(const Layer3& data) +{ + if (&data != this) { + ::memcpy(m_data, data.m_data, 22U); + + m_verbose = data.m_verbose; + + m_messageType = data.m_messageType; + + m_group = data.m_group; + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + + m_dataBlocks = m_dataBlocks; + } + + return *this; +} + +/// +/// Decode layer 3 data. +/// +/// +/// True, if SACCH was decoded, otherwise false. +void Layer3::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 Layer 3 Data", m_data, 22U); + } + + m_messageType = m_data[0U] & 0x3FU; // Message Type + + m_group = (m_data[2U] & 0x80U) != 0x80U; + + m_srcId = (m_data[3U] << 8) | m_data[4U]; // Source Radio Address + m_dstId = (m_data[5U] << 8) | m_data[6U]; // Target Radio Address + + m_dataBlocks = (m_data[8U] & 0x0FU) + 1U; // Data Blocks +} + +/// +/// Encode layer 3 data. +/// +/// +void Layer3::encode(uint8_t* data, uint32_t length, uint32_t offset) +{ + assert(data != NULL); + + m_data[0U] = m_messageType & 0x3FU; // Message Type + + m_data[2U] = (m_group ? 0x80U : 0x00U); + + m_data[3U] = (m_srcId >> 8U) & 0xFFU; // Source Radio Address + m_data[4U] = (m_srcId >> 0U) & 0xFFU; // ... + m_data[5U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address + m_data[6U] = (m_dstId >> 0U) & 0xFFU; // ... + + m_data[8U] = m_dataBlocks & 0x0FU; // Data Blocks + + 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 Layer 3 Data", data, length); + } +} + +/// +/// +/// +void Layer3::reset() +{ + ::memset(m_data, 0x00U, 22U); + + m_messageType = MESSAGE_TYPE_IDLE; + + m_srcId = 0U; + m_dstId = 0U; + + m_group = true; + + m_dataBlocks = 0U; +} + +/// +/// Gets the raw layer 3 data. +/// +/// +void Layer3::getData(uint8_t* data) const +{ + ::memcpy(data, m_data, 22U); +} + +/// +/// Sets the raw layer 3 data. +/// +/// +/// +void Layer3::setData(const uint8_t* data, uint32_t length) +{ + ::memset(m_data, 0x00U, 22U); + ::memcpy(m_data, data, length); + + m_messageType = m_data[0U] & 0x3FU; + + m_group = (m_data[2U] & 0x80U) != 0x80U; + + m_srcId = (m_data[3U] << 8) | m_data[4U]; + m_dstId = (m_data[5U] << 8) | m_data[6U]; + + m_dataBlocks = (m_data[8U] & 0x0FU) + 1U; +} diff --git a/nxdn/data/Layer3.h b/nxdn/data/Layer3.h new file mode 100644 index 00000000..19cf7caf --- /dev/null +++ b/nxdn/data/Layer3.h @@ -0,0 +1,95 @@ +/** +* 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) 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 +* 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_LAYER_3_H__) +#define __NXDN_LAYER_3_H__ + +#include "Defines.h" + +namespace nxdn +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements NXDN Layer 3 Connection Control. + // --------------------------------------------------------------------------- + + class HOST_SW_API Layer3 { + public: + /// Initializes a copy instance of the Layer3 class. + Layer3(const Layer3& data); + /// Initializes a new instance of the Layer3 class. + Layer3(); + /// Finalizes a instance of the Layer3 class. + ~Layer3(); + + /// Equals operator. + Layer3& operator=(const Layer3& 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); + + 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); + + /// Flag indicating a group/talkgroup operation. + __PROPERTY(bool, group, Group); + + /// + __PROPERTY(uint8_t, dataBlocks, DataBlocks); + + private: + uint8_t* m_data; + }; + } // namespace data +} // namespace nxdn + +#endif // __NXDN_LAYER_3_H__ diff --git a/nxdn/packet/Data.cpp b/nxdn/packet/Data.cpp new file mode 100644 index 00000000..da1c27ee --- /dev/null +++ b/nxdn/packet/Data.cpp @@ -0,0 +1,431 @@ +/** +* 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) 2015-2020 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 +* 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/UDCH.h" +#include "nxdn/packet/Data.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 + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +// Don't process RF frames if the network isn't in a idle state and the RF destination +// is the network destination and stop network frames from processing -- RF wants to +// transmit on a different talkgroup +#define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ + if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ + resetRF(); \ + return false; \ + } \ + \ + if (m_nxdn->m_netState != RS_NET_IDLE) { \ + if (m_nxdn->m_netLayer3.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, \ + m_nxdn->m_netLayer3.getSrcId(), m_nxdn->m_netLastDstId); \ + resetRF(); \ + return false; \ + } \ + else { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, \ + m_nxdn->m_netLastDstId); \ + resetNet(); \ + } \ + } + +// Don't process network frames if the destination ID's don't match and the network TG hang +// timer is running, and don't process network frames if the RF modem isn't in a listening state +#define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ + if (m_nxdn->m_rfLastDstId != 0U) { \ + if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ + resetNet(); \ + return false; \ + } \ + \ + if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ + m_nxdn->m_rfTGHang.start(); \ + } \ + } \ + \ + if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ + if (_LAYER3.getSrcId() == srcId && _LAYER3.getDstId() == dstId) { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ + srcId, dstId); \ + resetNet(); \ + return false; \ + } \ + else { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", _LAYER3.getDstId(), \ + dstId); \ + resetNet(); \ + return false; \ + } \ + } + +// Validate the source RID +#define VALID_SRCID(_SRC_ID, _DST_ID, _GROUP) \ + if (!acl::AccessControl::validateSrcId(_SRC_ID)) { \ + if (m_lastRejectId == 0U || m_lastRejectId != _SRC_ID) { \ + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_DCALL " denial, RID rejection, srcId = %u", _SRC_ID); \ + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ + m_lastRejectId = _SRC_ID; \ + } \ + \ + m_nxdn->m_rfLastDstId = 0U; \ + m_nxdn->m_rfTGHang.stop(); \ + m_nxdn->m_rfState = RS_RF_REJECTED; \ + return false; \ + } + +// Validate the destination ID +#define VALID_DSTID(_SRC_ID, _DST_ID, _GROUP) \ + if (!_GROUP) { \ + if (!acl::AccessControl::validateSrcId(_DST_ID)) { \ + if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_DCALL " denial, RID rejection, dstId = %u", _DST_ID); \ + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ + m_lastRejectId = _DST_ID; \ + } \ + \ + m_nxdn->m_rfLastDstId = 0U; \ + m_nxdn->m_rfTGHang.stop(); \ + m_nxdn->m_rfState = RS_RF_REJECTED; \ + return false; \ + } \ + } \ + else { \ + if (!acl::AccessControl::validateTGId(_DST_ID)) { \ + if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_DCALL " denial, TGID rejection, dstId = %u", _DST_ID); \ + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ + m_lastRejectId = _DST_ID; \ + } \ + \ + m_nxdn->m_rfLastDstId = 0U; \ + m_nxdn->m_rfTGHang.stop(); \ + m_nxdn->m_rfState = RS_RF_REJECTED; \ + return false; \ + } \ + } + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Resets the data states for the RF interface. +/// +void Data::resetRF() +{ + /* stub */ +} + +/// +/// Resets the data states for the network. +/// +void Data::resetNet() +{ + /* stub */ +} + +/// +/// Process a data frame from the RF interface. +/// +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Data::process(uint8_t option, uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + channel::UDCH udch; + bool validUDCH = udch.decode(data + 2U); + if (m_nxdn->m_rfState == RS_RF_LISTENING && !validUDCH) + return false; + + if (validUDCH) { + uint8_t ran = udch.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[23U]; + udch.getData(buffer); + + data::Layer3 layer3; + layer3.decode(buffer, 184U); + uint16_t dstId = layer3.getDstId(); + uint16_t srcId = layer3.getSrcId(); + bool group = layer3.getGroup(); + + if (m_nxdn->m_rfState == RS_RF_LISTENING) { + uint8_t type = layer3.getMessageType(); + if (type != MESSAGE_TYPE_DCALL_HDR) + return false; + + CHECK_TRAFFIC_COLLISION(srcId, dstId); + + // validate source RID + VALID_SRCID(srcId, dstId, group); + + // validate destination ID + VALID_DSTID(srcId, dstId, group); + + uint8_t frames = layer3.getDataBlocks(); + + if (m_verbose) { + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_DCALL ", srcId = %u, dstId = %u, blocks = %u", + srcId, dstId, frames); + } + + ::ActivityLog("NXDN", true, "RF data transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + + m_nxdn->m_rfLayer3 = layer3; + m_nxdn->m_voice->m_rfFrames = 0U; + + m_nxdn->m_rfState = RS_RF_DATA; + } + + if (m_nxdn->m_rfState != RS_RF_DATA) + return false; + + Sync::addNXDNSync(data + 2U); + + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_UDCH); + lich.setOption(option); + lich.setDirection(!m_nxdn->m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); + + uint8_t type = MESSAGE_TYPE_DCALL_DATA; + if (validUDCH) { + type = layer3.getMessageType(); + data[0U] = type == MESSAGE_TYPE_TX_REL ? modem::TAG_EOT : modem::TAG_DATA; + + udch.setRAN(m_nxdn->m_ran); + udch.encode(data + 2U); + } else { + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + } + + m_nxdn->scrambler(data + 2U); + + writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); + + if (m_nxdn->m_duplex) { + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + } + + m_nxdn->m_voice->m_rfFrames++; + + if (data[0U] == modem::TAG_EOT) { + ::ActivityLog("NXDN", true, "RF ended RF data transmission"); + + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_TX_REL ", total frames: %d", + m_nxdn->m_voice->m_rfFrames); + + m_nxdn->writeEndRF(); + } + + return true; +} + +/// +/// Process a data frame from the RF interface. +/// +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Data::processNetwork(uint8_t option, uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + if (m_nxdn->m_netState == RS_NET_IDLE) { + m_nxdn->m_queue.clear(); + + resetRF(); + resetNet(); + } + + channel::UDCH udch; + bool validUDCH = udch.decode(data + 2U); + if (m_nxdn->m_netState == RS_NET_IDLE && !validUDCH) + return false; + + // The layer3 data will only be correct if valid is true + uint8_t buffer[23U]; + udch.getData(buffer); + + data::Layer3 layer3; + layer3.decode(buffer, 184U); + uint16_t dstId = layer3.getDstId(); + uint16_t srcId = layer3.getSrcId(); + bool group = layer3.getGroup(); + + if (m_nxdn->m_netState == RS_NET_IDLE) { + uint8_t type = layer3.getMessageType(); + if (type != MESSAGE_TYPE_DCALL_HDR) + return false; + + CHECK_NET_TRAFFIC_COLLISION(layer3, srcId, dstId); + + // validate source RID + VALID_SRCID(srcId, dstId, group); + + // validate destination ID + VALID_DSTID(srcId, dstId, group); + + uint8_t frames = layer3.getDataBlocks(); + + if (m_verbose) { + LogMessage(LOG_NET, NXDN_MESSAGE_TYPE_DCALL ", srcId = %u, dstId = %u, blocks = %u", + srcId, dstId, frames); + } + + ::ActivityLog("NXDN", false, "network data transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + + m_nxdn->m_netLayer3 = layer3; + m_nxdn->m_voice->m_netFrames = 0U; + + m_nxdn->m_netState = RS_NET_DATA; + } + + if (m_nxdn->m_netState != RS_NET_DATA) + return false; + + Sync::addNXDNSync(data + 2U); + + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_UDCH); + lich.setOption(option); + lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + uint8_t type = MESSAGE_TYPE_DCALL_DATA; + if (validUDCH) { + type = layer3.getMessageType(); + data[0U] = type == MESSAGE_TYPE_TX_REL ? modem::TAG_EOT : modem::TAG_DATA; + + udch.setRAN(m_nxdn->m_ran); + udch.encode(data + 2U); + } else { + 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); + } + + m_nxdn->m_voice->m_netFrames++; + + if (data[0U] == modem::TAG_EOT) { + ::ActivityLog("NXDN", true, "network ended RF data transmission"); + + LogMessage(LOG_NET, NXDN_MESSAGE_TYPE_TX_REL ", total frames: %d", + m_nxdn->m_voice->m_netFrames); + + m_nxdn->writeEndNet(); + } + + return true; +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the Data class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether NXDN debug is enabled. +/// Flag indicating whether NXDN verbose logging is enabled. +Data::Data(Control* nxdn, network::BaseNetwork* network, bool debug, bool verbose) : + m_nxdn(nxdn), + m_network(network), + m_lastRejectId(0U), + m_verbose(verbose), + m_debug(debug) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Data class. +/// +Data::~Data() +{ + /* stub */ +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +void Data::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(data, len); +} diff --git a/nxdn/packet/Data.h b/nxdn/packet/Data.h new file mode 100644 index 00000000..9e2c9cf5 --- /dev/null +++ b/nxdn/packet/Data.h @@ -0,0 +1,90 @@ +/** +* 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) 2015-2020 by Jonathan Naylor G4KLX +* Copyright (C) 2017-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_DATA_H__) +#define __NXDN_PACKET_DATA_H__ + +#include "Defines.h" +#include "nxdn/Control.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 data packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API Data { + 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 option, uint8_t* data, uint32_t len); + /// Process a data frame from the network. + virtual bool processNetwork(uint8_t option, uint8_t* data, uint32_t len); + + protected: + friend class nxdn::Control; + Control *m_nxdn; + + network::BaseNetwork* m_network; + + uint16_t m_lastRejectId; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the Data class. + Data(Control* nxdn, network::BaseNetwork* network, bool debug, bool verbose); + /// Finalizes a instance of the Data class. + virtual ~Data(); + + /// Write data processed from RF to the network. + void writeNetwork(const uint8_t* data, uint32_t len); + }; + } // namespace packet +} // namespace nxdn + +#endif // __NXDN_PACKET_DATA_H__ diff --git a/nxdn/packet/Voice.cpp b/nxdn/packet/Voice.cpp new file mode 100644 index 00000000..745ca4e8 --- /dev/null +++ b/nxdn/packet/Voice.cpp @@ -0,0 +1,969 @@ +/** +* 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) 2015-2020 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 +* 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/packet/Voice.h" +#include "nxdn/channel/FACCH1.h" +#include "nxdn/channel/SACCH.h" +#include "nxdn/acl/AccessControl.h" +#include "nxdn/Audio.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 + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +// Don't process RF frames if the network isn't in a idle state and the RF destination +// is the network destination and stop network frames from processing -- RF wants to +// transmit on a different talkgroup +#define CHECK_TRAFFIC_COLLISION(_SRC_ID, _DST_ID) \ + if (m_nxdn->m_netState != RS_NET_IDLE && _DST_ID == m_nxdn->m_netLastDstId) { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); \ + resetRF(); \ + return false; \ + } \ + \ + if (m_nxdn->m_netState != RS_NET_IDLE) { \ + if (m_nxdn->m_netLayer3.getSrcId() == _SRC_ID && m_nxdn->m_netLastDstId == _DST_ID) { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", srcId, dstId, \ + m_nxdn->m_netLayer3.getSrcId(), m_nxdn->m_netLastDstId); \ + resetRF(); \ + return false; \ + } \ + else { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting existing network traffic to new RF traffic, rfDstId = %u, netDstId = %u", dstId, \ + m_nxdn->m_netLastDstId); \ + resetNet(); \ + } \ + } + +// Don't process network frames if the destination ID's don't match and the network TG hang +// timer is running, and don't process network frames if the RF modem isn't in a listening state +#define CHECK_NET_TRAFFIC_COLLISION(_LAYER3, _SRC_ID, _DST_ID) \ + if (m_nxdn->m_rfLastDstId != 0U) { \ + if (m_nxdn->m_rfLastDstId != dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ + resetNet(); \ + return false; \ + } \ + \ + if (m_nxdn->m_rfLastDstId == dstId && (m_nxdn->m_rfTGHang.isRunning() && !m_nxdn->m_rfTGHang.hasExpired())) { \ + m_nxdn->m_rfTGHang.start(); \ + } \ + } \ + \ + if (m_nxdn->m_rfState != RS_RF_LISTENING) { \ + if (_LAYER3.getSrcId() == srcId && _LAYER3.getDstId() == dstId) { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic (Are we in a voting condition?), rfSrcId = %u, rfDstId = %u, netSrcId = %u, netDstId = %u", _LAYER3.getSrcId(), _LAYER3.getDstId(), \ + srcId, dstId); \ + resetNet(); \ + return false; \ + } \ + else { \ + LogWarning(LOG_RF, "Traffic collision detect, preempting new network traffic to existing RF traffic, rfDstId = %u, netDstId = %u", _LAYER3.getDstId(), \ + dstId); \ + resetNet(); \ + return false; \ + } \ + } + +// Validate the source RID +#define VALID_SRCID(_SRC_ID, _DST_ID, _GROUP) \ + if (!acl::AccessControl::validateSrcId(_SRC_ID)) { \ + if (m_lastRejectId == 0U || m_lastRejectId != _SRC_ID) { \ + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_VCALL " denial, RID rejection, srcId = %u", _SRC_ID); \ + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ + m_lastRejectId = _SRC_ID; \ + } \ + \ + m_nxdn->m_rfLastDstId = 0U; \ + m_nxdn->m_rfTGHang.stop(); \ + m_nxdn->m_rfState = RS_RF_REJECTED; \ + return false; \ + } + +// Validate the destination ID +#define VALID_DSTID(_SRC_ID, _DST_ID, _GROUP) \ + if (!_GROUP) { \ + if (!acl::AccessControl::validateSrcId(_DST_ID)) { \ + if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_VCALL " denial, RID rejection, dstId = %u", _DST_ID); \ + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ + m_lastRejectId = _DST_ID; \ + } \ + \ + m_nxdn->m_rfLastDstId = 0U; \ + m_nxdn->m_rfTGHang.stop(); \ + m_nxdn->m_rfState = RS_RF_REJECTED; \ + return false; \ + } \ + } \ + else { \ + if (!acl::AccessControl::validateTGId(_DST_ID)) { \ + if (m_lastRejectId == 0 || m_lastRejectId != _DST_ID) { \ + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_VCALL " denial, TGID rejection, dstId = %u", _DST_ID); \ + ::ActivityLog("NXDN", true, "RF voice rejection from %u to %s%u ", _SRC_ID, _GROUP ? "TG " : "", _DST_ID); \ + m_lastRejectId = _DST_ID; \ + } \ + \ + m_nxdn->m_rfLastDstId = 0U; \ + m_nxdn->m_rfTGHang.stop(); \ + m_nxdn->m_rfState = RS_RF_REJECTED; \ + return false; \ + } \ + } + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Resets the data states for the RF interface. +/// +void Voice::resetRF() +{ + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfUndecodableLC = 0U; +} + +/// +/// Resets the data states for the network. +/// +void Voice::resetNet() +{ + m_netFrames = 0U; + m_netLost = 0U; +} + +/// +/// Process a data frame from the RF interface. +/// +/// +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + channel::SACCH sacch; + bool valid = sacch.decode(data + 2U); + if (valid) { + uint8_t ran = sacch.getRAN(); + if (ran != m_nxdn->m_ran && ran != 0U) + return false; + } else if (m_nxdn->m_rfState == RS_RF_LISTENING) { + return false; + } + + if (usc == 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); + if (!valid) + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + if (!valid) + return false; + + uint8_t buffer[10U]; + facch.getData(buffer); + + data::Layer3 layer3; + layer3.decode(buffer, NXDN_FACCH1_LENGTH_BITS); + uint16_t dstId = layer3.getDstId(); + uint16_t srcId = layer3.getSrcId(); + bool group = layer3.getGroup(); + + uint8_t type = layer3.getMessageType(); + if (type == MESSAGE_TYPE_TX_REL) { + if (m_nxdn->m_rfState != RS_RF_AUDIO) { + m_nxdn->m_rfState = RS_RF_LISTENING; + m_nxdn->m_rfMask = 0x00U; + m_nxdn->m_rfLayer3.reset(); + return false; + } + } else if (type == MESSAGE_TYPE_VCALL) { + CHECK_TRAFFIC_COLLISION(srcId, dstId); + + // validate source RID + VALID_SRCID(srcId, dstId, group); + + // validate destination ID + VALID_DSTID(srcId, dstId, group); + } else { + return false; + } + + m_nxdn->m_rfLayer3 = layer3; + + Sync::addNXDNSync(data + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_SACCH_NS); + lich.setOption(NXDN_LICH_STEAL_FACCH); + lich.setDirection(!m_nxdn->m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + // generate the SACCH + channel::SACCH sacch; + sacch.setRAN(m_nxdn->m_ran); + sacch.setStructure(NXDN_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(data + 2U); + + facch.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + facch.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + + m_nxdn->scrambler(data + 2U); + + writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); + + if (m_nxdn->m_duplex) { + data[0U] = type == MESSAGE_TYPE_TX_REL ? modem::TAG_EOT : modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + } + + if (data[0U] == modem::TAG_EOT) { + m_rfFrames++; + if (m_nxdn->m_rssi != 0U) { + ::ActivityLog("NXDN", true, "RF end of transmission, %.1f seconds, BER: %.1f%%, RSSI : -%u / -%u / -%u dBm", + float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits), m_nxdn->m_minRSSI, m_nxdn->m_maxRSSI, + m_nxdn->m_aveRSSI / m_nxdn->m_rssiCount); + } + else { + ::ActivityLog("NXDN", true, "RF end of transmission, %.1f seconds, BER: %.1f%%", + float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits)); + } + + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + m_rfFrames, m_rfBits, m_rfUndecodableLC, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits)); + + m_nxdn->writeEndRF(); + } else { + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_nxdn->m_rfTimeout.start(); + m_nxdn->m_rfState = RS_RF_AUDIO; + + m_nxdn->m_minRSSI = m_nxdn->m_rssi; + m_nxdn->m_maxRSSI = m_nxdn->m_rssi; + m_nxdn->m_aveRSSI = m_nxdn->m_rssi; + m_nxdn->m_rssiCount = 1U; + + ::ActivityLog("NXDN", true, "RF voice transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + } + + return true; + } else { + if (m_nxdn->m_rfState == RS_RF_LISTENING) { + channel::FACCH1 facch; + bool valid = false; + switch (option) { + case NXDN_LICH_STEAL_FACCH: + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + if (!valid) + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + break; + case NXDN_LICH_STEAL_FACCH1_1: + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + break; + case NXDN_LICH_STEAL_FACCH1_2: + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + break; + default: + break; + } + + bool hasInfo = false; + if (valid) { + uint8_t buffer[10U]; + facch.getData(buffer); + + data::Layer3 layer3; + layer3.decode(buffer, NXDN_FACCH1_LENGTH_BITS); + + hasInfo = layer3.getMessageType() == MESSAGE_TYPE_VCALL; + if (!hasInfo) + return false; + + m_nxdn->m_rfLayer3 = layer3; + } + + if (!hasInfo) { + uint8_t message[3U]; + sacch.getData(message); + + uint8_t structure = sacch.getStructure(); + switch (structure) { + case NXDN_SR_1_4: + m_nxdn->m_rfLayer3.decode(message, 18U, 0U); + if(m_nxdn->m_rfLayer3.getMessageType() == MESSAGE_TYPE_VCALL) + m_nxdn->m_rfMask = 0x01U; + else + m_nxdn->m_rfMask = 0x00U; + break; + case NXDN_SR_2_4: + m_nxdn->m_rfMask |= 0x02U; + m_nxdn->m_rfLayer3.decode(message, 18U, 18U); + break; + case NXDN_SR_3_4: + m_nxdn->m_rfMask |= 0x04U; + m_nxdn->m_rfLayer3.decode(message, 18U, 36U); + break; + case NXDN_SR_4_4: + m_nxdn->m_rfMask |= 0x08U; + m_nxdn->m_rfLayer3.decode(message, 18U, 54U); + break; + default: + break; + } + + if (m_nxdn->m_rfMask != 0x0FU) + return false; + + uint8_t type = m_nxdn->m_rfLayer3.getMessageType(); + if (type != MESSAGE_TYPE_VCALL) + return false; + } + + uint16_t dstId = m_nxdn->m_rfLayer3.getDstId(); + uint16_t srcId = m_nxdn->m_rfLayer3.getSrcId(); + bool group = m_nxdn->m_rfLayer3.getGroup(); + + CHECK_TRAFFIC_COLLISION(srcId, dstId); + + // validate source RID + VALID_SRCID(srcId, dstId, group); + + // validate destination ID + VALID_DSTID(srcId, dstId, group); + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_nxdn->m_rfTimeout.start(); + m_nxdn->m_rfState = RS_RF_AUDIO; + + m_nxdn->m_minRSSI = m_nxdn->m_rssi; + m_nxdn->m_maxRSSI = m_nxdn->m_rssi; + m_nxdn->m_aveRSSI = m_nxdn->m_rssi; + m_nxdn->m_rssiCount = 1U; + + ::ActivityLog("NXDN", true, "RF late entry from %u to %s%u", srcId, group ? "TG " : "", dstId); + + // create a dummy start message + uint8_t start[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(start, 0x00U, NXDN_FRAME_LENGTH_BYTES + 2U); + + // generate the sync + Sync::addNXDNSync(start + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_SACCH_NS); + lich.setOption(NXDN_LICH_STEAL_FACCH); + lich.setDirection(!m_nxdn->m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(start + 2U); + + lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); + + // generate the SACCH + channel::SACCH sacch; + sacch.setRAN(m_nxdn->m_ran); + sacch.setStructure(NXDN_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(start + 2U); + + uint8_t message[22U]; + m_nxdn->m_rfLayer3.getData(message); + + facch.setData(message); + facch.encode(start + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + facch.encode(start + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + + m_nxdn->scrambler(start + 2U); + + writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); + + if (m_nxdn->m_duplex) { + start[0U] = modem::TAG_DATA; + start[1U] = 0x00U; + + m_nxdn->addFrame(start, NXDN_FRAME_LENGTH_BYTES + 2U); + } + } + } + + if (m_nxdn->m_rfState == RS_RF_AUDIO) { + // regenerate the sync + Sync::addNXDNSync(data + 2U); + + // regenerate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_SACCH_SS); + lich.setOption(option); + lich.setDirection(!m_nxdn->m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); + + // regenerate SACCH if it's valid + channel::SACCH sacch; + bool validSACCH = sacch.decode(data + 2U); + if (validSACCH) { + sacch.setRAN(m_nxdn->m_ran); + sacch.encode(data + 2U); + } + + // regenerate the audio and interpret the FACCH1 data + if (option == NXDN_LICH_STEAL_NONE) { + edac::AMBEFEC ambe; + + uint32_t errors = 0U; + + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + m_rfErrs += errors; + m_rfBits += 188U; + + if (m_verbose) { + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", audio, errs = %u/141 (%.1f%%)", + errors, float(errors) / 1.88F); + } +/* + Audio audio; + audio.decode(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U, netData + 5U + 0U); + audio.decode(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U, netData + 5U + 14U); +*/ + } else if (option == NXDN_LICH_STEAL_FACCH1_1) { + channel::FACCH1 facch1; + bool valid = facch1.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + if (valid) + facch1.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + + edac::AMBEFEC ambe; + + uint32_t errors = 0U; + + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + m_rfErrs += errors; + m_rfBits += 94U; + + if (m_verbose) { + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", audio, errs = %u/94 (%.1f%%)", + errors, float(errors) / 0.94F); + } +/* + Audio audio; + audio.decode(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U, netData + 5U + 14U); +*/ + } else if (option == NXDN_LICH_STEAL_FACCH1_2) { + edac::AMBEFEC ambe; + + uint32_t errors = 0U; + + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + + m_rfErrs += errors; + m_rfBits += 94U; + + if (m_verbose) { + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", audio, errs = %u/94 (%.1f%%)", + errors, float(errors) / 0.94F); + } +/* + Audio audio; + audio.decode(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U, netData + 5U + 0U); +*/ + channel::FACCH1 facch1; + bool valid = facch1.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + if (valid) + facch1.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + } else { + channel::FACCH1 facch11; + bool valid1 = facch11.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + if (valid1) + facch11.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + + channel::FACCH1 facch12; + bool valid2 = facch12.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + if (valid2) + facch12.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + } + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->scrambler(data + 2U); + + writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); + + if (m_nxdn->m_duplex) { + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + } + + m_rfFrames++; + } + + return true; +} + +/// +/// Process a data frame from the network. +/// +/// +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Voice::processNetwork(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + if (m_nxdn->m_netState == RS_NET_IDLE) { + m_nxdn->m_queue.clear(); + + resetRF(); + resetNet(); + } + + channel::SACCH sacch; + sacch.decode(data + 2U); + + if (usc == 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); + if (!valid) + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + if (!valid) + return false; + + uint8_t buffer[10U]; + facch.getData(buffer); + + data::Layer3 layer3; + layer3.decode(buffer, NXDN_FACCH1_LENGTH_BITS); + uint16_t dstId = layer3.getDstId(); + uint16_t srcId = layer3.getSrcId(); + bool group = layer3.getGroup(); + + uint8_t type = layer3.getMessageType(); + if (type == MESSAGE_TYPE_TX_REL) { + if (m_nxdn->m_netState != RS_NET_AUDIO) { + m_nxdn->m_netState = RS_NET_IDLE; + m_nxdn->m_netMask = 0x00U; + m_nxdn->m_netLayer3.reset(); + return false; + } + } else if (type == MESSAGE_TYPE_VCALL) { + CHECK_NET_TRAFFIC_COLLISION(layer3, srcId, dstId); + + // validate source RID + VALID_SRCID(srcId, dstId, group); + + // validate destination ID + VALID_DSTID(srcId, dstId, group); + } else { + return false; + } + + m_nxdn->m_netLayer3 = layer3; + + Sync::addNXDNSync(data + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_SACCH_NS); + lich.setOption(NXDN_LICH_STEAL_FACCH); + lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + // generate the SACCH + channel::SACCH sacch; + sacch.setRAN(m_nxdn->m_ran); + sacch.setStructure(NXDN_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(data + 2U); + + facch.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + facch.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + + m_nxdn->scrambler(data + 2U); + + if (m_nxdn->m_duplex) { + data[0U] = type == MESSAGE_TYPE_TX_REL ? modem::TAG_EOT : modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U, true); + } + + if (data[0U] == modem::TAG_EOT) { + m_netFrames++; + ::ActivityLog("NXDN", false, "network end of transmission, %.1f seconds", + float(m_netFrames) / 12.5F); + + LogMessage(LOG_NET, NXDN_MESSAGE_TYPE_TX_REL ", total frames: %d", + m_netFrames); + + m_nxdn->writeEndNet(); + } else { + m_netFrames = 0U; + m_nxdn->m_netTimeout.start(); + m_nxdn->m_netState = RS_NET_AUDIO; + + ::ActivityLog("NXDN", false, "network voice transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + } + + return true; + } else { + if (m_nxdn->m_netState == RS_NET_IDLE) { + channel::FACCH1 facch; + bool valid = false; + switch (option) { + case NXDN_LICH_STEAL_FACCH: + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + if (!valid) + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + break; + case NXDN_LICH_STEAL_FACCH1_1: + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + break; + case NXDN_LICH_STEAL_FACCH1_2: + valid = facch.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + break; + default: + break; + } + + bool hasInfo = false; + if (valid) { + uint8_t buffer[10U]; + facch.getData(buffer); + + data::Layer3 layer3; + layer3.decode(buffer, NXDN_FACCH1_LENGTH_BITS); + + hasInfo = layer3.getMessageType() == MESSAGE_TYPE_VCALL; + if (!hasInfo) + return false; + + m_nxdn->m_netLayer3 = layer3; + } + + if (!hasInfo) { + uint8_t message[3U]; + sacch.getData(message); + + uint8_t structure = sacch.getStructure(); + switch (structure) { + case NXDN_SR_1_4: + m_nxdn->m_netLayer3.decode(message, 18U, 0U); + if(m_nxdn->m_netLayer3.getMessageType() == MESSAGE_TYPE_VCALL) + m_nxdn->m_netMask = 0x01U; + else + m_nxdn->m_netMask = 0x00U; + break; + case NXDN_SR_2_4: + m_nxdn->m_netMask |= 0x02U; + m_nxdn->m_netLayer3.decode(message, 18U, 18U); + break; + case NXDN_SR_3_4: + m_nxdn->m_netMask |= 0x04U; + m_nxdn->m_netLayer3.decode(message, 18U, 36U); + break; + case NXDN_SR_4_4: + m_nxdn->m_netMask |= 0x08U; + m_nxdn->m_netLayer3.decode(message, 18U, 54U); + break; + default: + break; + } + + if (m_nxdn->m_netMask != 0x0FU) + return false; + + uint8_t type = m_nxdn->m_netLayer3.getMessageType(); + if (type != MESSAGE_TYPE_VCALL) + return false; + } + + uint16_t dstId = m_nxdn->m_netLayer3.getDstId(); + uint16_t srcId = m_nxdn->m_netLayer3.getSrcId(); + bool group = m_nxdn->m_netLayer3.getGroup(); + + CHECK_NET_TRAFFIC_COLLISION(m_nxdn->m_netLayer3, srcId, dstId); + + // validate source RID + VALID_SRCID(srcId, dstId, group); + + // validate destination ID + VALID_DSTID(srcId, dstId, group); + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_nxdn->m_netTimeout.start(); + m_nxdn->m_netState = RS_NET_AUDIO; + + ::ActivityLog("NXDN", false, "network late entry from %u to %s%u", srcId, group ? "TG " : "", dstId); + + // create a dummy start message + uint8_t start[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(start, 0x00U, NXDN_FRAME_LENGTH_BYTES + 2U); + + // generate the sync + Sync::addNXDNSync(start + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_SACCH_NS); + lich.setOption(NXDN_LICH_STEAL_FACCH); + lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(start + 2U); + + // generate the SACCH + channel::SACCH sacch; + sacch.setRAN(m_nxdn->m_ran); + sacch.setStructure(NXDN_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(start + 2U); + + uint8_t message[22U]; + m_nxdn->m_rfLayer3.getData(message); + + facch.setData(message); + facch.encode(start + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + facch.encode(start + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + + m_nxdn->scrambler(start + 2U); + + if (m_nxdn->m_duplex) { + start[0U] = modem::TAG_DATA; + start[1U] = 0x00U; + + m_nxdn->addFrame(start, NXDN_FRAME_LENGTH_BYTES + 2U, true); + } + } + } + + if (m_nxdn->m_rfState == RS_RF_AUDIO) { + // regenerate the sync + Sync::addNXDNSync(data + 2U); + + // regenerate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RDCH); + lich.setFCT(NXDN_LICH_USC_SACCH_SS); + lich.setOption(option); + lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + // regenerate SACCH if it's valid + channel::SACCH sacch; + bool validSACCH = sacch.decode(data + 2U); + if (validSACCH) { + sacch.setRAN(m_nxdn->m_ran); + sacch.encode(data + 2U); + } + + // regenerate the audio and interpret the FACCH1 data + if (option == NXDN_LICH_STEAL_NONE) { + edac::AMBEFEC ambe; + + uint32_t errors = 0U; + + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + m_rfErrs += errors; + m_rfBits += 188U; + + if (m_verbose) { + LogMessage(LOG_NET, NXDN_MESSAGE_TYPE_VCALL ", audio, errs = %u/141 (%.1f%%)", + errors, float(errors) / 1.88F); + } + } else if (option == NXDN_LICH_STEAL_FACCH1_1) { + channel::FACCH1 facch1; + bool valid = facch1.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + if (valid) + facch1.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + + edac::AMBEFEC ambe; + + uint32_t errors = 0U; + + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + m_rfErrs += errors; + m_rfBits += 94U; + + if (m_verbose) { + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", audio, errs = %u/94 (%.1f%%)", + errors, float(errors) / 0.94F); + } + } else if (option == NXDN_LICH_STEAL_FACCH1_2) { + edac::AMBEFEC ambe; + + uint32_t errors = 0U; + + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + + m_rfErrs += errors; + m_rfBits += 94U; + + if (m_verbose) { + LogMessage(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", audio, errs = %u/94 (%.1f%%)", + errors, float(errors) / 0.94F); + } + channel::FACCH1 facch1; + bool valid = facch1.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + if (valid) + facch1.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + } else { + channel::FACCH1 facch11; + bool valid1 = facch11.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + if (valid1) + facch11.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); + + channel::FACCH1 facch12; + bool valid2 = facch12.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + if (valid2) + facch12.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); + } + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->scrambler(data + 2U); + + if (m_nxdn->m_duplex) { + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + } + + m_netFrames++; + } + + return true; +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the Voice class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether NXDN debug is enabled. +/// Flag indicating whether NXDN verbose logging is enabled. +Voice::Voice(Control* nxdn, network::BaseNetwork* network, bool debug, bool verbose) : + m_nxdn(nxdn), + m_network(network), + m_rfFrames(0U), + m_rfBits(0U), + m_rfErrs(0U), + m_rfUndecodableLC(0U), + m_netFrames(0U), + m_netLost(0U), + m_lastRejectId(0U), + m_silenceThreshold(DEFAULT_SILENCE_THRESHOLD), + m_verbose(verbose), + m_debug(debug) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Voice class. +/// +Voice::~Voice() +{ + /* stub */ +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +void Voice::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(data, len); +} diff --git a/nxdn/packet/Voice.h b/nxdn/packet/Voice.h new file mode 100644 index 00000000..9c6acf55 --- /dev/null +++ b/nxdn/packet/Voice.h @@ -0,0 +1,101 @@ +/** +* 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) 2015-2020 by Jonathan Naylor G4KLX +* Copyright (C) 2017-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_VOICE_H__) +#define __NXDN_PACKET_VOICE_H__ + +#include "Defines.h" +#include "nxdn/Control.h" +#include "network/BaseNetwork.h" + +#include +#include + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + namespace packet { class HOST_SW_API Data; } + class HOST_SW_API Control; + + namespace packet + { + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for NXDN voice packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API Voice { + 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 usc, 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, uint8_t* data, uint32_t len); + + protected: + friend class packet::Data; + friend class nxdn::Control; + Control* m_nxdn; + + network::BaseNetwork* m_network; + + uint32_t m_rfFrames; + uint32_t m_rfBits; + uint32_t m_rfErrs; + uint32_t m_rfUndecodableLC; + uint32_t m_netFrames; + uint32_t m_netLost; + + uint16_t m_lastRejectId; + + uint32_t m_silenceThreshold; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the Voice class. + Voice(Control* nxdn, network::BaseNetwork* network, bool debug, bool verbose); + /// Finalizes a instance of the Voice class. + virtual ~Voice(); + + /// Write data processed from RF to the network. + void writeNetwork(const uint8_t* data, uint32_t len); + }; + } // namespace packet +} // namespace nxdn + +#endif // __NXDN_PACKET_VOICE_H__ diff --git a/p25/Control.cpp b/p25/Control.cpp index af321b2d..995beb74 100644 --- a/p25/Control.cpp +++ b/p25/Control.cpp @@ -203,7 +203,7 @@ void Control::reset() /// /// Helper to set P25 configuration options. /// -/// Instance of the ConfigINI class. +/// Instance of the yaml::Node class. /// /// /// @@ -733,11 +733,20 @@ void Control::clock(uint32_t ms) } } +/// +/// Flag indicating whether the processor or is busy or not. +/// +/// True, if processor is busy, otherwise false. +bool Control::isBusy() const +{ + return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE; +} + /// /// Helper to change the debug and verbose state. /// -/// Flag indicating whether DMR debug is enabled. -/// Flag indicating whether DMR verbose logging is enabled. +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. void Control::setDebugVerbose(bool debug, bool verbose) { m_debug = m_voice->m_debug = m_data->m_debug = m_trunk->m_debug = debug; diff --git a/p25/Control.h b/p25/Control.h index 2361af80..8a7fd6e3 100644 --- a/p25/Control.h +++ b/p25/Control.h @@ -113,6 +113,9 @@ namespace p25 /// Gets instance of the Trunk class. packet::Trunk* trunk() { return m_trunk; } + /// Flag indicating whether the processor or is busy or not. + bool isBusy() const; + /// Helper to change the debug and verbose state. void setDebugVerbose(bool debug, bool verbose); diff --git a/p25/lc/TDULC.h b/p25/lc/TDULC.h index 88074195..fa3e4b64 100644 --- a/p25/lc/TDULC.h +++ b/p25/lc/TDULC.h @@ -68,9 +68,9 @@ namespace p25 /// Equals operator. TDULC& operator=(const TDULC& data); - /// Decode a trunking signalling block. + /// Decode a terminator data unit w/ link control. bool decode(const uint8_t* data); - /// Encode a trunking signalling block. + /// Encode a terminator data unit w/ link control. void encode(uint8_t* data); public: