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.
3529 lines
141 KiB
3529 lines
141 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-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 <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, 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::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
|
|
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<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) {
|
|
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::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()
|
|
{
|
|
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::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
|
|
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::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.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::milliseconds>(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::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::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++;
|
|
}
|
|
}
|
|
}
|