/* * Copyright (C) 2015-2021 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; version 2 of the License. * * 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. */ #include "NXDNControl.h" #include "NXDNFACCH1.h" #include "NXDNSACCH.h" #include "NXDNAudio.h" #include "NXDNUDCH.h" #include "AMBEFEC.h" #include "Utils.h" #include "Sync.h" #include "Log.h" #include #include #include #include const unsigned char 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 }; // #define DUMP_NXDN const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; #define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) #define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) CNXDNControl::CNXDNControl(unsigned int ran, unsigned int id, bool selfOnly, INXDNNetwork* network, CDisplay* display, unsigned int timeout, bool duplex, bool remoteGateway, CNXDNLookup* lookup, CRSSIInterpolator* rssiMapper) : m_ran(ran), m_id(id), m_selfOnly(selfOnly), m_network(network), m_display(display), m_duplex(duplex), m_remoteGateway(remoteGateway), m_lookup(lookup), m_queue(5000U, "NXDN Control"), m_rfState(RS_RF_LISTENING), m_netState(RS_NET_IDLE), m_rfTimeoutTimer(1000U, timeout), m_netTimeoutTimer(1000U, timeout), m_packetTimer(1000U, 0U, 200U), m_networkWatchdog(1000U, 0U, 1500U), m_elapsed(), m_rfFrames(0U), m_netFrames(0U), m_rfErrs(0U), m_rfBits(1U), m_rfLastLICH(), m_rfLayer3(), m_netLayer3(), m_rfMask(0x00U), m_netMask(0x00U), m_rssiMapper(rssiMapper), m_rssi(0U), m_maxRSSI(0U), m_minRSSI(0U), m_aveRSSI(0U), m_rssiCount(0U), m_enabled(true), m_fp(NULL) { assert(display != NULL); assert(lookup != NULL); assert(rssiMapper != NULL); } CNXDNControl::~CNXDNControl() { } bool CNXDNControl::writeModem(unsigned char *data, unsigned int len) { assert(data != NULL); if (!m_enabled) return false; unsigned char type = data[0U]; if (type == TAG_LOST && m_rfState == RS_RF_AUDIO) { unsigned short dstId = m_rfLayer3.getDestinationGroupId(); bool grp = m_rfLayer3.getIsGroup(); std::string source = m_lookup->find(m_rfLayer3.getSourceUnitId()); if (m_rssi != 0U) LogMessage("NXDN, transmission lost from %s to %s%u, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); else LogMessage("NXDN, transmission lost from %s to %s%u, %.1f seconds, BER: %.1f%%", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits)); writeEndRF(); return false; } if (type == TAG_LOST && m_rfState == RS_RF_DATA) { writeEndRF(); return false; } if (type == 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 (rssi != 0) LogDebug("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); CNXDNLICH lich; bool valid = lich.decode(data + 2U); if (valid) m_rfLastLICH = lich; // Stop repeater packets coming through, unless we're acting as a remote gateway if (m_remoteGateway) { unsigned char direction = m_rfLastLICH.getDirection(); if (direction == NXDN_LICH_DIRECTION_INBOUND) return false; } else { unsigned char direction = m_rfLastLICH.getDirection(); if (direction == NXDN_LICH_DIRECTION_OUTBOUND) return false; } unsigned char usc = m_rfLastLICH.getFCT(); unsigned char option = m_rfLastLICH.getOption(); bool ret; if (usc == NXDN_LICH_USC_UDCH) ret = processData(option, data); else ret = processVoice(usc, option, data); return ret; } bool CNXDNControl::processVoice(unsigned char usc, unsigned char option, unsigned char *data) { CNXDNSACCH sacch; bool valid = sacch.decode(data + 2U); if (valid) { unsigned char ran = sacch.getRAN(); if (ran != m_ran && ran != 0U) return false; } else if (m_rfState == RS_RF_LISTENING) { return false; } unsigned char netData[40U]; ::memset(netData, 0x00U, 40U); 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. CNXDNFACCH1 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; unsigned char buffer[10U]; facch.getData(buffer); CNXDNLayer3 layer3; layer3.decode(buffer, NXDN_FACCH1_LENGTH_BITS); unsigned char type = layer3.getMessageType(); if (type == NXDN_MESSAGE_TYPE_TX_REL) { if (m_rfState != RS_RF_AUDIO) { m_rfState = RS_RF_LISTENING; m_rfMask = 0x00U; m_rfLayer3.reset(); return false; } } else if (type == NXDN_MESSAGE_TYPE_VCALL) { if (m_rfState == RS_RF_LISTENING && m_selfOnly) { unsigned short srcId = layer3.getSourceUnitId(); if (srcId != m_id) { m_rfState = RS_RF_REJECTED; return true; } } } else { return false; } m_rfLayer3 = layer3; data[0U] = type == NXDN_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; data[1U] = 0x00U; CSync::addNXDNSync(data + 2U); CNXDNLICH lich; lich.setRFCT(NXDN_LICH_RFCT_RDCH); lich.setFCT(NXDN_LICH_USC_SACCH_NS); lich.setOption(NXDN_LICH_STEAL_FACCH); lich.setDirection(m_remoteGateway || !m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); lich.encode(data + 2U); lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); netData[0U] = lich.getRaw(); CNXDNSACCH sacch; sacch.setRAN(m_ran); sacch.setStructure(NXDN_SR_SINGLE); sacch.setData(SACCH_IDLE); sacch.encode(data + 2U); sacch.getRaw(netData + 1U); 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); facch.getRaw(netData + 5U + 0U); facch.getRaw(netData + 5U + 14U); scrambler(data + 2U); writeNetwork(netData, data[0U] == TAG_EOT ? NNMT_VOICE_TRAILER : NNMT_VOICE_HEADER); #if defined(DUMP_NXDN) writeFile(data + 2U); #endif if (m_duplex) writeQueueRF(data); if (data[0U] == TAG_EOT) { unsigned short dstId = m_rfLayer3.getDestinationGroupId(); bool grp = m_rfLayer3.getIsGroup(); std::string source = m_lookup->find(m_rfLayer3.getSourceUnitId()); m_rfFrames++; if (m_rssi != 0U) LogMessage("NXDN, received RF end of transmission from %s to %s%u, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); else LogMessage("NXDN, received RF end of transmission from %s to %s%u, %.1f seconds, BER: %.1f%%", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits)); writeEndRF(); } else { m_rfFrames = 0U; m_rfErrs = 0U; m_rfBits = 1U; m_rfTimeoutTimer.start(); m_rfState = RS_RF_AUDIO; m_minRSSI = m_rssi; m_maxRSSI = m_rssi; m_aveRSSI = m_rssi; m_rssiCount = 1U; #if defined(DUMP_NXDN) openFile(); #endif unsigned short srcId = m_rfLayer3.getSourceUnitId(); unsigned short dstId = m_rfLayer3.getDestinationGroupId(); bool grp = m_rfLayer3.getIsGroup(); std::string source = m_lookup->find(srcId); LogMessage("NXDN, received RF header from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); m_display->writeNXDN(source.c_str(), grp, dstId, "R"); } return true; } else { if (m_rfState == RS_RF_LISTENING) { CNXDNFACCH1 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) { unsigned char buffer[10U]; facch.getData(buffer); CNXDNLayer3 layer3; layer3.decode(buffer, NXDN_FACCH1_LENGTH_BITS); hasInfo = layer3.getMessageType() == NXDN_MESSAGE_TYPE_VCALL; if (!hasInfo) return false; m_rfLayer3 = layer3; } if (!hasInfo) { unsigned char message[3U]; sacch.getData(message); unsigned char structure = sacch.getStructure(); switch (structure) { case NXDN_SR_1_4: m_rfLayer3.decode(message, 18U, 0U); if(m_rfLayer3.getMessageType() == NXDN_MESSAGE_TYPE_VCALL) m_rfMask = 0x01U; else m_rfMask = 0x00U; break; case NXDN_SR_2_4: m_rfMask |= 0x02U; m_rfLayer3.decode(message, 18U, 18U); break; case NXDN_SR_3_4: m_rfMask |= 0x04U; m_rfLayer3.decode(message, 18U, 36U); break; case NXDN_SR_4_4: m_rfMask |= 0x08U; m_rfLayer3.decode(message, 18U, 54U); break; default: break; } if (m_rfMask != 0x0FU) return false; unsigned char type = m_rfLayer3.getMessageType(); if (type != NXDN_MESSAGE_TYPE_VCALL) return false; } unsigned short srcId = m_rfLayer3.getSourceUnitId(); unsigned short dstId = m_rfLayer3.getDestinationGroupId(); bool grp = m_rfLayer3.getIsGroup(); if (m_selfOnly) { if (srcId != m_id) { m_rfState = RS_RF_REJECTED; return true; } } m_rfFrames = 0U; m_rfErrs = 0U; m_rfBits = 1U; m_rfTimeoutTimer.start(); m_rfState = RS_RF_AUDIO; m_minRSSI = m_rssi; m_maxRSSI = m_rssi; m_aveRSSI = m_rssi; m_rssiCount = 1U; #if defined(DUMP_NXDN) openFile(); #endif std::string source = m_lookup->find(srcId); LogMessage("NXDN, received RF late entry from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); m_display->writeNXDN(source.c_str(), grp, dstId, "R"); m_rfState = RS_RF_AUDIO; // Create a dummy start message unsigned char start[NXDN_FRAME_LENGTH_BYTES + 2U]; start[0U] = TAG_DATA; start[1U] = 0x00U; // Generate the sync CSync::addNXDNSync(start + 2U); // Generate the LICH CNXDNLICH lich; lich.setRFCT(NXDN_LICH_RFCT_RDCH); lich.setFCT(NXDN_LICH_USC_SACCH_NS); lich.setOption(NXDN_LICH_STEAL_FACCH); lich.setDirection(m_remoteGateway || !m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); lich.encode(start + 2U); lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); netData[0U] = lich.getRaw(); CNXDNSACCH sacch; sacch.setRAN(m_ran); sacch.setStructure(NXDN_SR_SINGLE); sacch.setData(SACCH_IDLE); sacch.encode(start + 2U); sacch.getRaw(netData + 1U); unsigned char message[22U]; 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); facch.getRaw(netData + 5U + 0U); facch.getRaw(netData + 5U + 14U); scrambler(start + 2U); writeNetwork(netData, NNMT_VOICE_HEADER); #if defined(DUMP_NXDN) writeFile(start + 2U); #endif if (m_duplex) writeQueueRF(start); } } if (m_rfState == RS_RF_AUDIO) { // Regenerate the sync CSync::addNXDNSync(data + 2U); // Regenerate the LICH CNXDNLICH lich; lich.setRFCT(NXDN_LICH_RFCT_RDCH); lich.setFCT(NXDN_LICH_USC_SACCH_SS); lich.setOption(option); lich.setDirection(m_remoteGateway || !m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); lich.encode(data + 2U); lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); netData[0U] = lich.getRaw(); // Regenerate SACCH if it's valid CNXDNSACCH sacch; bool validSACCH = sacch.decode(data + 2U); if (validSACCH) { sacch.setRAN(m_ran); sacch.encode(data + 2U); } sacch.getRaw(netData + 1U); // Regenerate the audio and interpret the FACCH1 data if (option == NXDN_LICH_STEAL_NONE) { CAMBEFEC ambe; unsigned int errors = 0U; errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); m_rfErrs += errors; m_rfBits += 188U; m_display->writeNXDNBER(float(errors) / 1.88F); LogDebug("NXDN, AMBE FEC %u/188 (%.1f%%)", errors, float(errors) / 1.88F); CNXDNAudio 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) { CNXDNFACCH1 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); facch1.getRaw(netData + 5U + 0U); CAMBEFEC ambe; unsigned int errors = 0U; errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); m_rfErrs += errors; m_rfBits += 94U; m_display->writeNXDNBER(float(errors) / 0.94F); LogDebug("NXDN, AMBE FEC %u/94 (%.1f%%)", errors, float(errors) / 0.94F); CNXDNAudio audio; audio.decode(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U, netData + 5U + 14U); } else if (option == NXDN_LICH_STEAL_FACCH1_2) { CAMBEFEC ambe; unsigned int errors = 0U; errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES); errors += ambe.regenerateYSFDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); m_rfErrs += errors; m_rfBits += 94U; m_display->writeNXDNBER(float(errors) / 0.94F); LogDebug("NXDN, AMBE FEC %u/94 (%.1f%%)", errors, float(errors) / 0.94F); CNXDNAudio audio; audio.decode(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U, netData + 5U + 0U); CNXDNFACCH1 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); facch1.getRaw(netData + 5U + 14U); } else { CNXDNFACCH1 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); facch11.getRaw(netData + 5U + 0U); CNXDNFACCH1 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); facch12.getRaw(netData + 5U + 14U); } data[0U] = TAG_DATA; data[1U] = 0x00U; scrambler(data + 2U); writeNetwork(netData, NNMT_VOICE_BODY); #if defined(DUMP_NXDN) writeFile(data + 2U); #endif if (m_duplex) writeQueueRF(data); m_rfFrames++; m_display->writeNXDNRSSI(m_rssi); } return true; } bool CNXDNControl::processData(unsigned char option, unsigned char *data) { CNXDNUDCH udch; bool validUDCH = udch.decode(data + 2U); if (m_rfState == RS_RF_LISTENING && !validUDCH) return false; if (validUDCH) { unsigned char ran = udch.getRAN(); if (ran != m_ran && ran != 0U) return false; } unsigned char netData[40U]; ::memset(netData, 0x00U, 40U); // The layer3 data will only be correct if valid is true unsigned char buffer[23U]; udch.getData(buffer); CNXDNLayer3 layer3; layer3.decode(buffer, 184U); if (m_rfState == RS_RF_LISTENING) { unsigned char type = layer3.getMessageType(); if (type != NXDN_MESSAGE_TYPE_DCALL_HDR) return false; unsigned short srcId = layer3.getSourceUnitId(); unsigned short dstId = layer3.getDestinationGroupId(); bool grp = layer3.getIsGroup(); if (m_selfOnly) { if (srcId != m_id) return false; } unsigned char frames = layer3.getDataBlocks(); std::string source = m_lookup->find(srcId); m_display->writeNXDN(source.c_str(), grp, dstId, "R"); m_display->writeNXDNRSSI(m_rssi); LogMessage("NXDN, received RF data header from %s to %s%u, %u blocks", source.c_str(), grp ? "TG " : "", dstId, frames); m_rfLayer3 = layer3; m_rfFrames = 0U; m_rfState = RS_RF_DATA; #if defined(DUMP_NXDN) openFile(); #endif } if (m_rfState != RS_RF_DATA) return false; CSync::addNXDNSync(data + 2U); CNXDNLICH lich; lich.setRFCT(NXDN_LICH_RFCT_RDCH); lich.setFCT(NXDN_LICH_USC_UDCH); lich.setOption(option); lich.setDirection(m_remoteGateway || !m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); lich.encode(data + 2U); lich.setDirection(NXDN_LICH_DIRECTION_INBOUND); netData[0U] = lich.getRaw(); udch.getRaw(netData + 1U); unsigned char type = NXDN_MESSAGE_TYPE_DCALL_DATA; if (validUDCH) { type = layer3.getMessageType(); data[0U] = type == NXDN_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; udch.setRAN(m_ran); udch.encode(data + 2U); } else { data[0U] = TAG_DATA; data[1U] = 0x00U; } scrambler(data + 2U); switch (type) { case NXDN_MESSAGE_TYPE_DCALL_HDR: writeNetwork(netData, NNMT_DATA_HEADER); break; case NXDN_MESSAGE_TYPE_TX_REL: writeNetwork(netData, NNMT_DATA_TRAILER); break; default: writeNetwork(netData, NNMT_DATA_BODY); break; } if (m_duplex) writeQueueRF(data); m_rfFrames++; #if defined(DUMP_NXDN) writeFile(data + 2U); #endif if (data[0U] == TAG_EOT) { unsigned short dstId = m_rfLayer3.getDestinationGroupId(); bool grp = m_rfLayer3.getIsGroup(); std::string source = m_lookup->find(m_rfLayer3.getSourceUnitId()); LogMessage("NXDN, ended RF data transmission from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); writeEndRF(); } return true; } unsigned int CNXDNControl::readModem(unsigned char* data) { assert(data != NULL); if (m_queue.isEmpty()) return 0U; unsigned char len = 0U; m_queue.getData(&len, 1U); m_queue.getData(data, len); return len; } void CNXDNControl::writeEndRF() { m_rfState = RS_RF_LISTENING; m_rfMask = 0x00U; m_rfLayer3.reset(); m_rfTimeoutTimer.stop(); if (m_netState == RS_NET_IDLE) { m_display->clearNXDN(); if (m_network != NULL) m_network->reset(); } #if defined(DUMP_NXDN) closeFile(); #endif } void CNXDNControl::writeEndNet() { m_netState = RS_NET_IDLE; m_netMask = 0x00U; m_netLayer3.reset(); m_netTimeoutTimer.stop(); m_networkWatchdog.stop(); m_packetTimer.stop(); m_display->clearNXDN(); if (m_network != NULL) m_network->reset(); } void CNXDNControl::writeNetwork() { unsigned char netData[100U]; bool exists = m_network->read(netData); if (!exists) return; if (!m_enabled) return; if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) return; m_networkWatchdog.start(); unsigned char data[NXDN_FRAME_LENGTH_BYTES + 2U]; CSync::addNXDNSync(data + 2U); CNXDNLICH lich; lich.setRaw(netData[0U]); unsigned char usc = lich.getFCT(); unsigned char option = lich.getOption(); lich.setDirection(m_remoteGateway || !m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); lich.encode(data + 2U); if (usc == NXDN_LICH_USC_UDCH) { CNXDNLayer3 layer3; layer3.setData(netData + 2U, 23U); unsigned char type = layer3.getMessageType(); if (m_netState == RS_NET_IDLE) { if (type == NXDN_MESSAGE_TYPE_DCALL_HDR) { unsigned short srcId = layer3.getSourceUnitId(); unsigned short dstId = layer3.getDestinationGroupId(); bool grp = layer3.getIsGroup(); unsigned char frames = layer3.getDataBlocks(); std::string source = m_lookup->find(srcId); m_display->writeNXDN(source.c_str(), grp, dstId, "N"); LogMessage("NXDN, received network data header from %s to %s%u, %u blocks", source.c_str(), grp ? "TG " : "", dstId, frames); m_netState = RS_NET_DATA; } else { return; } } if (m_netState == RS_NET_DATA) { data[0U] = type == NXDN_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; data[1U] = 0x00U; CNXDNUDCH udch; udch.setRAN(m_ran); udch.setData(netData + 2U); udch.encode(data + 2U); scrambler(data + 2U); writeQueueNet(data); if (type == NXDN_MESSAGE_TYPE_TX_REL) { unsigned short dstId = m_netLayer3.getDestinationGroupId(); bool grp = m_netLayer3.getIsGroup(); std::string source = m_lookup->find(m_netLayer3.getSourceUnitId()); LogMessage("NXDN, ended network data transmission from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); writeEndNet(); } } } else if (usc == NXDN_LICH_USC_SACCH_NS) { m_netLayer3.setData(netData + 5U + 0U, 10U); unsigned char type = m_netLayer3.getMessageType(); if (type == NXDN_MESSAGE_TYPE_TX_REL && m_netState == RS_NET_IDLE) return; if (type == NXDN_MESSAGE_TYPE_VCALL && m_netState != RS_NET_IDLE) return; CNXDNSACCH sacch; sacch.setRAN(m_ran); sacch.setStructure(NXDN_SR_SINGLE); sacch.setData(SACCH_IDLE); sacch.encode(data + 2U); CNXDNFACCH1 facch; facch.setRaw(netData + 5U + 0U); 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); data[0U] = type == NXDN_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; data[1U] = 0x00U; scrambler(data + 2U); writeQueueNet(data); unsigned short dstId = m_netLayer3.getDestinationGroupId(); bool grp = m_netLayer3.getIsGroup(); class CUserDBentry source; m_lookup->findWithName(m_netLayer3.getSourceUnitId(), &source); if (type == NXDN_MESSAGE_TYPE_TX_REL) { m_netFrames++; LogMessage("NXDN, received network end of transmission from %s to %s%u, %.1f seconds", source.get(keyCALLSIGN).c_str(), grp ? "TG " : "", dstId, float(m_netFrames) / 12.5F); writeEndNet(); } else if (type == NXDN_MESSAGE_TYPE_VCALL) { LogMessage("NXDN, received network transmission from %s to %s%u", source.get(keyCALLSIGN).c_str(), grp ? "TG " : "", dstId); m_display->writeNXDN(source, grp, dstId, "N"); m_netTimeoutTimer.start(); m_packetTimer.start(); m_elapsed.start(); m_netState = RS_NET_AUDIO; m_netFrames = 1U; } else { CUtils::dump(2U, "NXDN, interesting non superblock network frame", netData, 33U); return; } } else { if (m_netState == RS_NET_IDLE) { unsigned char structure = (netData[1U] >> 6) & 0x03U; switch (structure) { case NXDN_SR_1_4: m_netLayer3.decode(netData + 2U, 18U, 0U); if (m_netLayer3.getMessageType() == NXDN_MESSAGE_TYPE_VCALL) m_netMask = 0x01U; else m_netMask = 0x00U; break; case NXDN_SR_2_4: m_netMask |= 0x02U; m_netLayer3.decode(netData + 2U, 18U, 18U); break; case NXDN_SR_3_4: m_netMask |= 0x04U; m_netLayer3.decode(netData + 2U, 18U, 36U); break; case NXDN_SR_4_4: m_netMask |= 0x08U; m_netLayer3.decode(netData + 2U, 18U, 54U); break; default: break; } if (m_netMask != 0x0FU) return; unsigned char type = m_netLayer3.getMessageType(); if (type != NXDN_MESSAGE_TYPE_VCALL) return; unsigned short srcId = m_netLayer3.getSourceUnitId(); unsigned short dstId = m_netLayer3.getDestinationGroupId(); bool grp = m_netLayer3.getIsGroup(); class CUserDBentry source; m_lookup->findWithName(srcId, &source); LogMessage("NXDN, received network transmission from %s to %s%u", source.get(keyCALLSIGN).c_str(), grp ? "TG " : "", dstId); m_display->writeNXDN(source, grp, dstId, "N"); m_netTimeoutTimer.start(); m_packetTimer.start(); m_elapsed.start(); m_netState = RS_NET_AUDIO; m_netFrames = 1U; // Create a dummy start message unsigned char start[NXDN_FRAME_LENGTH_BYTES + 2U]; start[0U] = TAG_DATA; start[1U] = 0x00U; // Generate the sync CSync::addNXDNSync(start + 2U); // Generate the LICH CNXDNLICH lich; lich.setRFCT(NXDN_LICH_RFCT_RDCH); lich.setFCT(NXDN_LICH_USC_SACCH_NS); lich.setOption(NXDN_LICH_STEAL_FACCH); lich.setDirection(m_remoteGateway || !m_duplex ? NXDN_LICH_DIRECTION_INBOUND : NXDN_LICH_DIRECTION_OUTBOUND); lich.encode(start + 2U); CNXDNSACCH sacch; sacch.setRAN(m_ran); sacch.setStructure(NXDN_SR_SINGLE); sacch.setData(SACCH_IDLE); sacch.encode(start + 2U); unsigned char message[22U]; m_netLayer3.getData(message); CNXDNFACCH1 facch; 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); scrambler(start + 2U); writeQueueNet(start); } m_netFrames++; data[0U] = TAG_DATA; data[1U] = 0x00U; CNXDNSACCH sacch; sacch.setRaw(netData + 1U); sacch.setRAN(m_ran); sacch.encode(data + 2U); if (option == NXDN_LICH_STEAL_NONE) { CNXDNAudio audio; audio.encode(netData + 5U + 0U, data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); audio.encode(netData + 5U + 14U, data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); } else if (option == NXDN_LICH_STEAL_FACCH1_1) { CNXDNFACCH1 facch1; facch1.setRaw(netData + 5U + 0U); facch1.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); CNXDNAudio audio; audio.encode(netData + 5U + 14U, data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); } else if (option == NXDN_LICH_STEAL_FACCH1_2) { CNXDNAudio audio; audio.encode(netData + 5U + 0U, data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); CNXDNFACCH1 facch1; facch1.setRaw(netData + 5U + 14U); facch1.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); } else { CNXDNFACCH1 facch11; facch11.setRaw(netData + 5U + 0U); facch11.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS); CNXDNFACCH1 facch12; facch12.setRaw(netData + 5U + 14U); facch12.encode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_LENGTH_BITS + NXDN_FACCH1_LENGTH_BITS); } scrambler(data + 2U); writeQueueNet(data); } } void CNXDNControl::clock(unsigned int ms) { if (m_network != NULL) writeNetwork(); m_rfTimeoutTimer.clock(ms); m_netTimeoutTimer.clock(ms); if (m_netState == RS_NET_AUDIO) { m_networkWatchdog.clock(ms); if (m_networkWatchdog.hasExpired()) { LogMessage("NXDN, network watchdog has expired, %.1f seconds", float(m_netFrames) / 12.5F); writeEndNet(); } } } void CNXDNControl::writeQueueRF(const unsigned char *data) { assert(data != NULL); if (m_netState != RS_NET_IDLE) return; if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) return; unsigned char len = NXDN_FRAME_LENGTH_BYTES + 2U; unsigned int space = m_queue.freeSpace(); if (space < (len + 1U)) { LogError("NXDN, overflow in the NXDN RF queue"); return; } m_queue.addData(&len, 1U); m_queue.addData(data, len); } void CNXDNControl::writeQueueNet(const unsigned char *data) { assert(data != NULL); if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired()) return; unsigned char len = NXDN_FRAME_LENGTH_BYTES + 2U; unsigned int space = m_queue.freeSpace(); if (space < (len + 1U)) { LogError("NXDN, overflow in the NXDN RF queue"); return; } m_queue.addData(&len, 1U); m_queue.addData(data, len); } void CNXDNControl::writeNetwork(const unsigned char *data, NXDN_NETWORK_MESSAGE_TYPE type) { assert(data != NULL); if (m_network == NULL) return; if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) return; m_network->write(data, type); } void CNXDNControl::scrambler(unsigned char* data) const { assert(data != NULL); for (unsigned int i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) data[i] ^= SCRAMBLER[i]; } bool CNXDNControl::openFile() { if (m_fp != NULL) return true; time_t t; ::time(&t); struct tm* tm = ::localtime(&t); char name[100U]; ::sprintf(name, "NXDN_%04d%02d%02d_%02d%02d%02d.ambe", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); m_fp = ::fopen(name, "wb"); if (m_fp == NULL) return false; ::fwrite("NXDN", 1U, 4U, m_fp); return true; } bool CNXDNControl::writeFile(const unsigned char* data) { if (m_fp == NULL) return false; ::fwrite(data, 1U, NXDN_FRAME_LENGTH_BYTES, m_fp); return true; } void CNXDNControl::closeFile() { if (m_fp != NULL) { ::fclose(m_fp); m_fp = NULL; } } bool CNXDNControl::isBusy() const { return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE; } void CNXDNControl::enable(bool enabled) { if (!enabled && m_enabled) { m_queue.clear(); // Reset the RF section m_rfState = RS_RF_LISTENING; m_rfMask = 0x00U; m_rfLayer3.reset(); m_rfTimeoutTimer.stop(); // Reset the networking section m_netState = RS_NET_IDLE; m_netMask = 0x00U; m_netLayer3.reset(); m_netTimeoutTimer.stop(); m_networkWatchdog.stop(); m_packetTimer.stop(); } m_enabled = enabled; }