You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dvmhost/src/host/modem/ModemV24.cpp

2521 lines
90 KiB

// 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-2024 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/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 <cassert>
// ---------------------------------------------------------------------------
// 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, bool diu, uint16_t jitter, bool dumpModemStatus, bool trace, bool debug) :
Modem(port, duplex, false, false, false, false, false, 80U, 7U, 8U, 1U, p25QueueSize, 1U,
false, false, dumpModemStatus, trace, debug),
m_rtrt(rtrt),
m_diu(diu),
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()
{
LogMessage(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)
LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / TIA-102]");
else
LogMessage(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::milliseconds>(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<std::mutex> 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
convertToAir(m_buffer + (cmdOffset + 1U), m_length - (cmdOffset + 1U));
}
}
break;
case CMD_P25_LOST:
{
if (m_p25Enabled) {
std::lock_guard<std::mutex> 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) {
LogDebug(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);
LogDebug(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("Buffer dump", 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::milliseconds>(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()
{
LogDebug(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) {
UInt8Array __buffer = std::make_unique<uint8_t[]>(length);
uint8_t* buffer = __buffer.get();
::memset(buffer, 0x00U, length);
::memcpy(buffer, data + 2U, length);
if (m_useTIAFormat)
convertFromAirTIA(buffer, length);
else
convertFromAir(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::milliseconds>(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
UInt8Array __buffer = std::make_unique<uint8_t[]>(len);
uint8_t* buffer = __buffer.get();
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("Storing converted RX data", buffer, length);
m_rxP25Queue.addData(buffer, length);
}
/* 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::convertToAir(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)
UInt8Array __dfsiData = std::make_unique<uint8_t[]>(length - 1U);
uint8_t* dfsiData = __dfsiData.get();
::memset(dfsiData, 0x00U, length - 1U);
::memcpy(dfsiData, data + 1U, length - 1U);
if (m_debug)
Utils::dump("V24 RX data from board", dfsiData, length - 1U);
DFSIFrameType::E frameType = (DFSIFrameType::E)dfsiData[0U];
m_rxLastFrameTime = std::chrono::duration_cast<std::chrono::milliseconds>(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.getStartStop() == StartStopFlag::START) {
m_rxCall->resetCallData();
m_rxCallInProgress = true;
if (m_debug) {
LogDebug(LOG_MODEM, "V24 RX, ICW START, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]);
}
} else {
if (m_rxCallInProgress) {
m_rxCall->resetCallData();
m_rxCallInProgress = false;
if (m_debug) {
LogDebug(LOG_MODEM, "V24 RX, ICW STOP, RT = $%02X, Type = $%02X", dfsiData[2U], dfsiData[4U]);
}
// generate a TDU
create_TDU(buffer);
storeConvertedRx(buffer, P25_TDU_FRAME_LENGTH_BYTES + 2U);
}
}
}
break;
case DFSIFrameType::MOT_VHDR_1:
{
MotVoiceHeader1 vhdr1 = MotVoiceHeader1(dfsiData);
// copy to call data VHDR1
::memset(m_rxCall->VHDR1, 0x00U, MotVoiceHeader1::HCW_LENGTH);
::memcpy(m_rxCall->VHDR1, vhdr1.header, MotVoiceHeader1::HCW_LENGTH);
}
break;
case DFSIFrameType::MOT_VHDR_2:
{
MotVoiceHeader2 vhdr2 = MotVoiceHeader2(dfsiData);
// copy to call data VHDR2
::memset(m_rxCall->VHDR2, 0x00U, MotVoiceHeader2::HCW_LENGTH);
::memcpy(m_rxCall->VHDR2, vhdr2.header, MotVoiceHeader2::HCW_LENGTH);
// buffer for raw VHDR data
uint8_t raw[DFSI_VHDR_RAW_LEN];
::memset(raw, 0x00U, DFSI_VHDR_RAW_LEN);
::memcpy(raw, 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);
// 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);
// 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 {
// 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");
}
::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES);
m_rxCall->mfId = vhdr[9U];
m_rxCall->algoId = vhdr[10U];
m_rxCall->kId = __GET_UINT16B(vhdr, 11U);
m_rxCall->dstId = __GET_UINT16B(vhdr, 13U);
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);
::memcpy(m_rxCall->netLDU1 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES);
m_rxCall->n++;
}
break;
case DFSIFrameType::LDU2_VOICE10:
{
MotStartVoiceFrame svf = MotStartVoiceFrame(dfsiData);
::memcpy(m_rxCall->netLDU2 + 10U, svf.fullRateVoice->imbeData, RAW_IMBE_LENGTH_BYTES);
m_rxCall->n++;
}
break;
case DFSIFrameType::PDU:
{
// bryanb: this is gonna be a complete clusterfuck...
MotPDUFrame pf = MotPDUFrame(dfsiData);
data::DataHeader dataHeader = data::DataHeader();
if (!dataHeader.decode(pf.pduHeaderData, true)) {
LogError(LOG_MODEM, "V.24/DFSI traffic failed to decode PDU FEC");
} else {
uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS;
uint32_t offset = P25_PREAMBLE_LENGTH_BITS;
UInt8Array __data = std::make_unique<uint8_t[]>((bitLength / 8U) + 1U);
uint8_t* data = __data.get();
::memset(data, 0x00U, bitLength / 8U);
uint8_t block[P25_PDU_FEC_LENGTH_BYTES];
::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
uint32_t blocksToFollow = dataHeader.getBlocksToFollow();
if (blocksToFollow > 0U) {
uint32_t dataOffset = MotPDUFrame::LENGTH + 1U;
// 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(dfsiData + dataOffset);
::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
dataBlock.encode(block);
Utils::setBitRange(block, data, 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) + 1U;
}
}
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES + 2U];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U);
// add the data
uint32_t newBitLength = P25Utils::encodeByLength(data, buffer + 2U, bitLength);
uint32_t newByteLength = newBitLength / 8U;
if ((newBitLength % 8U) > 0U)
newByteLength++;
// generate Sync
Sync::addP25Sync(buffer + 2U);
// generate NID
m_nid->encode(buffer + 2U, DUID::PDU);
// add status bits
P25Utils::addStatusBits(buffer + 2U, newBitLength, false, false);
P25Utils::addIdleStatusBits(buffer + 2U, newBitLength);
P25Utils::setStatusBitsStartIdle(buffer + 2U);
storeConvertedRx(buffer, P25_PDU_FRAME_LENGTH_BYTES + 2U);
}
}
break;
case DFSIFrameType::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);
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];
} 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_UINT16(voice.additionalData, 0U);
} 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_UINT16(voice.additionalData, 0U);
} 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);
}
break;
case DFSIFrameType::LDU1_VOICE7:
{
::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES);
}
break;
case DFSIFrameType::LDU1_VOICE8:
{
::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES);
}
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);
} 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);
} 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);
} 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_UINT16B(voice.additionalData, 1U);
} 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);
}
break;
case DFSIFrameType::LDU2_VOICE17:
{
::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES);
}
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) {
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->lco;
rsBuffer[1U] = m_rxCall->mfId;
rsBuffer[2U] = m_rxCall->serviceOptions;
rsBuffer[3U] = (m_rxCall->dstId >> 16) & 0xFFU;
rsBuffer[4U] = (m_rxCall->dstId >> 8) & 0xFFU;
rsBuffer[5U] = (m_rxCall->dstId >> 0) & 0xFFU;
rsBuffer[6U] = (m_rxCall->srcId >> 16) & 0xFFU;
rsBuffer[7U] = (m_rxCall->srcId >> 8) & 0xFFU;
rsBuffer[8U] = (m_rxCall->srcId >> 0) & 0xFFU;
// 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) {
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)
UInt8Array __dfsiData = std::make_unique<uint8_t[]>(length - 1U);
uint8_t* dfsiData = __dfsiData.get();
::memset(dfsiData, 0x00U, length - 1U);
::memcpy(dfsiData, data + 1U, length - 1U);
if (m_debug)
Utils::dump("DFSI RX data from board", dfsiData, length - 1U);
ControlOctet ctrl = ControlOctet();
ctrl.decode(dfsiData);
uint8_t blockCnt = ctrl.getBlockHeaderCnt();
// 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);
BlockType::E blockType = hdr.getBlockType();
switch (blockType) {
case BlockType::START_OF_STREAM:
{
StartOfStream start = StartOfStream();
start.decode(dfsiData + dataOffs);
uint16_t nac = start.getNID() & 0xFFFU;
// bryanb: maybe compare the NACs?
dataOffs += StartOfStream::LENGTH;
}
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::VOICE_HEADER_P1:
{
// copy to call data VHDR1
::memset(m_rxCall->VHDR1, 0x00U, 18U);
::memcpy(m_rxCall->VHDR1, dfsiData + dataOffs + 1U, 18U);
dataOffs += 19U; // 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);
dataOffs += 19U; // 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);
}
// 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 {
// 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");
}
::memcpy(m_rxCall->MI, vhdr, MI_LENGTH_BYTES);
m_rxCall->mfId = vhdr[9U];
m_rxCall->algoId = vhdr[10U];
m_rxCall->kId = __GET_UINT16B(vhdr, 11U);
m_rxCall->dstId = __GET_UINT16B(vhdr, 13U);
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;
case BlockType::FULL_RATE_VOICE:
{
FullRateVoice voice = FullRateVoice();
//m_superFrameCnt = voice.getSuperframeCnt();
voice.decode(dfsiData + dataOffs);
DFSIFrameType::E frameType = voice.getFrameType();
switch (frameType) {
case DFSIFrameType::LDU1_VOICE1:
{
::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];
} 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_UINT16(voice.additionalData, 0U);
} 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_UINT16(voice.additionalData, 0U);
} 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);
}
break;
case DFSIFrameType::LDU1_VOICE7:
{
::memcpy(m_rxCall->netLDU1 + 155U, voice.imbeData, RAW_IMBE_LENGTH_BYTES);
}
break;
case DFSIFrameType::LDU1_VOICE8:
{
::memcpy(m_rxCall->netLDU1 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES);
}
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_VOICE10:
{
::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);
} 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);
} 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);
} 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_UINT16B(voice.additionalData, 1U);
} 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);
}
break;
case DFSIFrameType::LDU2_VOICE17:
{
::memcpy(m_rxCall->netLDU2 + 180U, voice.imbeData, RAW_IMBE_LENGTH_BYTES);
}
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;
default:
break;
}
hdrOffs += BlockHeader::LENGTH;
}
m_rxLastFrameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// encode LDU1 if ready
if (m_rxCall->n == 9U) {
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->lco;
rsBuffer[1U] = m_rxCall->mfId;
rsBuffer[2U] = m_rxCall->serviceOptions;
rsBuffer[3U] = (m_rxCall->dstId >> 16) & 0xFFU;
rsBuffer[4U] = (m_rxCall->dstId >> 8) & 0xFFU;
rsBuffer[5U] = (m_rxCall->dstId >> 0) & 0xFFU;
rsBuffer[6U] = (m_rxCall->srcId >> 16) & 0xFFU;
rsBuffer[7U] = (m_rxCall->srcId >> 8) & 0xFFU;
rsBuffer[8U] = (m_rxCall->srcId >> 0) & 0xFFU;
// 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) {
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)
LogDebug(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::milliseconds>(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::startOfStream(const p25::lc::LC& control)
{
m_txCallInProgress = true;
MotStartOfStream start = MotStartOfStream();
start.setStartStop(StartStopFlag::START);
start.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED);
// create buffer for bytes and encode
uint8_t startBuf[start.LENGTH];
::memset(startBuf, 0x00U, start.LENGTH);
start.encode(startBuf);
if (m_trace)
Utils::dump(1U, "ModemV24::startOfStream() MotStartOfStream", startBuf, MotStartOfStream::LENGTH);
queueP25Frame(startBuf, MotStartOfStream::LENGTH, 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_UINT16B(control.getKId(), vhdr, 11U);
__SET_UINT16B(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
MotVoiceHeader1 vhdr1 = MotVoiceHeader1();
vhdr1.startOfStream->setStartStop(StartStopFlag::START);
vhdr1.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED);
vhdr1.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR);
::memcpy(vhdr1.header, raw, 8U);
::memcpy(vhdr1.header + 9U, raw + 8U, 8U);
::memcpy(vhdr1.header + 18U, raw + 16U, 2U);
// encode VHDR1 and send
uint8_t vhdr1Buf[vhdr1.LENGTH];
::memset(vhdr1Buf, 0x00U, vhdr1.LENGTH);
vhdr1.encode(vhdr1Buf);
if (m_trace)
Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader1", vhdr1Buf, MotVoiceHeader1::LENGTH);
queueP25Frame(vhdr1Buf, MotVoiceHeader1::LENGTH, STT_NON_IMBE);
// prepare VHDR2
MotVoiceHeader2 vhdr2 = MotVoiceHeader2();
::memcpy(vhdr2.header, raw + 18U, 8U);
::memcpy(vhdr2.header + 9U, raw + 26U, 8U);
::memcpy(vhdr2.header + 18U, raw + 34U, 2U);
// encode VHDR2 and send
uint8_t vhdr2Buf[vhdr2.LENGTH];
::memset(vhdr2Buf, 0x00U, vhdr2.LENGTH);
vhdr2.encode(vhdr2Buf);
if (m_trace)
Utils::dump(1U, "ModemV24::startOfStream() MotVoiceHeader2", vhdr2Buf, MotVoiceHeader2::LENGTH);
queueP25Frame(vhdr2Buf, MotVoiceHeader2::LENGTH, STT_NON_IMBE);
}
/* Send an end of stream sequence (TDU, etc) to the connected serial V.24 device */
void ModemV24::endOfStream()
{
MotStartOfStream end = MotStartOfStream();
end.setStartStop(StartStopFlag::STOP);
// create buffer and encode
uint8_t endBuf[MotStartOfStream::LENGTH];
::memset(endBuf, 0x00U, MotStartOfStream::LENGTH);
end.encode(endBuf);
if (m_trace)
Utils::dump(1U, "ModemV24::endOfStream() MotStartOfStream", endBuf, MotStartOfStream::LENGTH);
queueP25Frame(endBuf, MotStartOfStream::LENGTH, 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_UINT16B(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 = 0U;
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());
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_UINT16B(control.getKId(), vhdr, 11U);
__SET_UINT16B(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);
}
::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 += 19U; // 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 += 19U; // 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 = 0U;
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;
}
/* Internal helper to convert from TIA-102 air interface to V.24/DFSI. */
void ModemV24::convertFromAir(uint8_t* data, uint32_t length)
{
assert(data != nullptr);
assert(length > 0U);
if (m_trace)
Utils::dump(1U, "ModemV24::convertFromAir() 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");
}
startOfStream(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) {
startOfStream(lc);
if (m_debug)
LogDebug(LOG_MODEM, "V24 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)
endOfStream();
break;
case DUID::PDU:
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 startOfStream = MotStartOfStream();
startOfStream.setStartStop(StartStopFlag::START);
startOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED);
startOfStream.setStreamType(StreamTypeFlag::TSBK);
// create buffer and encode
uint8_t startBuf[MotStartOfStream::LENGTH];
::memset(startBuf, 0x00U, MotStartOfStream::LENGTH);
startOfStream.encode(startBuf);
queueP25Frame(startBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER);
MotTSBKFrame tf = MotTSBKFrame();
tf.startOfStream->setStartStop(StartStopFlag::START);
tf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED);
tf.startOfStream->setStreamType(StreamTypeFlag::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[MotTSBKFrame::LENGTH];
::memset(tsbkBuf, 0x00U, MotTSBKFrame::LENGTH);
tf.encode(tsbkBuf);
if (m_trace)
Utils::dump(1U, "ModemV24::convertFromAir() MotTSBKFrame", tsbkBuf, MotTSBKFrame::LENGTH);
queueP25Frame(tsbkBuf, MotTSBKFrame::LENGTH, STT_NON_IMBE_NO_JITTER);
MotStartOfStream endOfStream = MotStartOfStream();
endOfStream.setStartStop(StartStopFlag::STOP);
endOfStream.setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED);
endOfStream.setStreamType(StreamTypeFlag::TSBK);
// create buffer and encode
uint8_t endBuf[MotStartOfStream::LENGTH];
::memset(endBuf, 0x00U, MotStartOfStream::LENGTH);
endOfStream.encode(endBuf);
queueP25Frame(endBuf, MotStartOfStream::LENGTH, STT_NON_IMBE_NO_JITTER);
queueP25Frame(endBuf, MotStartOfStream::LENGTH, 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();
switch (n) {
case 0: // VOICE1/10
{
voice.setFrameType((duid == DUID::LDU1) ? DFSIFrameType::LDU1_VOICE1 : DFSIFrameType::LDU2_VOICE10);
MotStartVoiceFrame svf = MotStartVoiceFrame();
svf.startOfStream->setStartStop(StartStopFlag::START);
svf.startOfStream->setRT(m_rtrt ? RTFlag::ENABLED : RTFlag::DISABLED);
svf.fullRateVoice->setFrameType(voice.getFrameType());
svf.fullRateVoice->setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR);
svf.setICW(m_diu ? ICWFlag::DIU : ICWFlag::QUANTAR);
::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);
voice.setSource(m_diu ? SourceFlag::DIU : SourceFlag::QUANTAR);
::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::convertFromAir() 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)
LogDebug(LOG_MODEM, "V24 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();
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(1U);
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;
voice.setSuperframeCnt(m_superFrameCnt);
voice.encode(buffer + bufferSize);
bufferSize += voice.getLength(); // 18, 17 or 14 depending on voice frame type
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 == 255U)
m_superFrameCnt = 0U;
else
m_superFrameCnt++;
}
}
}

Powered by TurnKey Linux.