// SPDX-License-Identifier: GPL-2.0-only /* * Digital Voice Modem - Modem Host Software * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2024 Patrick McDonnell, W3AXL * */ #include "Defines.h" #include "common/p25/P25Defines.h" #include "common/p25/data/DataHeader.h" #include "common/p25/data/DataBlock.h" #include "common/p25/data/LowSpeedData.h" #include "common/p25/dfsi/LC.h" #include "common/p25/dfsi/frames/Frames.h" #include "common/p25/lc/LC.h" #include "common/p25/lc/tdulc/TDULCFactory.h" #include "common/p25/lc/tsbk/TSBKFactory.h" #include "common/p25/NID.h" #include "common/p25/P25Utils.h" #include "common/p25/Sync.h" #include "common/Log.h" #include "common/Utils.h" #include "modem/ModemV24.h" using namespace modem; using namespace p25; using namespace p25::defines; using namespace p25::dfsi::defines; using namespace p25::dfsi::frames; #include // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /* Initializes a new instance of the ModemV24 class. */ ModemV24::ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, uint32_t p25TxQueueSize, bool rtrt, uint16_t jitter, bool dumpModemStatus, bool displayDebugMessages, bool trace, bool debug) : Modem(port, duplex, false, false, false, false, false, 80U, 7U, 8U, 1U, p25QueueSize, 1U, false, false, dumpModemStatus, displayDebugMessages, trace, debug), m_rtrt(rtrt), m_superFrameCnt(0U), m_audio(), m_nid(nullptr), m_txP25Queue(p25TxQueueSize, "TX P25 Queue"), m_txCall(), m_rxCall(), m_txCallInProgress(false), m_rxCallInProgress(false), m_txLastFrameTime(0U), m_rxLastFrameTime(0U), m_callTimeout(200U), m_jitter(jitter), m_lastP25Tx(0U), m_rs(), m_useTIAFormat(false) { m_v24Connected = false; // defaulted to false for V.24 modems // Init m_call m_txCall = new DFSICallData(); m_rxCall = new DFSICallData(); } /* Finalizes a instance of the Modem class. */ ModemV24::~ModemV24() { delete m_nid; delete m_txCall; delete m_rxCall; } /* Sets the call timeout. */ void ModemV24::setCallTimeout(uint16_t timeout) { m_callTimeout = timeout; } /* Sets the P25 NAC. */ void ModemV24::setP25NAC(uint32_t nac) { Modem::setP25NAC(nac); m_nid = new NID(nac); } /* Helper to set the TIA-102 format DFSI frame flag. */ void ModemV24::setTIAFormat(bool set) { m_useTIAFormat = set; } /* Opens connection to the air interface modem. */ bool ModemV24::open() { LogInfoEx(LOG_MODEM, "Initializing modem"); m_gotModemStatus = false; bool ret = m_port->open(); if (!ret) return false; ret = getFirmwareVersion(); if (!ret) { m_port->close(); return false; } else { // Stopping the inactivity timer here when a firmware version has been // successfuly read prevents the death spiral of "no reply from modem..." m_inactivityTimer.stop(); } m_rspOffset = 0U; m_rspState = RESP_START; // do we have an open port handler? if (m_openPortHandler) { ret = m_openPortHandler(this); if (!ret) return false; m_error = false; return true; } m_statusTimer.start(); m_error = false; if (m_useTIAFormat) LogInfoEx(LOG_MODEM, "Modem Ready [Direct Mode / TIA-102]"); else LogInfoEx(LOG_MODEM, "Modem Ready [Direct Mode / V.24]"); return true; } /* Updates the timer by the passed number of milliseconds. */ void ModemV24::clock(uint32_t ms) { // poll the modem status m_statusTimer.clock(ms); if (m_statusTimer.hasExpired()) { getStatus(); m_statusTimer.start(); } m_inactivityTimer.clock(ms); if (m_inactivityTimer.hasExpired()) { LogError(LOG_MODEM, "No reply from the modem for some time, resetting it"); reset(); } uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); bool forceModemReset = false; RESP_TYPE_DVM type = getResponse(); // do we have a custom response handler? if (m_rspHandler != nullptr) { // execute custom response handler if (m_rspHandler(this, ms, type, m_rspDoubleLength, m_buffer, m_length)) { // all logic handled by handler -- return return; } } if (type == RTM_TIMEOUT) { // Nothing to do } else if (type == RTM_ERROR) { // Nothing to do } else { // type == RTM_OK uint8_t cmdOffset = 2U; if (m_rspDoubleLength) { cmdOffset = 3U; } switch (m_buffer[cmdOffset]) { /** Project 25 */ case CMD_P25_DATA: { if (m_p25Enabled) { std::lock_guard lock(m_p25ReadLock); // convert data from V.24/DFSI formatting to TIA-102 air formatting if (m_useTIAFormat) convertToAirTIA(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); else convertToAirV24(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); if (m_trace) Utils::dump(1U, "ModemV24::clock(), RX P25 Data", m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U)); } } break; case CMD_P25_LOST: { if (m_p25Enabled) { std::lock_guard lock(m_p25ReadLock); if (m_rspDoubleLength) { LogError(LOG_MODEM, "CMD_P25_LOST double length?; len = %u", m_length); break; } uint8_t data = 1U; m_rxP25Queue.addData(&data, 1U); data = TAG_LOST; m_rxP25Queue.addData(&data, 1U); } } break; /** General */ case CMD_GET_STATUS: { m_isHotspot = (m_buffer[3U] & 0x01U) == 0x01U; // override hotspot flag if we're forcing hotspot if (m_forceHotspot) { m_isHotspot = m_forceHotspot; } bool dmrEnable = (m_buffer[3U] & 0x02U) == 0x02U; bool p25Enable = (m_buffer[3U] & 0x08U) == 0x08U; bool nxdnEnable = (m_buffer[3U] & 0x10U) == 0x10U; // flag indicating if free space is being reported in 16-byte blocks instead of LDUs bool spaceInBlocks = (m_buffer[3U] & 0x80U) == 0x80U; m_v24Connected = (m_buffer[3U] & 0x40U) == 0x40U; m_modemState = (DVM_STATE)m_buffer[4U]; m_tx = (m_buffer[5U] & 0x01U) == 0x01U; bool adcOverflow = (m_buffer[5U] & 0x02U) == 0x02U; if (adcOverflow) { //LogError(LOG_MODEM, "ADC levels have overflowed"); m_adcOverFlowCount++; if (m_adcOverFlowCount >= MAX_ADC_OVERFLOW / 2U) { LogWarning(LOG_MODEM, "ADC overflow count > %u!", MAX_ADC_OVERFLOW / 2U); } if (!m_disableOFlowReset) { if (m_adcOverFlowCount > MAX_ADC_OVERFLOW) { LogError(LOG_MODEM, "ADC overflow count > %u, resetting modem", MAX_ADC_OVERFLOW); forceModemReset = true; } } else { m_adcOverFlowCount = 0U; } } else { if (m_adcOverFlowCount != 0U) { m_adcOverFlowCount--; } } bool rxOverflow = (m_buffer[5U] & 0x04U) == 0x04U; if (rxOverflow) LogError(LOG_MODEM, "RX buffer has overflowed"); bool txOverflow = (m_buffer[5U] & 0x08U) == 0x08U; if (txOverflow) LogError(LOG_MODEM, "TX buffer has overflowed"); m_lockout = (m_buffer[5U] & 0x10U) == 0x10U; bool dacOverflow = (m_buffer[5U] & 0x20U) == 0x20U; if (dacOverflow) { //LogError(LOG_MODEM, "DAC levels have overflowed"); m_dacOverFlowCount++; if (m_dacOverFlowCount > MAX_DAC_OVERFLOW / 2U) { LogWarning(LOG_MODEM, "DAC overflow count > %u!", MAX_DAC_OVERFLOW / 2U); } if (!m_disableOFlowReset) { if (m_dacOverFlowCount > MAX_DAC_OVERFLOW) { LogError(LOG_MODEM, "DAC overflow count > %u, resetting modem", MAX_DAC_OVERFLOW); forceModemReset = true; } } else { m_dacOverFlowCount = 0U; } } else { if (m_dacOverFlowCount != 0U) { m_dacOverFlowCount--; } } m_cd = (m_buffer[5U] & 0x40U) == 0x40U; // spaces from the modem are returned in "logical" frame count, or a block size, not raw byte size // DMR and NXDN space are always 0U since the board doesn't support them m_dmrSpace1 = 0U; m_dmrSpace2 = 0U; m_nxdnSpace = 0U; // P25 free space can be reported as 16-byte blocks or frames based on the flag above if (spaceInBlocks) m_p25Space = m_buffer[10U] * P25_BUFFER_BLOCK_SIZE; else m_p25Space = m_buffer[10U] * (P25DEF::P25_LDU_FRAME_LENGTH_BYTES); if (m_dumpModemStatus) { LogDebugEx(LOG_MODEM, "ModemV24::clock()", "CMD_GET_STATUS, isHotspot = %u, v24Connected = %u, dmr = %u / %u, p25 = %u / %u, nxdn = %u / %u, modemState = %u, tx = %u, adcOverflow = %u, rxOverflow = %u, txOverflow = %u, dacOverflow = %u, dmrSpace1 = %u, dmrSpace2 = %u, p25Space = %u, nxdnSpace = %u", m_isHotspot, m_v24Connected, dmrEnable, m_dmrEnabled, p25Enable, m_p25Enabled, nxdnEnable, m_nxdnEnabled, m_modemState, m_tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_dmrSpace1, m_dmrSpace2, m_p25Space, m_nxdnSpace); LogDebugEx(LOG_MODEM, "ModemV24::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_rxDMRQueue1.length(), m_rxDMRQueue1.dataSize(), m_rxDMRQueue1.freeSpace(), m_rxDMRQueue2.length(), m_rxDMRQueue2.dataSize(), m_rxDMRQueue2.freeSpace(), m_rxP25Queue.length(), m_rxP25Queue.dataSize(), m_rxP25Queue.freeSpace(), m_rxNXDNQueue.length(), m_rxNXDNQueue.dataSize(), m_rxNXDNQueue.freeSpace()); } m_gotModemStatus = true; m_inactivityTimer.start(); } break; case CMD_GET_VERSION: case CMD_ACK: break; case CMD_NAK: { LogWarning(LOG_MODEM, "NAK, command = 0x%02X (%s), reason = %u (%s)", m_buffer[3U], cmdToString(m_buffer[3U]).c_str(), m_buffer[4U], rsnToString(m_buffer[4U]).c_str()); switch (m_buffer[4U]) { case RSN_RINGBUFF_FULL: { switch (m_buffer[3U]) { case CMD_DMR_DATA1: LogWarning(LOG_MODEM, "NAK, %s, dmrSpace1 = %u", rsnToString(m_buffer[4U]).c_str(), m_dmrSpace1); break; case CMD_DMR_DATA2: LogWarning(LOG_MODEM, "NAK, %s, dmrSpace2 = %u", rsnToString(m_buffer[4U]).c_str(), m_dmrSpace2); break; case CMD_P25_DATA: LogWarning(LOG_MODEM, "NAK, %s, p25Space = %u", rsnToString(m_buffer[4U]).c_str(), m_p25Space); break; case CMD_NXDN_DATA: LogWarning(LOG_MODEM, "NAK, %s, nxdnSpace = %u", rsnToString(m_buffer[4U]).c_str(), m_nxdnSpace); break; } } break; } } break; case CMD_DEBUG1: case CMD_DEBUG2: case CMD_DEBUG3: case CMD_DEBUG4: case CMD_DEBUG5: case CMD_DEBUG_DUMP: printDebug(m_buffer, m_length); break; default: LogWarning(LOG_MODEM, "Unknown message, type = %02X", m_buffer[2U]); Utils::dump("ModemV24::clock(), m_buffer", m_buffer, m_length); if (m_rspState != RESP_START) m_rspState = RESP_START; break; } } // force a modem reset because of a error condition if (forceModemReset) { forceModemReset = false; reset(); } // write anything waiting to the serial port int len = writeSerial(); if (m_debug && len > 0) { LogDebug(LOG_MODEM, "Wrote %u-byte message to the serial V24 device", len); } else if (len < 0) { LogError(LOG_MODEM, "Failed to write to serial port!"); } // clear an RX call in progress flag if we're longer than our timeout value now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (m_rxCallInProgress && (now - m_rxLastFrameTime > m_callTimeout)) { m_rxCallInProgress = false; m_rxCall->resetCallData(); LogWarning(LOG_MODEM, "No call data received from V24 for %u ms, resetting RX call", (now - m_rxLastFrameTime)); } } /* Closes connection to the air interface modem. */ void ModemV24::close() { LogInfoEx(LOG_MODEM, "Closing the modem"); m_port->close(); m_gotModemStatus = false; // do we have a close port handler? if (m_closePortHandler != nullptr) { m_closePortHandler(this); } } /* Helper to test if the P25 ring buffer has free space. */ bool ModemV24::hasP25Space(uint32_t length) const { return Modem::hasP25Space(length); } /* Writes raw data to the air interface modem. */ int ModemV24::write(const uint8_t* data, uint32_t length) { assert(data != nullptr); uint8_t modemCommand = CMD_GET_VERSION; if (data[0U] == DVM_SHORT_FRAME_START) { modemCommand = data[2U]; } else if (data[0U] == DVM_LONG_FRAME_START) { modemCommand = data[3U]; } if (modemCommand == CMD_P25_DATA) { DECLARE_UINT8_ARRAY(buffer, length); ::memcpy(buffer, data + 2U, length); if (m_useTIAFormat) convertFromAirTIA(buffer, length); else convertFromAirV24(buffer, length); return length; } else { return Modem::write(data, length); } } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /* Helper to write data from the P25 Tx queue to the serial interface. */ int ModemV24::writeSerial() { /* * Serial TX ringbuffer format: * * | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x0A | 0x0B | 0x0C | ... | * | Length | Tag | int64_t timestamp in ms | data | */ // check empty if (m_txP25Queue.isEmpty()) return 0U; // get length uint8_t length[2U]; ::memset(length, 0x00U, 2U); m_txP25Queue.peek(length, 2U); // convert length byets to int uint16_t len = 0U; len = (length[0U] << 8) + length[1U]; // this ensures we never get in a situation where we have length & type bytes stuck in the queue by themselves if (m_txP25Queue.dataSize() == 2U && len > m_txP25Queue.dataSize()) { m_txP25Queue.get(length, 2U); // ensure we pop bytes off return 0U; } // get current timestamp int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); // peek the timestamp to see if we should wait if (m_txP25Queue.dataSize() >= 11U) { uint8_t lengthTagTs[11U]; ::memset(lengthTagTs, 0x00U, 11U); m_txP25Queue.peek(lengthTagTs, 11U); // get the timestamp int64_t ts; assert(sizeof ts == 8); ::memcpy(&ts, lengthTagTs + 3U, 8U); // if it's not time to send, return if (ts > now) { return 0U; } } // check if we have enough data to get everything - len + 2U (length bytes) + 1U (tag) + 8U (timestamp) if (m_txP25Queue.dataSize() >= len + 11U) { // Get the length, tag and timestamp uint8_t lengthTagTs[11U]; m_txP25Queue.get(lengthTagTs, 11U); // Get the actual data DECLARE_UINT8_ARRAY(buffer, len); m_txP25Queue.get(buffer, len); // Sanity check on data tag uint8_t tag = lengthTagTs[2U]; if (tag != TAG_DATA) { LogError(LOG_MODEM, "Got unexpected data tag from TX P25 ringbuffer! %02X", tag); return 0U; } // we already checked the timestamp above, so we just get the data and write it return m_port->write(buffer, len); } return 0U; } /* Helper to store converted Rx frames. */ void ModemV24::storeConvertedRx(const uint8_t* buffer, uint32_t length) { // store converted frame into the Rx modem queue uint8_t storedLen[2U]; if (length > 255U) storedLen[0U] = (length >> 8U) & 0xFFU; else storedLen[0U] = 0x00U; storedLen[1U] = length & 0xFFU; m_rxP25Queue.addData(storedLen, 2U); // Utils::dump("ModemV24::storeConvertedRx(), Storing Converted Rx Data", buffer, length); m_rxP25Queue.addData(buffer, length); } /* Internal helper to store converted PDU Rx frames. */ void ModemV24::storeConvertedRxPDU(data::DataHeader& dataHeader, uint8_t* pduUserData) { assert(pduUserData != nullptr); uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; if (dataHeader.getPadLength() > 0U) bitLength += (dataHeader.getPadLength() * 8U); uint32_t offset = P25_PREAMBLE_LENGTH_BITS; DECLARE_UINT8_ARRAY(pdu, (bitLength / 8U) + 1U); uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); // generate the PDU header and 1/2 rate Trellis dataHeader.encode(block); Utils::setBitRange(block, pdu, offset, P25_PDU_FEC_LENGTH_BITS); offset += P25_PDU_FEC_LENGTH_BITS; if (blocksToFollow > 0U) { uint32_t dataOffset = 0U; uint32_t packetLength = dataHeader.getPDULength(); // generate the PDU data for (uint32_t i = 0U; i < blocksToFollow; i++) { data::DataBlock dataBlock = data::DataBlock(); dataBlock.setFormat(dataHeader); dataBlock.setSerialNo(i); dataBlock.setData(pduUserData + dataOffset); dataBlock.setLastBlock((i + 1U) == blocksToFollow); ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); dataBlock.encode(block); Utils::setBitRange(block, pdu, offset, P25_PDU_FEC_LENGTH_BITS); offset += P25_PDU_FEC_LENGTH_BITS; dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; } } uint8_t data[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); // add the data uint32_t newBitLength = P25Utils::encodeByLength(pdu, data + 2U, bitLength); uint32_t newByteLength = newBitLength / 8U; if ((newBitLength % 8U) > 0U) newByteLength++; // generate Sync Sync::addP25Sync(data + 2U); // generate NID m_nid->encode(data + 2U, DUID::PDU); // add status bits P25Utils::addStatusBits(data + 2U, newBitLength, true, true); P25Utils::setStatusBitsStartIdle(data + 2U); //Utils::dump("P25, Data::writeRF_PDU(), Raw PDU OSP", data, newByteLength + 2U); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; storeConvertedRx(data, newByteLength + 2U); } /* Helper to generate a P25 TDU packet. */ void ModemV24::create_TDU(uint8_t* buffer) { assert(buffer != nullptr); uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); // generate Sync Sync::addP25Sync(data + 2U); // generate NID m_nid->encode(data + 2U, DUID::TDU); // add status bits P25Utils::addStatusBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, false, false); buffer[0U] = modem::TAG_EOT; buffer[1U] = 0x01U; ::memcpy(buffer, data, P25_TDU_FRAME_LENGTH_BYTES + 2U); } /* Internal helper to convert from V.24/DFSI to TIA-102 air interface. */ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) { assert(data != nullptr); assert(length > 0U); uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); // get the DFSI data (skip the 0x00 padded byte at the start) DECLARE_UINT8_ARRAY(dfsiData, length - 1U); ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) Utils::dump("ModemV24::convertToAirV24(), V.24 RX Data From Modem", dfsiData, length - 1U); DFSIFrameType::E frameType = (DFSIFrameType::E)dfsiData[0U]; m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); // Switch based on DFSI frame type switch (frameType) { case DFSIFrameType::MOT_START_STOP: { MotStartOfStream start = MotStartOfStream(dfsiData); if (start.getParam1() == DSFI_MOT_ICW_PARM_PAYLOAD) { m_rxCall->resetCallData(); m_rxCallInProgress = true; if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } } else { if (m_rxCallInProgress) { m_rxCall->resetCallData(); m_rxCallInProgress = false; if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, ICW, opcode = $%02X, type = $%02X", start.getOpcode(), start.getArgument1()); } // generate a TDU create_TDU(buffer); storeConvertedRx(buffer, P25_TDU_FRAME_LENGTH_BYTES + 2U); } } } break; case DFSIFrameType::MOT_VHDR_1: { MotStartOfStream start = MotStartOfStream(dfsiData); // copy to call data VHDR1 ::memset(m_rxCall->VHDR1, 0x00U, DFSI_MOT_VHDR_1_LEN); ::memcpy(m_rxCall->VHDR1, dfsiData + DFSI_MOT_START_LEN, DFSI_MOT_VHDR_1_LEN - DFSI_MOT_START_LEN); if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirV24(), V.24 RX, VHDR1", m_rxCall->VHDR1, DFSI_MOT_VHDR_1_LEN); } break; case DFSIFrameType::MOT_VHDR_2: { MotStartOfStream start = MotStartOfStream(dfsiData); // copy to call data VHDR2 ::memset(m_rxCall->VHDR2, 0x00U, DFSI_MOT_VHDR_2_LEN); ::memcpy(m_rxCall->VHDR2, dfsiData + 1U, DFSI_MOT_VHDR_2_LEN); if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirV24(), V.24 RX, VHDR2", m_rxCall->VHDR2, DFSI_MOT_VHDR_2_LEN); // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; ::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN); ::memcpy(raw + 0U, m_rxCall->VHDR1, 8U); ::memcpy(raw + 8U, m_rxCall->VHDR1 + 9U, 8U); ::memcpy(raw + 16U, m_rxCall->VHDR1 + 18U, 2U); ::memcpy(raw + 18U, m_rxCall->VHDR2, 8U); ::memcpy(raw + 26U, m_rxCall->VHDR2 + 9U, 8U); ::memcpy(raw + 34U, m_rxCall->VHDR2 + 18U, 2U); // Utils::dump("ModemV24::convertToAirV24(), V.24 RX VHDR, raw", raw, DFSI_VHDR_RAW_LEN); // buffer for decoded VHDR data uint8_t vhdr[DFSI_VHDR_LEN]; uint32_t offset = 0U; for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) Utils::hex2Bin(raw[i], vhdr, offset); if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirV24() V.24 RX VHDR, VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); // try to decode the RS data try { bool ret = m_rs.decode362017(vhdr); if (!ret) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); } else { if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirV24(), V.24 RX VHDR, VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); // late entry? if (!m_rxCallInProgress) { m_rxCallInProgress = true; m_rxCall->resetCallData(); if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX VHDR late entry, resetting call data"); } uint32_t dstId = GET_UINT16(vhdr, 13U); if (m_rxCallInProgress && dstId == 0U) { LogWarning(LOG_MODEM, "V.24/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); m_rxCall->dstId = dstId; if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); } // generate a HDU lc::LC lc = lc::LC(); lc.setDstId(m_rxCall->dstId); lc.setAlgId(m_rxCall->algoId); lc.setKId(m_rxCall->kId); lc.setMI(m_rxCall->MI); // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::HDU); // generate HDU lc.encodeHDU(buffer + 2U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U); } } catch (...) { LogError(LOG_MODEM, "V.24/DFSI RX traffic got exception while trying to decode RS data for VHDR"); } } break; // VOICE1/10 create a start voice frame case DFSIFrameType::LDU1_VOICE1: { MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); m_rxCall->errors = 0U; // process start of stream ICW for the voice call uint8_t* icw = svf.startOfStream->getICW(); uint8_t payloadType = MotStreamPayload::VOICE; uint8_t rssi = 20U; if (icw != nullptr) { for (uint8_t i = 0U; i < 6U; i += 2U) { uint8_t param = icw[i]; uint8_t value = icw[i + 1U]; switch (param) { case DSFI_MOT_ICW_PARM_PAYLOAD: payloadType = value; break; case DFSI_MOT_ICW_PARM_RSSI1: rssi = value; break; case DFSI_MOT_ICW_PARM_RSSI2: // don't do anything with this RSSI break; case DFSI_MOT_ICW_TX_ADDRESS: case DFSI_MOT_ICW_RX_ADDRESS: // don't do anything with this ICW break; default: LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); break; } } } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, Start of Voice LDU1, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } if (svf.fullRateVoice->getTotalErrors() > 0U) { if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); m_rxCall->errors += svf.fullRateVoice->getTotalErrors(); } m_rxCall->n++; } break; case DFSIFrameType::LDU2_VOICE10: { MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData); ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES); m_rxCall->errors = 0U; // process start of stream ICW for the voice call uint8_t* icw = svf.startOfStream->getICW(); uint8_t payloadType = MotStreamPayload::VOICE; uint8_t rssi = 20U; if (icw != nullptr) { for (uint8_t i = 0U; i < 6U; i += 2U) { uint8_t param = icw[i]; uint8_t value = icw[i + 1U]; switch (param) { case DSFI_MOT_ICW_PARM_PAYLOAD: payloadType = value; break; case DFSI_MOT_ICW_PARM_RSSI1: rssi = value; break; case DFSI_MOT_ICW_PARM_RSSI2: // don't do anything with this RSSI break; case DFSI_MOT_ICW_TX_ADDRESS: case DFSI_MOT_ICW_RX_ADDRESS: // don't do anything with this ICW break; default: LogWarning(LOG_MODEM, "ModemV24::convertToAirV24() unknown ICW parameter $%02X with value %u", param, value); break; } } } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, Start of Voice LDU2, ICW opcode = $%02X, rssi = %u", svf.startOfStream->getOpcode(), rssi); } if (svf.fullRateVoice->getTotalErrors() > 0U) { if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", svf.fullRateVoice->getTotalErrors(), svf.fullRateVoice->getFrameType()); m_rxCall->errors += svf.fullRateVoice->getTotalErrors(); } m_rxCall->n++; } break; case DFSIFrameType::MOT_TDULC: { MotTDULCFrame tf = MotTDULCFrame(dfsiData); lc::tdulc::LC_TDULC_RAW tdulc = lc::tdulc::LC_TDULC_RAW(); if (!tdulc.decode(tf.tdulcData, true)) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode TDULC FEC"); } else { uint8_t buffer[P25_TDULC_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES + 2U); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::TDULC); // regenerate TDULC Data tdulc.encode(buffer + 2U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES, false, true); P25Utils::addIdleStatusBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES); P25Utils::setStatusBitsStartIdle(buffer + 2U); storeConvertedRx(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U); } } break; case DFSIFrameType::MOT_PDU_SINGLE_UNCONF: { m_rxCall->resetCallData(); uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); data::DataHeader pduHeader = data::DataHeader(); bool ret = pduHeader.decode(header, true); if (!ret) { LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); break; } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), pduHeader.getHeaderOffset(), pduHeader.getLLId()); } m_rxCall->dataCall = true; m_rxCall->dataHeader = pduHeader; for (uint8_t i = 0U; i < pduHeader.getBlocksToFollow() + 1U; i++) { uint8_t dataBlock[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; ::memset(dataBlock, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES), P25_PDU_UNCONFIRMED_LENGTH_BYTES); uint32_t offset = i * P25_PDU_UNCONFIRMED_LENGTH_BYTES; ::memcpy(m_rxCall->pduUserData + offset, dataBlock, P25_PDU_UNCONFIRMED_LENGTH_BYTES); } storeConvertedRxPDU(pduHeader, m_rxCall->pduUserData); } break; case DFSIFrameType::MOT_PDU_UNCONF_HEADER: { m_rxCall->resetCallData(); uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); data::DataHeader pduHeader = data::DataHeader(); bool ret = pduHeader.decode(header, true); if (!ret) { LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); break; } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), pduHeader.getHeaderOffset(), pduHeader.getLLId()); } // make sure we don't get a PDU with more blocks then we support if (pduHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { LogError(LOG_MODEM, P25_PDU_STR ", ISP, too many PDU blocks to process, %u > %u", pduHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); m_rxCall->resetCallData(); break; } m_rxCall->dataCall = true; m_rxCall->dataHeader = pduHeader; // PDU_UNCONF_HEADER only contains 3 blocks for (uint8_t i = 0U; i < 3U; i++) { uint8_t dataBlock[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; ::memset(dataBlock, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES), P25_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_UNCONFIRMED_LENGTH_BYTES); m_rxCall->pduUserDataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; m_rxCall->pduTotalBlocks++; } } break; case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_1: case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_2: case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_3: case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_4: case DFSIFrameType::MOT_PDU_UNCONF_END: { // only process blocks if we've received a header and started a data call if (!m_rxCall->dataCall) break; uint32_t blockCnt = DFSI_PDU_BLOCK_CNT; // PDU_UNCONF_END are variable length depending on the message if (frameType == DFSIFrameType::MOT_PDU_UNCONF_END) { blockCnt = m_rxCall->dataHeader.getBlocksToFollow() - m_rxCall->pduTotalBlocks; // bryanb: I wonder if there's a chance somehow the calculation will be less then zero...reasonably // as far as I can tell that should never happen as PDU_UNCONF_BLOCK_X should *always* contain // 4 blocks of user data, and the PDU_UNCONF_END is always variable with at least the last data block } // PDU_UNCONF_BLOCK_X and PDU_UNCONF_END only contains 4 blocks each for (uint8_t i = 0U; i < blockCnt; i++) { uint8_t dataBlock[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; ::memset(dataBlock, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES), P25_PDU_UNCONFIRMED_LENGTH_BYTES); ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_UNCONFIRMED_LENGTH_BYTES); m_rxCall->pduUserDataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; m_rxCall->pduTotalBlocks++; } if (frameType == DFSIFrameType::MOT_PDU_UNCONF_END) { storeConvertedRxPDU(m_rxCall->dataHeader, m_rxCall->pduUserData); } } break; case DFSIFrameType::MOT_PDU_SINGLE_CONF: { m_rxCall->resetCallData(); uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); data::DataHeader pduHeader = data::DataHeader(); bool ret = pduHeader.decode(header, true); if (!ret) { LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); break; } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), pduHeader.getHeaderOffset(), pduHeader.getLLId()); } m_rxCall->dataCall = true; m_rxCall->dataHeader = pduHeader; for (uint8_t i = 0U; i < pduHeader.getBlocksToFollow() + 1U; i++) { uint8_t dataBlock[P25_PDU_CONFIRMED_LENGTH_BYTES]; ::memset(dataBlock, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_CONFIRMED_LENGTH_BYTES), P25_PDU_CONFIRMED_LENGTH_BYTES); uint32_t offset = i * P25_PDU_CONFIRMED_LENGTH_BYTES; ::memcpy(m_rxCall->pduUserData + offset, dataBlock, P25_PDU_CONFIRMED_LENGTH_BYTES); } storeConvertedRxPDU(pduHeader, m_rxCall->pduUserData); } break; case DFSIFrameType::MOT_PDU_CONF_HEADER: { m_rxCall->resetCallData(); uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); data::DataHeader pduHeader = data::DataHeader(); bool ret = pduHeader.decode(header, true); if (!ret) { LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); break; } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), pduHeader.getHeaderOffset(), pduHeader.getLLId()); } // make sure we don't get a PDU with more blocks then we support if (pduHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { LogError(LOG_MODEM, P25_PDU_STR ", ISP, too many PDU blocks to process, %u > %u", pduHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); m_rxCall->resetCallData(); break; } m_rxCall->dataCall = true; m_rxCall->dataHeader = pduHeader; // PDU_CONF_HEADER only contains 3 blocks for (uint8_t i = 0U; i < 3U; i++) { uint8_t dataBlock[P25_PDU_CONFIRMED_LENGTH_BYTES]; ::memset(dataBlock, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_CONFIRMED_LENGTH_BYTES), P25_PDU_CONFIRMED_LENGTH_BYTES); ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_CONFIRMED_LENGTH_BYTES); m_rxCall->pduUserDataOffset += P25_PDU_CONFIRMED_LENGTH_BYTES; m_rxCall->pduTotalBlocks++; } } break; case DFSIFrameType::MOT_PDU_CONF_BLOCK_1: case DFSIFrameType::MOT_PDU_CONF_BLOCK_2: case DFSIFrameType::MOT_PDU_CONF_BLOCK_3: case DFSIFrameType::MOT_PDU_CONF_BLOCK_4: case DFSIFrameType::MOT_PDU_CONF_END: { // only process blocks if we've received a header and started a data call if (!m_rxCall->dataCall) break; uint32_t blockCnt = DFSI_PDU_BLOCK_CNT; // PDU_CONF_END are variable length depending on the message if (frameType == DFSIFrameType::MOT_PDU_CONF_END) { blockCnt = m_rxCall->dataHeader.getBlocksToFollow() - m_rxCall->pduTotalBlocks; // bryanb: I wonder if there's a chance somehow the calculation will be less then zero...reasonably // as far as I can tell that should never happen as PDU_CONF_BLOCK_X should *always* contain // 4 blocks of user data, and the PDU_CONF_END is always variable with at least the last data block } // PDU_CONF_BLOCK_X only contains 4 blocks each for (uint8_t i = 0U; i < blockCnt; i++) { uint8_t dataBlock[P25_PDU_CONFIRMED_LENGTH_BYTES]; ::memset(dataBlock, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_CONFIRMED_LENGTH_BYTES), P25_PDU_CONFIRMED_LENGTH_BYTES); ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_CONFIRMED_LENGTH_BYTES); m_rxCall->pduUserDataOffset += P25_PDU_CONFIRMED_LENGTH_BYTES; m_rxCall->pduTotalBlocks++; } if (frameType == DFSIFrameType::MOT_PDU_CONF_END) { storeConvertedRxPDU(m_rxCall->dataHeader, m_rxCall->pduUserData); } } break; case DFSIFrameType::MOT_TSBK: { MotTSBKFrame tf = MotTSBKFrame(dfsiData); lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); if (!tsbk.decode(tf.tsbkData, true)) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode TSBK FEC"); } else { uint8_t buffer[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES + 2U); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x00U; // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::TSDU); // regenerate TSDU Data tsbk.setLastBlock(true); // always set last block -- this a Single Block TSDU tsbk.encode(buffer + 2U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES, false, true); P25Utils::addIdleStatusBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES); P25Utils::setStatusBitsStartIdle(buffer + 2U); storeConvertedRx(buffer, P25_TSDU_FRAME_LENGTH_BYTES + 2U); } } break; // The remaining LDUs all create full rate voice frames so we do that here default: { MotFullRateVoice voice = MotFullRateVoice(dfsiData); if (m_debug) { LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "Full Rate Voice, frameType = $%02X, errors = %u, busy = %u", voice.getFrameType(), voice.getTotalErrors(), voice.getBusy()); Utils::dump(1U, "ModemV24::converToAirV24(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } if (voice.getTotalErrors() > 0U) { if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); m_rxCall->errors += voice.getTotalErrors(); } switch (frameType) { case DFSIFrameType::LDU1_VOICE2: { ::memcpy(m_rxCall->netLDU1 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; case DFSIFrameType::LDU1_VOICE3: { ::memcpy(m_rxCall->netLDU1 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->lco = voice.additionalData[0U]; m_rxCall->mfId = voice.additionalData[1U]; m_rxCall->serviceOptions = voice.additionalData[2U]; // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[0U] = voice.additionalData[0U]; m_rxCall->LDULC[1U] = voice.additionalData[1U]; m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC3 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE4: { ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->dstId = GET_UINT24(voice.additionalData, 0U); // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[3U] = voice.additionalData[0U]; m_rxCall->LDULC[4U] = voice.additionalData[1U]; m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC4 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE5: { ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->srcId = GET_UINT24(voice.additionalData, 0U); // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[6U] = voice.additionalData[0U]; m_rxCall->LDULC[7U] = voice.additionalData[1U]; m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC5 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE6: { ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[9U] = voice.additionalData[0U]; m_rxCall->LDULC[10U] = voice.additionalData[1U]; m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC6 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE7: { ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[12U] = voice.additionalData[0U]; m_rxCall->LDULC[13U] = voice.additionalData[1U]; m_rxCall->LDULC[14U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC7 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE8: { ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[15U] = voice.additionalData[0U]; m_rxCall->LDULC[16U] = voice.additionalData[1U]; m_rxCall->LDULC[17U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC8 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE9: { ::memcpy(m_rxCall->netLDU1 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC9 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE11: { ::memcpy(m_rxCall->netLDU2 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; case DFSIFrameType::LDU2_VOICE12: { ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI, voice.additionalData, 3U); // copy LDU2 LC bytes into LDU LC buffer ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); m_rxCall->LDULC[0U] = voice.additionalData[0U]; m_rxCall->LDULC[1U] = voice.additionalData[1U]; m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC12 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE13: { ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[3U] = voice.additionalData[0U]; m_rxCall->LDULC[4U] = voice.additionalData[1U]; m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC13 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE14: { ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[6U] = voice.additionalData[0U]; m_rxCall->LDULC[7U] = voice.additionalData[1U]; m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC14 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE15: { ::memcpy(m_rxCall->netLDU2 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->algoId = voice.additionalData[0U]; m_rxCall->kId = GET_UINT16(voice.additionalData, 1U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[9U] = voice.additionalData[0U]; m_rxCall->LDULC[10U] = voice.additionalData[1U]; m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC15 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE16: { ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[12U] = voice.additionalData[0U]; m_rxCall->LDULC[13U] = voice.additionalData[1U]; m_rxCall->LDULC[14U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC16 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE17: { ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[15U] = voice.additionalData[0U]; m_rxCall->LDULC[16U] = voice.additionalData[1U]; m_rxCall->LDULC[17U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC17 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE18: { ::memcpy(m_rxCall->netLDU2 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { LogWarning(LOG_MODEM, "V.24/DFSI VC18 traffic missing metadata"); } } break; default: break; } // increment our voice frame counter m_rxCall->n++; } break; } // encode LDU1 if ready if (m_rxCall->n == 9U) { // decode RS (24,12,13) FEC // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now // we'll just log the error try { bool ret = m_rs.decode241213(m_rxCall->LDULC); if (!ret) { LogError(LOG_MODEM, "V.24/DFSI LDU1, failed to decode RS (24,12,13) FEC"); } } catch (...) { Utils::dump(2U, "Modem, V.24 LDU1 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } if (m_rxCall->errors > 0U) { LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", V.24, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); if (lc.isStandardMFId()) { lc.setSrcId(m_rxCall->srcId); lc.setDstId(m_rxCall->dstId); } else { uint8_t rsBuffer[P25_LDU_LC_FEC_LENGTH_BYTES]; ::memset(rsBuffer, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); rsBuffer[0U] = m_rxCall->LDULC[0U]; rsBuffer[1U] = m_rxCall->LDULC[1U]; rsBuffer[2U] = m_rxCall->LDULC[2U]; rsBuffer[3U] = m_rxCall->LDULC[3U]; rsBuffer[4U] = m_rxCall->LDULC[4U]; rsBuffer[5U] = m_rxCall->LDULC[5U]; rsBuffer[6U] = m_rxCall->LDULC[6U]; rsBuffer[7U] = m_rxCall->LDULC[7U]; rsBuffer[8U] = m_rxCall->LDULC[8U]; // combine bytes into ulong64_t (8 byte) value ulong64_t rsValue = 0U; rsValue = rsBuffer[1U]; rsValue = (rsValue << 8) + rsBuffer[2U]; rsValue = (rsValue << 8) + rsBuffer[3U]; rsValue = (rsValue << 8) + rsBuffer[4U]; rsValue = (rsValue << 8) + rsBuffer[5U]; rsValue = (rsValue << 8) + rsBuffer[6U]; rsValue = (rsValue << 8) + rsBuffer[7U]; rsValue = (rsValue << 8) + rsBuffer[8U]; lc.setRS(rsValue); } bool emergency = ((m_rxCall->serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag bool encryption = ((m_rxCall->serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag uint8_t priority = ((m_rxCall->serviceOptions & 0xFFU) & 0x07U); // Priority lc.setEmergency(emergency); lc.setEncrypted(encryption); lc.setPriority(priority); data::LowSpeedData lsd = data::LowSpeedData(); lsd.setLSD1(m_rxCall->lsd1); lsd.setLSD2(m_rxCall->lsd2); // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::LDU1); // generate LDU1 Data lc.encodeLDU1(buffer + 2U); // generate Low Speed Data lsd.process(buffer + 2U); // generate audio m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 10U, 0U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 26U, 1U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 55U, 2U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 80U, 3U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 105U, 4U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 130U, 5U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 155U, 6U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 204U, 8U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); } // encode LDU2 if ready if (m_rxCall->n == 18U) { // decode RS (24,16,9) FEC // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now // we'll just log the error try { bool ret = m_rs.decode24169(m_rxCall->LDULC); if (!ret) { LogError(LOG_MODEM, "V.24/DFSI LDU2, failed to decode RS (24,16,9) FEC"); } } catch (...) { Utils::dump(2U, "Modem, V.24 LDU2 RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } if (m_rxCall->errors > 0U) { LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", V.24, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); lc.setKId(m_rxCall->kId); data::LowSpeedData lsd = data::LowSpeedData(); lsd.setLSD1(m_rxCall->lsd1); lsd.setLSD2(m_rxCall->lsd2); // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::LDU2); // generate LDU2 data lc.encodeLDU2(buffer + 2U); // generate Low Speed Data lsd.process(buffer + 2U); // generate audio m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 10U, 0U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 26U, 1U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 55U, 2U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 80U, 3U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 105U, 4U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 130U, 5U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 155U, 6U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 204U, 8U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); m_rxCall->n = 0; } } /* Internal helper to convert from TIA-102 DFSI to TIA-102 air interface. */ void ModemV24::convertToAirTIA(const uint8_t *data, uint32_t length) { assert(data != nullptr); assert(length > 0U); uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); // get the DFSI data (skip the 0x00 padded byte at the start) DECLARE_UINT8_ARRAY(dfsiData, length - 1U); ::memcpy(dfsiData, data + 1U, length - 1U); if (m_debug) Utils::dump("ModemV24::converToAirTIA(), DFSI RX Data From UDP", dfsiData, length - 1U); ControlOctet ctrl = ControlOctet(); ctrl.decode(dfsiData); uint8_t blockCnt = ctrl.getBlockHeaderCnt(); if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "blockCnt = %u", blockCnt); // iterate through blocks uint8_t hdrOffs = 1U, dataOffs = blockCnt + 1U; for (uint8_t i = 0U; i < blockCnt; i++) { BlockHeader hdr = BlockHeader(); hdr.decode(dfsiData + hdrOffs); if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "block = %u, blockType = $%02X", i, hdr.getBlockType()); BlockType::E blockType = hdr.getBlockType(); switch (blockType) { case BlockType::START_OF_STREAM: { StartOfStream start = StartOfStream(); start.decode(dfsiData + dataOffs); uint16_t nac = ((start.getNID() & 0xFFFFU) >> 4) & 0xFFFU; if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Start of Stream, nac = $%03X, errs: %u", nac, start.getErrorCount()); // bryanb: maybe compare the NACs? dataOffs += StartOfStream::LENGTH; // ack start of stream ackStartOfStreamTIA(); } break; case BlockType::END_OF_STREAM: { dataOffs += 1U; // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::TDU); // add status bits P25Utils::setStatusBitsAllIdle(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_TDU_FRAME_LENGTH_BITS + 2U); } break; case BlockType::START_OF_STREAM_ACK: { if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Start of Stream ACK"); // do nothing with the start of stream ack } break; case BlockType::VOICE_HEADER_P1: { // copy to call data VHDR1 ::memset(m_rxCall->VHDR1, 0x00U, 18U); ::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U); if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirTIA(), DFSI RX, VHDR1", m_rxCall->VHDR1, 18U); dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker } break; case BlockType::VOICE_HEADER_P2: { // copy to call data VHDR2 ::memset(m_rxCall->VHDR2, 0x00U, 18U); ::memcpy(m_rxCall->VHDR2, dfsiData + dataOffs + 1U, 18U); if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirTIA(), DFSI RX, VHDR2", m_rxCall->VHDR1, 18U); dataOffs += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker // buffer for raw VHDR data uint8_t raw[DFSI_VHDR_RAW_LEN]; ::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN); ::memcpy(raw, m_rxCall->VHDR1, 18U); ::memcpy(raw + 18U, m_rxCall->VHDR2, 18U); assert(raw != nullptr); // buffer for decoded VHDR data uint8_t vhdr[P25_HDU_LENGTH_BYTES]; ::memset(vhdr, 0x00U, P25_HDU_LENGTH_BYTES); assert(vhdr != nullptr); uint32_t offset = 0U; for (uint32_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { Utils::hex2Bin(raw[i], vhdr, offset); } if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirTIA(), DFSI RX VHDR, VHDR Before FEC", vhdr, P25_HDU_LENGTH_BYTES); // try to decode the RS data try { bool ret = m_rs.decode362017(vhdr); if (!ret) { LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode RS (36,20,17) FEC"); } else { if (m_debug && m_trace) Utils::dump("ModemV24::convertToAirTIA(), DFSI RX VHDR, VHDR After FEC", vhdr, P25_HDU_LENGTH_BYTES); // late entry? if (!m_rxCallInProgress) { m_rxCallInProgress = true; m_rxCall->resetCallData(); if (m_debug) LogDebug(LOG_MODEM, "V24 RX VHDR late entry, resetting call data"); } uint32_t dstId = GET_UINT16(vhdr, 13U); if (m_rxCallInProgress && dstId == 0U) { LogWarning(LOG_MODEM, "TIA/DFSI traffic sent voice header with no dstId while call is in progress?, ignoring header TGID"); break; } ::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES); m_rxCall->mfId = vhdr[9U]; m_rxCall->algoId = vhdr[10U]; m_rxCall->kId = GET_UINT16(vhdr, 11U); m_rxCall->dstId = dstId; if (m_debug) { LogDebug(LOG_MODEM, "P25, VHDR algId = $%02X, kId = $%04X, dstId = $%04X", m_rxCall->algoId, m_rxCall->kId, m_rxCall->dstId); } // generate a HDU lc::LC lc = lc::LC(); lc.setDstId(m_rxCall->dstId); lc.setAlgId(m_rxCall->algoId); lc.setKId(m_rxCall->kId); lc.setMI(m_rxCall->MI); // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::HDU); // generate HDU lc.encodeHDU(buffer + 2U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U); } } catch (...) { LogError(LOG_MODEM, "TIA/DFSI RX traffic got exception while trying to decode RS data for VHDR"); } } break; case BlockType::FULL_RATE_VOICE: { FullRateVoice voice = FullRateVoice(); //m_superFrameCnt = voice.getSuperframeCnt(); voice.decode(dfsiData + dataOffs); if (m_debug) { LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "Full Rate Voice, frameType = $%02X, busy = %u, lostFrame = %u, muteFrame = %u, superFrameCnt = %u, errors = %u", voice.getFrameType(), voice.getBusy(), voice.getLostFrame(), voice.getMuteFrame(), voice.getSuperframeCnt(), voice.getTotalErrors()); Utils::dump(1U, "ModemV24::convertToAirTIA(), Full Rate Voice IMBE", voice.imbeData, RAW_IMBE_LENGTH_BYTES); } if (voice.getTotalErrors() > 0U) { if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirTIA()", "TIA/DFSI traffic has %u errors in frameType = $%02X", voice.getTotalErrors(), voice.getFrameType()); m_rxCall->errors += voice.getTotalErrors(); } dataOffs += voice.getLength(); DFSIFrameType::E frameType = voice.getFrameType(); switch (frameType) { case DFSIFrameType::LDU1_VOICE1: { ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU1 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; case DFSIFrameType::LDU1_VOICE2: { ::memcpy(m_rxCall->netLDU1 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; case DFSIFrameType::LDU1_VOICE3: { ::memcpy(m_rxCall->netLDU1 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->lco = voice.additionalData[0U]; m_rxCall->mfId = voice.additionalData[1U]; m_rxCall->serviceOptions = voice.additionalData[2U]; // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[0U] = voice.additionalData[0U]; m_rxCall->LDULC[1U] = voice.additionalData[1U]; m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC3 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE4: { ::memcpy(m_rxCall->netLDU1 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->dstId = GET_UINT24(voice.additionalData, 0U); // copy LDU1 LC bytes into LDU LC buffer ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); m_rxCall->LDULC[3U] = voice.additionalData[0U]; m_rxCall->LDULC[4U] = voice.additionalData[1U]; m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC4 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE5: { ::memcpy(m_rxCall->netLDU1 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->srcId = GET_UINT24(voice.additionalData, 0U); // copy LDU1 LC bytes into LDU LC buffer ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); m_rxCall->LDULC[6U] = voice.additionalData[0U]; m_rxCall->LDULC[7U] = voice.additionalData[1U]; m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC5 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE6: { ::memcpy(m_rxCall->netLDU1 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[9U] = voice.additionalData[0U]; m_rxCall->LDULC[10U] = voice.additionalData[1U]; m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC6 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE7: { ::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[12U] = voice.additionalData[0U]; m_rxCall->LDULC[13U] = voice.additionalData[1U]; m_rxCall->LDULC[14U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC7 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE8: { ::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU1 LC bytes into LDU LC buffer m_rxCall->LDULC[15U] = voice.additionalData[0U]; m_rxCall->LDULC[16U] = voice.additionalData[1U]; m_rxCall->LDULC[17U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC8 traffic missing metadata"); } } break; case DFSIFrameType::LDU1_VOICE9: { ::memcpy(m_rxCall->netLDU1 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC9 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE10: { ::memset(m_rxCall->LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); ::memcpy(m_rxCall->netLDU2 + 10U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; case DFSIFrameType::LDU2_VOICE11: { ::memcpy(m_rxCall->netLDU2 + 26U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); } break; case DFSIFrameType::LDU2_VOICE12: { ::memcpy(m_rxCall->netLDU2 + 55U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI, voice.additionalData, 3U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[0U] = voice.additionalData[0U]; m_rxCall->LDULC[1U] = voice.additionalData[1U]; m_rxCall->LDULC[2U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC12 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE13: { ::memcpy(m_rxCall->netLDU2 + 80U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 3U, voice.additionalData, 3U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[3U] = voice.additionalData[0U]; m_rxCall->LDULC[4U] = voice.additionalData[1U]; m_rxCall->LDULC[5U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC13 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE14: { ::memcpy(m_rxCall->netLDU2 + 105U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { ::memcpy(m_rxCall->MI + 6U, voice.additionalData, 3U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[6U] = voice.additionalData[0U]; m_rxCall->LDULC[7U] = voice.additionalData[1U]; m_rxCall->LDULC[8U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC14 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE15: { ::memcpy(m_rxCall->netLDU2 + 130U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->algoId = voice.additionalData[0U]; m_rxCall->kId = GET_UINT16(voice.additionalData, 1U); // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[9U] = voice.additionalData[0U]; m_rxCall->LDULC[10U] = voice.additionalData[1U]; m_rxCall->LDULC[11U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC15 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE16: { ::memcpy(m_rxCall->netLDU2 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[12U] = voice.additionalData[0U]; m_rxCall->LDULC[13U] = voice.additionalData[1U]; m_rxCall->LDULC[14U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC16 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE17: { ::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { // copy LDU2 LC bytes into LDU LC buffer m_rxCall->LDULC[15U] = voice.additionalData[0U]; m_rxCall->LDULC[16U] = voice.additionalData[1U]; m_rxCall->LDULC[17U] = voice.additionalData[2U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC17 traffic missing metadata"); } } break; case DFSIFrameType::LDU2_VOICE18: { ::memcpy(m_rxCall->netLDU2 + 204U, voice.imbeData, RAW_IMBE_LENGTH_BYTES); if (voice.additionalData != nullptr) { m_rxCall->lsd1 = voice.additionalData[0U]; m_rxCall->lsd2 = voice.additionalData[1U]; } else { LogWarning(LOG_MODEM, "TIA/DFSI VC18 traffic missing metadata"); } } break; default: break; } // increment our voice frame counter m_rxCall->n++; } break; default: break; } hdrOffs += BlockHeader::LENGTH; } m_rxLastFrameTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); // encode LDU1 if ready if (m_rxCall->n == 9U) { // decode RS (24,12,13) FEC // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now // we'll just log the error try { bool ret = m_rs.decode241213(m_rxCall->LDULC); if (!ret) { LogError(LOG_MODEM, "TIA/DFSI LDU1, failed to decode RS (24,12,13) FEC"); } } catch (...) { Utils::dump(2U, "Modem, TIA LDU1, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } if (m_rxCall->errors > 0U) { LogWarning(LOG_MODEM, P25_DFSI_LDU1_STR ", TIA, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); lc.setLCO(m_rxCall->lco); lc.setMFId(m_rxCall->mfId); if (lc.isStandardMFId()) { lc.setSrcId(m_rxCall->srcId); lc.setDstId(m_rxCall->dstId); } else { uint8_t rsBuffer[P25_LDU_LC_FEC_LENGTH_BYTES]; ::memset(rsBuffer, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); rsBuffer[0U] = m_rxCall->LDULC[0U]; rsBuffer[1U] = m_rxCall->LDULC[1U]; rsBuffer[2U] = m_rxCall->LDULC[2U]; rsBuffer[3U] = m_rxCall->LDULC[3U]; rsBuffer[4U] = m_rxCall->LDULC[4U]; rsBuffer[5U] = m_rxCall->LDULC[5U]; rsBuffer[6U] = m_rxCall->LDULC[6U]; rsBuffer[7U] = m_rxCall->LDULC[7U]; rsBuffer[8U] = m_rxCall->LDULC[8U]; // combine bytes into ulong64_t (8 byte) value ulong64_t rsValue = 0U; rsValue = rsBuffer[1U]; rsValue = (rsValue << 8) + rsBuffer[2U]; rsValue = (rsValue << 8) + rsBuffer[3U]; rsValue = (rsValue << 8) + rsBuffer[4U]; rsValue = (rsValue << 8) + rsBuffer[5U]; rsValue = (rsValue << 8) + rsBuffer[6U]; rsValue = (rsValue << 8) + rsBuffer[7U]; rsValue = (rsValue << 8) + rsBuffer[8U]; lc.setRS(rsValue); } bool emergency = ((m_rxCall->serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag bool encryption = ((m_rxCall->serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag uint8_t priority = ((m_rxCall->serviceOptions & 0xFFU) & 0x07U); // Priority lc.setEmergency(emergency); lc.setEncrypted(encryption); lc.setPriority(priority); data::LowSpeedData lsd = data::LowSpeedData(); lsd.setLSD1(m_rxCall->lsd1); lsd.setLSD2(m_rxCall->lsd2); // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::LDU1); // generate LDU1 Data lc.encodeLDU1(buffer + 2U); // generate Low Speed Data lsd.process(buffer + 2U); // generate audio m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 10U, 0U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 26U, 1U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 55U, 2U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 80U, 3U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 105U, 4U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 130U, 5U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 155U, 6U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU1 + 204U, 8U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); } // encode LDU2 if ready if (m_rxCall->n == 18U) { // decode RS (24,16,9) FEC // bryanb: for now this won't abort the frame if RS fails, but maybe it should in the future, for now // we'll just log the error try { bool ret = m_rs.decode24169(m_rxCall->LDULC); if (!ret) { LogError(LOG_MODEM, "TIA/DFSI LDU2, failed to decode RS (24,16,9) FEC"); } } catch (...) { Utils::dump(2U, "Modem, TIA LDU2, RS excepted with input data", m_rxCall->LDULC, P25_LDU_LC_FEC_LENGTH_BYTES); } if (m_rxCall->errors > 0U) { LogWarning(LOG_MODEM, P25_DFSI_LDU2_STR ", TIA, errs = %u/1233 (%.1f%%)", m_rxCall->errors, float(m_rxCall->errors) / 12.33F); m_rxCall->errors = 0U; } lc::LC lc = lc::LC(); lc.setMI(m_rxCall->MI); lc.setAlgId(m_rxCall->algoId); lc.setKId(m_rxCall->kId); data::LowSpeedData lsd = data::LowSpeedData(); lsd.setLSD1(m_rxCall->lsd1); lsd.setLSD2(m_rxCall->lsd2); // generate Sync Sync::addP25Sync(buffer + 2U); // generate NID m_nid->encode(buffer + 2U, DUID::LDU2); // generate LDU2 data lc.encodeLDU2(buffer + 2U); // generate Low Speed Data lsd.process(buffer + 2U); // generate audio m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 10U, 0U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 26U, 1U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 55U, 2U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 80U, 3U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 105U, 4U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 130U, 5U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 155U, 6U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 180U, 7U); m_audio.encode(buffer + 2U, m_rxCall->netLDU2 + 204U, 8U); // add status bits P25Utils::addStatusBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false); buffer[0U] = modem::TAG_DATA; buffer[1U] = 0x01U; storeConvertedRx(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); m_rxCall->n = 0; } } /* Helper to add a V.24 data frame to the P25 TX queue with the proper timestamp and formatting */ void ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType) { assert(data != nullptr); assert(len > 0U); if (m_debug) LogDebugEx(LOG_MODEM, "ModemV24::queueP25Frame()", "msgType = $%02X", msgType); if (m_trace) Utils::dump(1U, "ModemV24::queueP25Frame(), data", data, len); // get current time in ms uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); // timestamp for this message (in ms) uint64_t msgTime = 0U; // if this is our first message, timestamp is just now + the jitter buffer offset in ms if (m_lastP25Tx == 0U) { msgTime = now + m_jitter; // if the message type requests no jitter delay -- just set the message time to now if (msgType == STT_NON_IMBE_NO_JITTER) msgTime = now; } // if we had a message before this, calculate the new timestamp dynamically else { // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above if ((int64_t)(now - m_lastP25Tx) > m_jitter) { msgTime = now + m_jitter; } // otherwise, we time out messages as required by the message type else { if (msgType == STT_IMBE) { // IMBEs must go out at 20ms intervals msgTime = m_lastP25Tx + 20U; } else { // Otherwise we don't care, we use 5ms since that's the theoretical minimum time a 9600 baud message can take msgTime = m_lastP25Tx + 5U; } } } len += 4U; // convert 16-bit length to 2 bytes uint8_t length[2U]; if (len > 255U) length[0U] = (len >> 8U) & 0xFFU; else length[0U] = 0x00U; length[1U] = len & 0xFFU; m_txP25Queue.addData(length, 2U); // add the data tag uint8_t tag = TAG_DATA; m_txP25Queue.addData(&tag, 1U); // convert 64-bit timestamp to 8 bytes and add uint8_t tsBytes[8U]; assert(sizeof msgTime == 8U); ::memcpy(tsBytes, &msgTime, 8U); m_txP25Queue.addData(tsBytes, 8U); // add the DVM start byte, length byte, CMD byte, and padding 0 uint8_t header[4U]; header[0U] = DVM_SHORT_FRAME_START; header[1U] = len & 0xFFU; header[2U] = CMD_P25_DATA; header[3U] = 0x00U; m_txP25Queue.addData(header, 4U); // add the data m_txP25Queue.addData(data, len - 4U); // update the last message time m_lastP25Tx = msgTime; } /* Send a start of stream sequence (HDU, etc) to the connected serial V.24 device */ void ModemV24::startOfStreamV24(const p25::lc::LC& control) { m_txCallInProgress = true; MotStartOfStream start = MotStartOfStream(); start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); start.setArgument1(MotStreamPayload::VOICE); // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::startOfStreamV24(), StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); control.getMI(mi); uint8_t vhdr[DFSI_VHDR_LEN]; ::memset(vhdr, 0x00U, DFSI_VHDR_LEN); ::memcpy(vhdr, mi, MI_LENGTH_BYTES); vhdr[9U] = control.getMFId(); vhdr[10U] = control.getAlgId(); SET_UINT16(control.getKId(), vhdr, 11U); SET_UINT16(control.getDstId(), vhdr, 13U); // perform RS encoding m_rs.encode362017(vhdr); // convert the binary bytes to hex bytes uint8_t raw[DFSI_VHDR_RAW_LEN]; uint32_t offset = 0; for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { raw[i] = Utils::bin2Hex(vhdr, offset); } // prepare VHDR1 uint8_t vhdr1Buf[DFSI_MOT_VHDR_1_LEN]; ::memset(vhdr1Buf, 0x00U, DFSI_MOT_VHDR_1_LEN); start.encode(vhdr1Buf); ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 0U, raw, 8U); ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 9U, raw + 8U, 8U); ::memcpy(vhdr1Buf + DFSI_MOT_START_LEN + 18U, raw + 16U, 2U); vhdr1Buf[0U] = DFSIFrameType::MOT_VHDR_1; vhdr1Buf[20U + DFSI_MOT_START_LEN] = DFSI_BUSY_BITS_INBOUND; if (m_trace) Utils::dump(1U, "ModemV24::startOfStreamV24(), VoiceHeader1", vhdr1Buf, DFSI_MOT_VHDR_1_LEN); queueP25Frame(vhdr1Buf, DFSI_MOT_VHDR_1_LEN, STT_NON_IMBE); // prepare VHDR2 uint8_t vhdr2Buf[DFSI_MOT_VHDR_2_LEN]; ::memset(vhdr2Buf, 0x00U, DFSI_MOT_VHDR_2_LEN); ::memcpy(vhdr2Buf + 1U, raw + 18U, 8U); ::memcpy(vhdr2Buf + 10U, raw + 26U, 8U); ::memcpy(vhdr2Buf + 19U, raw + 34U, 2U); vhdr2Buf[0U] = DFSIFrameType::MOT_VHDR_2; vhdr2Buf[21U] = DFSI_BUSY_BITS_INBOUND; // send VHDR2 if (m_trace) Utils::dump(1U, "ModemV24::startOfStreamV24(), VoiceHeader2", vhdr2Buf, DFSI_MOT_VHDR_2_LEN); queueP25Frame(vhdr2Buf, DFSI_MOT_VHDR_2_LEN, STT_NON_IMBE); } /* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */ void ModemV24::endOfStreamV24() { MotStartOfStream end = MotStartOfStream(); end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); end.setParam1(DFSI_MOT_ICW_PARM_STOP); end.setArgument1(MotStreamPayload::VOICE); // create buffer and encode uint8_t endBuf[DFSI_MOT_START_LEN]; ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); if (m_trace) Utils::dump(1U, "ModemV24::endOfStreamV24(), StartOfStream", endBuf, DFSI_MOT_START_LEN); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE); m_txCallInProgress = false; } /* Helper to generate the NID value. */ uint16_t ModemV24::generateNID(DUID::E duid) { uint8_t nid[2U]; ::memset(nid, 0x00U, 2U); nid[0U] = (m_p25NAC >> 4) & 0xFFU; nid[1U] = (m_p25NAC << 4) & 0xF0U; nid[1U] |= duid; return GET_UINT16(nid, 0U); } /* Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device. */ void ModemV24::startOfStreamTIA(const p25::lc::LC& control) { m_txCallInProgress = true; m_superFrameCnt = 1U; p25::lc::LC lc = p25::lc::LC(control); uint16_t length = 0U; uint8_t buffer[P25_HDU_LENGTH_BYTES]; ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); // generate control octet ControlOctet ctrl = ControlOctet(); ctrl.setBlockHeaderCnt(1U); ctrl.encode(buffer); length += ControlOctet::LENGTH; // generate block header BlockHeader hdr = BlockHeader(); hdr.setBlockType(BlockType::START_OF_STREAM); hdr.encode(buffer + 1U); length += BlockHeader::LENGTH; // generate start of stream StartOfStream start = StartOfStream(); start.setNID(generateNID(DUID::HDU)); start.encode(buffer + 2U); length += StartOfStream::LENGTH; if (m_trace) Utils::dump(1U, "ModemV24::startOfStreamTIA(), StartOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); control.getMI(mi); uint8_t vhdr[P25_HDU_LENGTH_BYTES]; ::memset(vhdr, 0x00U, P25_HDU_LENGTH_BYTES); ::memcpy(vhdr, mi, MI_LENGTH_BYTES); vhdr[9U] = control.getMFId(); vhdr[10U] = control.getAlgId(); SET_UINT16(control.getKId(), vhdr, 11U); SET_UINT16(control.getDstId(), vhdr, 13U); // perform RS encoding m_rs.encode362017(vhdr); // convert the binary bytes to hex bytes uint8_t raw[DFSI_VHDR_RAW_LEN]; uint32_t offset = 0U; for (uint8_t i = 0; i < DFSI_VHDR_RAW_LEN; i++, offset += 6) { raw[i] = Utils::bin2Hex(vhdr, offset); } ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); length = 0U; // generate control octet ctrl.setBlockHeaderCnt(2U); ctrl.encode(buffer); length += ControlOctet::LENGTH; // generate block header 1 hdr.setBlockType(BlockType::VOICE_HEADER_P1); hdr.encode(buffer + 1U); length += BlockHeader::LENGTH; hdr.setBlockType(BlockType::START_OF_STREAM); hdr.encode(buffer + 2U); length += BlockHeader::LENGTH; // generate voice header 1 uint8_t hdu[P25_HDU_LENGTH_BYTES]; ::memset(hdu, 0x00U, P25_HDU_LENGTH_BYTES); lc.encodeHDU(hdu, true); // prepare VHDR1 buffer[3U] = DFSIFrameType::MOT_VHDR_1; ::memcpy(buffer + 4U, raw, 18U); length += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker start.encode(buffer + length); length += StartOfStream::LENGTH; if (m_trace) Utils::dump(1U, "ModemV24::startOfStreamTIA(), VoiceHeader1", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); length = 0U; // generate control octet ctrl.setBlockHeaderCnt(2U); ctrl.encode(buffer); length += ControlOctet::LENGTH; // generate block header 1 hdr.setBlockType(BlockType::VOICE_HEADER_P2); hdr.encode(buffer + 1U); length += BlockHeader::LENGTH; hdr.setBlockType(BlockType::START_OF_STREAM); hdr.encode(buffer + 2U); length += BlockHeader::LENGTH; // prepare VHDR2 buffer[3U] = DFSIFrameType::MOT_VHDR_2; ::memcpy(buffer + 4U, raw + 18U, 18U); length += DFSI_TIA_VHDR_LEN; // 18 Golay + Block Type Marker start.encode(buffer + length); length += StartOfStream::LENGTH; if (m_trace) Utils::dump(1U, "ModemV24::startOfStreamTIA(), VoiceHeader2", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); } /* Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device. */ void ModemV24::endOfStreamTIA() { m_superFrameCnt = 1U; uint16_t length = 0U; uint8_t buffer[2U]; ::memset(buffer, 0x00U, 2U); // generate control octet ControlOctet ctrl = ControlOctet(); ctrl.setBlockHeaderCnt(1U); ctrl.encode(buffer); length += ControlOctet::LENGTH; // generate block header BlockHeader hdr = BlockHeader(); hdr.setBlockType(BlockType::END_OF_STREAM); hdr.encode(buffer + 1U); length += BlockHeader::LENGTH; if (m_trace) Utils::dump(1U, "ModemV24::endOfStreamTIA(), EndOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE); m_txCallInProgress = false; } /* Send a start of stream ACK. */ void ModemV24::ackStartOfStreamTIA() { uint16_t length = 0U; uint8_t buffer[P25_HDU_LENGTH_BYTES]; ::memset(buffer, 0x00U, P25_HDU_LENGTH_BYTES); // generate control octet ControlOctet ctrl = ControlOctet(); ctrl.setBlockHeaderCnt(1U); ctrl.encode(buffer); length += ControlOctet::LENGTH; // generate block header BlockHeader hdr = BlockHeader(); hdr.setBlockType(BlockType::START_OF_STREAM_ACK); hdr.encode(buffer + 1U); length += BlockHeader::LENGTH; if (m_trace) Utils::dump(1U, "ModemV24::ackStartOfStreamTIA(), Ack StartOfStream", buffer, length); queueP25Frame(buffer, length, STT_NON_IMBE_NO_JITTER); } /* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) { assert(data != nullptr); assert(length > 0U); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); // decode the NID bool valid = m_nid->decode(data + 2U); if (!valid) return; DUID::E duid = m_nid->getDUID(); // handle individual DUIDs lc::LC lc = lc::LC(); data::LowSpeedData lsd = data::LowSpeedData(); switch (duid) { case DUID::HDU: { bool ret = lc.decodeHDU(data + 2U); if (!ret) { LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); } startOfStreamV24(lc); } break; case DUID::LDU1: { bool ret = lc.decodeLDU1(data + 2U, true); if (!ret) { LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); return; } lsd.process(data + 2U); // late entry? if (!m_txCallInProgress) { startOfStreamV24(lc); if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "V.24 TX VHDR late entry, resetting TX call data"); } // generate audio m_audio.decode(data + 2U, ldu + 10U, 0U); m_audio.decode(data + 2U, ldu + 26U, 1U); m_audio.decode(data + 2U, ldu + 55U, 2U); m_audio.decode(data + 2U, ldu + 80U, 3U); m_audio.decode(data + 2U, ldu + 105U, 4U); m_audio.decode(data + 2U, ldu + 130U, 5U); m_audio.decode(data + 2U, ldu + 155U, 6U); m_audio.decode(data + 2U, ldu + 180U, 7U); m_audio.decode(data + 2U, ldu + 204U, 8U); } break; case DUID::LDU2: { bool ret = lc.decodeLDU2(data + 2U); if (!ret) { LogWarning(LOG_MODEM, P25_LDU2_STR ", undecodable LC"); return; } lsd.process(data + 2U); // generate audio m_audio.decode(data + 2U, ldu + 10U, 0U); m_audio.decode(data + 2U, ldu + 26U, 1U); m_audio.decode(data + 2U, ldu + 55U, 2U); m_audio.decode(data + 2U, ldu + 80U, 3U); m_audio.decode(data + 2U, ldu + 105U, 4U); m_audio.decode(data + 2U, ldu + 130U, 5U); m_audio.decode(data + 2U, ldu + 155U, 6U); m_audio.decode(data + 2U, ldu + 180U, 7U); m_audio.decode(data + 2U, ldu + 204U, 8U); } break; case DUID::TDU: if (m_txCallInProgress) endOfStreamV24(); break; case DUID::TDULC: { if (m_txCallInProgress) endOfStreamV24(); lc::tdulc::LC_TDULC_RAW tdulc = lc::tdulc::LC_TDULC_RAW(); if (!tdulc.decode(data + 2U)) { LogWarning(LOG_MODEM, P25_TDULC_STR ", undecodable LC"); return; } MotStartOfStream start = MotStartOfStream(); start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); start.setArgument1(MotStreamPayload::TERM_LC); // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), TDULC MotStartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); MotTDULCFrame tf = MotTDULCFrame(); tf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); tf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); tf.startOfStream->setArgument1(MotStreamPayload::TERM_LC); delete[] tf.tdulcData; tf.tdulcData = new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]; ::memset(tf.tdulcData, 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); ::memcpy(tf.tdulcData, tdulc.getDecodedRaw(), P25_TDULC_PAYLOAD_LENGTH_BYTES); // create buffer and encode uint8_t tdulcBuf[DFSI_MOT_TDULC_LEN]; ::memset(tdulcBuf, 0x00U, DFSI_MOT_TDULC_LEN); tf.encode(tdulcBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), MotTDULCFrame", tdulcBuf, DFSI_MOT_TDULC_LEN); queueP25Frame(tdulcBuf, DFSI_MOT_TDULC_LEN, STT_NON_IMBE_NO_JITTER); MotStartOfStream end = MotStartOfStream(); end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); end.setParam1(DFSI_MOT_ICW_PARM_STOP); end.setArgument1(MotStreamPayload::TERM_LC); // create buffer and encode uint8_t endBuf[DFSI_MOT_START_LEN]; ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); } break; case DUID::PDU: { uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); uint32_t bits = P25Utils::decode(data + 2U, buffer, 0U, P25_PDU_FRAME_LENGTH_BITS); uint8_t rawPDU[P25_PDU_FRAME_LENGTH_BYTES]; ::memset(rawPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); uint32_t pduBitCnt = Utils::getBits(buffer, rawPDU, 0U, bits); uint32_t offset = P25_PREAMBLE_LENGTH_BITS + P25_PDU_FEC_LENGTH_BITS; ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); Utils::getBitRange(rawPDU, buffer, P25_PREAMBLE_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS); data::DataHeader dataHeader = data::DataHeader(); bool ret = dataHeader.decode(buffer); if (!ret) { LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); return; } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "PDU OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), dataHeader.getHeaderOffset(), dataHeader.getLLId()); } // make sure we don't get a PDU with more blocks then we support if (dataHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "PDU OSP, too many PDU blocks to process, %u > %u", dataHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); return; } uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); uint32_t bitLength = ((blocksToFollow + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; if (pduBitCnt >= bitLength) { // process all blocks in the data stream // if the primary header has a header offset ensure data if offset by that amount if (dataHeader.getHeaderOffset() > 0U) { offset += dataHeader.getHeaderOffset() * 8; } data::DataBlock* dataBlocks = new data::DataBlock[P25_MAX_PDU_BLOCKS]; // decode data blocks for (uint32_t i = 0U; i < blocksToFollow; i++) { ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); Utils::getBitRange(rawPDU, buffer, offset, P25_PDU_FEC_LENGTH_BITS); bool ret = dataBlocks[i].decode(buffer, dataHeader); if (ret) { // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks // set this block as the last block for full packet CRC if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) || (dataHeader.getFormat() == PDUFormatType::UNCONFIRMED)) { if ((i + 1U) == blocksToFollow) { dataBlocks[i].setLastBlock(true); } } if (m_debug) { ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "PDU OSP, block %u, fmt = $%02X, lastBlock = %u", (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlocks[i].getSerialNo() : i, dataBlocks[i].getFormat(), dataBlocks[i].getLastBlock()); } } offset += P25_PDU_FEC_LENGTH_BITS; } if (blocksToFollow <= 3U) { DECLARE_UINT8_ARRAY(pduBuf, ((blocksToFollow + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)) + 1U); uint32_t pduLen = (blocksToFollow + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES); pduBuf[0U] = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_SINGLE_CONF : DFSIFrameType::MOT_PDU_SINGLE_UNCONF; dataHeader.encode(pduBuf + 1U, true); for (uint32_t i = 0U; i < blocksToFollow; i++) { dataBlocks[i].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); } MotStartOfStream start = MotStartOfStream(); start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); start.setArgument1(MotStreamPayload::DATA); // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); MotStartOfStream end = MotStartOfStream(); end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); end.setParam1(DFSI_MOT_ICW_PARM_STOP); end.setArgument1(MotStreamPayload::DATA); // create buffer and encode uint8_t endBuf[DFSI_MOT_START_LEN]; ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); } else { uint32_t remainderBlocks = (blocksToFollow - 3U) % DFSI_PDU_BLOCK_CNT; uint32_t baseBlockCnt = (blocksToFollow - 3U) / DFSI_PDU_BLOCK_CNT; uint32_t currentBlock = 0U; DECLARE_UINT8_ARRAY(pduBuf, ((DFSI_PDU_BLOCK_CNT + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)) + 1U); uint32_t pduLen = ((DFSI_PDU_BLOCK_CNT + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)) + 1U; MotStartOfStream start = MotStartOfStream(); start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); start.setArgument1(MotStreamPayload::DATA); // assemble the first frame pduBuf[0U] = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_HEADER : DFSIFrameType::MOT_PDU_UNCONF_HEADER; dataHeader.encode(pduBuf + 1U, true); for (uint32_t i = 0U; i < DFSI_PDU_BLOCK_CNT - 1U; i++) { dataBlocks[currentBlock].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); currentBlock++; } // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); // iterate through the count of full 4 block buffers and send uint8_t currentOpcode = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_BLOCK_1 : DFSIFrameType::MOT_PDU_UNCONF_BLOCK_1; for (uint32_t i = 1U; i < baseBlockCnt; i++) { // reset buffer and set data ::memset(pduBuf, 0x00U, pduLen); pduBuf[0U] = currentOpcode; for (uint32_t i = 0U; i < DFSI_PDU_BLOCK_CNT - 1U; i++) { dataBlocks[currentBlock].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); currentBlock++; } currentOpcode++; if (currentOpcode > ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_BLOCK_4 : DFSIFrameType::MOT_PDU_UNCONF_BLOCK_4)) currentOpcode = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_BLOCK_1 : DFSIFrameType::MOT_PDU_UNCONF_BLOCK_1; // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); } // do we have any remaining blocks? if (remainderBlocks > 0) { // reset buffer and set data ::memset(pduBuf, 0x00U, pduLen); pduBuf[0U] = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_END : DFSIFrameType::MOT_PDU_UNCONF_END; pduLen = 0U; for (uint32_t i = 0U; i < remainderBlocks; i++) { dataBlocks[currentBlock].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); pduLen += 1U + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; currentBlock++; } // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); MotStartOfStream end = MotStartOfStream(); end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); end.setParam1(DFSI_MOT_ICW_PARM_STOP); end.setArgument1(MotStreamPayload::DATA); // create buffer and encode uint8_t endBuf[DFSI_MOT_START_LEN]; ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); } } delete[] dataBlocks; } } break; case DUID::TSDU: { lc::tsbk::OSP_TSBK_RAW tsbk = lc::tsbk::OSP_TSBK_RAW(); if (!tsbk.decode(data + 2U)) { LogWarning(LOG_MODEM, P25_TSDU_STR ", undecodable LC"); return; } MotStartOfStream start = MotStartOfStream(); start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); start.setArgument1(MotStreamPayload::TSBK); // create buffer for bytes and encode uint8_t startBuf[DFSI_MOT_START_LEN]; ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); start.encode(startBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), TSBK StartOfStream", startBuf, DFSI_MOT_START_LEN); queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); MotTSBKFrame tf = MotTSBKFrame(); tf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); tf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); tf.startOfStream->setArgument1(MotStreamPayload::TSBK); delete[] tf.tsbkData; tf.tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES]; ::memset(tf.tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES); ::memcpy(tf.tsbkData, tsbk.getDecodedRaw(), P25_TSBK_LENGTH_BYTES); // create buffer and encode uint8_t tsbkBuf[DFSI_MOT_TSBK_LEN]; ::memset(tsbkBuf, 0x00U, DFSI_MOT_TSBK_LEN); tf.encode(tsbkBuf); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirV24(), MotTSBKFrame", tsbkBuf, DFSI_MOT_TSBK_LEN); queueP25Frame(tsbkBuf, DFSI_MOT_TSBK_LEN, STT_NON_IMBE_NO_JITTER); MotStartOfStream end = MotStartOfStream(); end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); end.setParam1(DFSI_MOT_ICW_PARM_STOP); end.setArgument1(MotStreamPayload::TSBK); // create buffer and encode uint8_t endBuf[DFSI_MOT_START_LEN]; ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); end.encode(endBuf); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); } break; default: break; } if (duid == DUID::LDU1 || duid == DUID::LDU2) { uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES]; ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); if (duid == DUID::LDU1) { rs[0U] = lc.getLCO(); // LCO // split ulong64_t (8 byte) value into bytes rs[1U] = (uint8_t)((lc.getRS() >> 56) & 0xFFU); rs[2U] = (uint8_t)((lc.getRS() >> 48) & 0xFFU); rs[3U] = (uint8_t)((lc.getRS() >> 40) & 0xFFU); rs[4U] = (uint8_t)((lc.getRS() >> 32) & 0xFFU); rs[5U] = (uint8_t)((lc.getRS() >> 24) & 0xFFU); rs[6U] = (uint8_t)((lc.getRS() >> 16) & 0xFFU); rs[7U] = (uint8_t)((lc.getRS() >> 8) & 0xFFU); rs[8U] = (uint8_t)((lc.getRS() >> 0) & 0xFFU); // encode RS (24,12,13) FEC m_rs.encode241213(rs); } else { // generate MI data uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); lc.getMI(mi); for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) rs[i] = mi[i]; // Message Indicator rs[9U] = lc.getAlgId(); // Algorithm ID rs[10U] = (lc.getKId() >> 8) & 0xFFU; // Key ID rs[11U] = (lc.getKId() >> 0) & 0xFFU; // ... // encode RS (24,16,9) FEC m_rs.encode24169(rs); } for (int n = 0; n < 9; n++) { uint8_t* buffer = nullptr; uint16_t bufferSize = 0; MotFullRateVoice voice = MotFullRateVoice(); voice.setBusy(DFSI_BUSY_BITS_INBOUND); switch (n) { case 0: // VOICE1/10 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); MotStartVoiceFrame svf = MotStartVoiceFrame(); svf.startOfStream->setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); svf.startOfStream->setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); svf.startOfStream->setArgument1(MotStreamPayload::VOICE); svf.fullRateVoice->setFrameType(voice.getFrameType()); ::memcpy(svf.fullRateVoice->imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); buffer = new uint8_t[MotStartVoiceFrame::LENGTH]; ::memset(buffer, 0x00U, MotStartVoiceFrame::LENGTH); svf.encode(buffer); bufferSize = MotStartVoiceFrame::LENGTH; } break; case 1: // VOICE2/11 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); } break; case 2: // VOICE3/12 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE3 : DFSIFrameType::LDU2_VOICE12); ::memcpy(voice.imbeData, ldu + 55U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); // copy additional data voice.additionalData[0U] = rs[0U]; voice.additionalData[1U] = rs[1U]; voice.additionalData[2U] = rs[2U]; } break; case 3: // VOICE4/13 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE4 : DFSIFrameType::LDU2_VOICE13); ::memcpy(voice.imbeData, ldu + 80U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); // copy additional data voice.additionalData[0U] = rs[3U]; voice.additionalData[1U] = rs[4U]; voice.additionalData[2U] = rs[5U]; } break; case 4: // VOICE5/14 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE5 : DFSIFrameType::LDU2_VOICE14); ::memcpy(voice.imbeData, ldu + 105U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[6U]; voice.additionalData[1U] = rs[7U]; voice.additionalData[2U] = rs[8U]; } break; case 5: // VOICE6/15 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE6 : DFSIFrameType::LDU2_VOICE15); ::memcpy(voice.imbeData, ldu + 130U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[9U]; voice.additionalData[1U] = rs[10U]; voice.additionalData[2U] = rs[11U]; } break; case 6: // VOICE7/16 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE7 : DFSIFrameType::LDU2_VOICE16); ::memcpy(voice.imbeData, ldu + 155U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[12U]; voice.additionalData[1U] = rs[13U]; voice.additionalData[2U] = rs[14U]; } break; case 7: // VOICE8/17 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE8 : DFSIFrameType::LDU2_VOICE17); ::memcpy(voice.imbeData, ldu + 180U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[15U]; voice.additionalData[1U] = rs[16U]; voice.additionalData[2U] = rs[17U]; } break; case 8: // VOICE9/18 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE9 : DFSIFrameType::LDU2_VOICE18); ::memcpy(voice.imbeData, ldu + 204U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = lsd.getLSD1(); voice.additionalData[1U] = lsd.getLSD2(); } break; } // For n=0 (VHDR1/10) case we create the buffer in the switch, for all other frame types we do that here if (n != 0) { buffer = new uint8_t[voice.size()]; ::memset(buffer, 0x00U, voice.size()); voice.encode(buffer); bufferSize = voice.size(); } if (buffer != nullptr) { if (m_trace) { Utils::dump("ModemV24::convertFromAirV24(), Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); delete[] buffer; } } } } /* Internal helper to convert from TIA-102 air interface to TIA-102 DFSI. */ void ModemV24::convertFromAirTIA(uint8_t* data, uint32_t length) { assert(data != nullptr); assert(length > 0U); if (m_trace) Utils::dump(1U, "ModemV24::convertFromAirTIA(), data", data, length); uint8_t ldu[9U * 25U]; ::memset(ldu, 0x00U, 9 * 25U); // decode the NID bool valid = m_nid->decode(data + 2U); if (!valid) return; DUID::E duid = m_nid->getDUID(); // handle individual DUIDs lc::LC lc = lc::LC(); data::LowSpeedData lsd = data::LowSpeedData(); switch (duid) { case DUID::HDU: { bool ret = lc.decodeHDU(data + 2U); if (!ret) { LogWarning(LOG_MODEM, P25_HDU_STR ", undecodable LC"); } startOfStreamTIA(lc); } break; case DUID::LDU1: { bool ret = lc.decodeLDU1(data + 2U, true); if (!ret) { LogWarning(LOG_MODEM, P25_LDU1_STR ", undecodable LC"); return; } lsd.process(data + 2U); // late entry? if (!m_txCallInProgress) { startOfStreamTIA(lc); if (m_debug) ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirTIA()", "DFSI TX VHDR late entry, resetting TX call data"); } // generate audio m_audio.decode(data + 2U, ldu + 10U, 0U); m_audio.decode(data + 2U, ldu + 26U, 1U); m_audio.decode(data + 2U, ldu + 55U, 2U); m_audio.decode(data + 2U, ldu + 80U, 3U); m_audio.decode(data + 2U, ldu + 105U, 4U); m_audio.decode(data + 2U, ldu + 130U, 5U); m_audio.decode(data + 2U, ldu + 155U, 6U); m_audio.decode(data + 2U, ldu + 180U, 7U); m_audio.decode(data + 2U, ldu + 204U, 8U); } break; case DUID::LDU2: { bool ret = lc.decodeLDU2(data + 2U); if (!ret) { LogWarning(LOG_MODEM, P25_LDU2_STR ", undecodable LC"); return; } lsd.process(data + 2U); // generate audio m_audio.decode(data + 2U, ldu + 10U, 0U); m_audio.decode(data + 2U, ldu + 26U, 1U); m_audio.decode(data + 2U, ldu + 55U, 2U); m_audio.decode(data + 2U, ldu + 80U, 3U); m_audio.decode(data + 2U, ldu + 105U, 4U); m_audio.decode(data + 2U, ldu + 130U, 5U); m_audio.decode(data + 2U, ldu + 155U, 6U); m_audio.decode(data + 2U, ldu + 180U, 7U); m_audio.decode(data + 2U, ldu + 204U, 8U); } break; case DUID::TDU: case DUID::TDULC: if (m_txCallInProgress) endOfStreamTIA(); break; case DUID::PDU: break; case DUID::TSDU: break; default: break; } if (duid == DUID::LDU1 || duid == DUID::LDU2) { uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES]; ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); if (duid == DUID::LDU1) { rs[0U] = lc.getLCO(); // LCO // split ulong64_t (8 byte) value into bytes rs[1U] = (uint8_t)((lc.getRS() >> 56) & 0xFFU); rs[2U] = (uint8_t)((lc.getRS() >> 48) & 0xFFU); rs[3U] = (uint8_t)((lc.getRS() >> 40) & 0xFFU); rs[4U] = (uint8_t)((lc.getRS() >> 32) & 0xFFU); rs[5U] = (uint8_t)((lc.getRS() >> 24) & 0xFFU); rs[6U] = (uint8_t)((lc.getRS() >> 16) & 0xFFU); rs[7U] = (uint8_t)((lc.getRS() >> 8) & 0xFFU); rs[8U] = (uint8_t)((lc.getRS() >> 0) & 0xFFU); // encode RS (24,12,13) FEC m_rs.encode241213(rs); } else { // generate MI data uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); lc.getMI(mi); for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) rs[i] = mi[i]; // Message Indicator rs[9U] = lc.getAlgId(); // Algorithm ID rs[10U] = (lc.getKId() >> 8) & 0xFFU; // Key ID rs[11U] = (lc.getKId() >> 0) & 0xFFU; // ... // encode RS (24,16,9) FEC m_rs.encode24169(rs); } for (int n = 0; n < 9; n++) { uint8_t* buffer = nullptr; uint16_t bufferSize = 0; FullRateVoice voice = FullRateVoice(); voice.setBusy(DFSI_BUSY_BITS_BUSY); voice.setSuperframeCnt(m_superFrameCnt); switch (n) { case 0: // VOICE1/10 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10); ::memcpy(voice.imbeData, ldu + 10U, RAW_IMBE_LENGTH_BYTES); } break; case 1: // VOICE2/11 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE2 : DFSIFrameType::LDU2_VOICE11); ::memcpy(voice.imbeData, ldu + 26U, RAW_IMBE_LENGTH_BYTES); } break; case 2: // VOICE3/12 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE3 : DFSIFrameType::LDU2_VOICE12); ::memcpy(voice.imbeData, ldu + 55U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); // copy additional data voice.additionalData[0U] = rs[0U]; voice.additionalData[1U] = rs[1U]; voice.additionalData[2U] = rs[2U]; } break; case 3: // VOICE4/13 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE4 : DFSIFrameType::LDU2_VOICE13); ::memcpy(voice.imbeData, ldu + 80U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); // copy additional data voice.additionalData[0U] = rs[3U]; voice.additionalData[1U] = rs[4U]; voice.additionalData[2U] = rs[5U]; } break; case 4: // VOICE5/14 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE5 : DFSIFrameType::LDU2_VOICE14); ::memcpy(voice.imbeData, ldu + 105U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[6U]; voice.additionalData[1U] = rs[7U]; voice.additionalData[2U] = rs[8U]; } break; case 5: // VOICE6/15 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE6 : DFSIFrameType::LDU2_VOICE15); ::memcpy(voice.imbeData, ldu + 130U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[9U]; voice.additionalData[1U] = rs[10U]; voice.additionalData[2U] = rs[11U]; } break; case 6: // VOICE7/16 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE7 : DFSIFrameType::LDU2_VOICE16); ::memcpy(voice.imbeData, ldu + 155U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[12U]; voice.additionalData[1U] = rs[13U]; voice.additionalData[2U] = rs[14U]; } break; case 7: // VOICE8/17 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE8 : DFSIFrameType::LDU2_VOICE17); ::memcpy(voice.imbeData, ldu + 180U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = rs[15U]; voice.additionalData[1U] = rs[16U]; voice.additionalData[2U] = rs[17U]; } break; case 8: // VOICE9/18 { voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE9 : DFSIFrameType::LDU2_VOICE18); ::memcpy(voice.imbeData, ldu + 204U, RAW_IMBE_LENGTH_BYTES); voice.additionalData = new uint8_t[voice.ADDITIONAL_LENGTH]; ::memset(voice.additionalData, 0x00U, voice.ADDITIONAL_LENGTH); voice.additionalData[0U] = lsd.getLSD1(); voice.additionalData[1U] = lsd.getLSD2(); } break; } buffer = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES]; ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); // generate control octet ControlOctet ctrl = ControlOctet(); ctrl.setBlockHeaderCnt(2U); ctrl.encode(buffer); bufferSize += ControlOctet::LENGTH; // generate block header BlockHeader hdr = BlockHeader(); hdr.setBlockType(BlockType::FULL_RATE_VOICE); hdr.encode(buffer + 1U); bufferSize += BlockHeader::LENGTH; hdr.setBlockType(BlockType::START_OF_STREAM); hdr.encode(buffer + 2U); bufferSize += BlockHeader::LENGTH; // encode voice frame voice.encode(buffer + bufferSize); bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type // generate start of stream and encode StartOfStream start = StartOfStream(); start.setNID(generateNID(duid)); start.encode(buffer + bufferSize); bufferSize += StartOfStream::LENGTH; if (buffer != nullptr) { if (m_trace) { Utils::dump("ModemV24::convertFromAirTIA(), Encoded V.24 Voice Frame Data", buffer, bufferSize); } queueP25Frame(buffer, bufferSize, STT_IMBE); delete[] buffer; } } // bryanb: this is a naive way of incrementing the superframe counter, we basically just increment it after // processing and LDU2 if (duid == DUID::LDU2) { if (m_superFrameCnt == 3U) m_superFrameCnt = 0U; else m_superFrameCnt++; } } }