Merge DFSI host changes into master (#63)

* begin implementing support for the V.24 modem as a regular modem dvmhost can utilize;

* add Tx support (maybe);

* theoretical TSBKs via V.24?;

* set start of stream correctly;

* ensure stream frame type is set correctly; send start of stream and end of stream correctly for TSBK (hopefully);

* fix variable naming; ensure voice frames aren't repeated back to the V.24 source;

* document configuration changes;

* add framework for eventual V.24 UDP support;

* correct issue not handling regular modem commands;

* fix up issue writing V.24 data to the modem buffers;

* initial working V24 modem implementation

* update commenting/documentation;

* reorganize configuration parameters and documentation;

* whoops forgot comment;

* ensure TSDU MBF is disabled for DFSI modems; tag DFSI parameters in log output proper for clarity; ensure P25 fixed mode is forced for DFSI modems;

* fixup V.24 UDP port, properly fake certain modem responses, fix bad RTP transmissions; add STT_NON_IMBE_NO_JITTER message type for ignoring message jitter;

* fix data offsets;

* ensure DFSI's internal call timeout is settable;

* bump version numbers;

* use LOG_MODEM not LOG_SERIAL;

* correct issue with PING/PONG resetting pktSeq incorrectly; add metric calculation for determining RTT for a PING/PONG;

* use milliseconds instead of seconds for delta clocking on the FNE and host for networking; correct several issues with incorrect packet sequences being sent for some control packets; correct issue with tracked packet sequence possibly being incorrectly reset;

---------

Co-authored-by: W3AXL <29879554+W3AXL@users.noreply.github.com>
pull/65/head
Bryan Biedenkapp 2 years ago committed by GitHub
parent 2c640a53c4
commit 617c889d1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -455,7 +455,13 @@ system:
#
protocol:
# Modem port type.
# null - Null Modem (Loopback for testing)
# uart - Serial Modem
type: "null" # Valid values are "null", and "uart"
# Modem interface mode.
# air - Standard air interface modem (Hotspot or Repeater)
# dfsi - TIA-102 DFSI interface modem
mode: "air" # Valid values are "air", and "dfsi"
uart:
# UART/RS232 serial port device.
@ -549,6 +555,19 @@ system:
rssiCoarse: 127 # Valid values between 0 and 255
rssiFine: 127 # Valid values between 0 and 255
#
# V.24 Modem Configuration
#
dfsi:
# RT/RT flag enabled (0x02) or disabled (0x04)
rtrt: true
# Use the DIU source flag (0x00) instead of the quantar source flag (0x02)
diu: true
# Jitter buffer length in ms
jitter: 200
# Timer which will reset local/remote call flags if frames aren't received longer than this time in ms
callTimeout: 200
# Sets received the signal offset from DC.
rxDCOffset: 0 # Valid values between -128 and 128
# Sets transmitted the signal offset from DC.

@ -108,8 +108,8 @@ typedef unsigned long long ulong64_t;
#define __EXE_NAME__ ""
#define VERSION_MAJOR "04"
#define VERSION_MINOR "02"
#define VERSION_REV "C"
#define VERSION_MINOR "04"
#define VERSION_REV "D"
#define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR
#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")"
@ -143,6 +143,9 @@ typedef unsigned long long ulong64_t;
#define UART_PORT "uart"
#define PTY_PORT "pty"
#define MODEM_MODE_AIR "air"
#define MODEM_MODE_DFSI "dfsi"
const uint32_t REMOTE_MODEM_PORT = 3334;
const uint32_t TRAFFIC_DEFAULT_PORT = 62031;
const uint32_t REST_API_DEFAULT_PORT = 9990;

@ -102,7 +102,7 @@ bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const
buffer[20U] = mode; // DVM Mode State
return writeMaster({ NET_FUNC::GRANT_REQ, NET_SUBFUNC::NOP }, buffer, MSG_HDR_SIZE, 0U, 0U);
return writeMaster({ NET_FUNC::GRANT_REQ, NET_SUBFUNC::NOP }, buffer, MSG_HDR_SIZE, RTP_END_OF_CALL_SEQ, 0U);
}
/* Writes the local activity log to the network. */
@ -123,7 +123,7 @@ bool BaseNetwork::writeActLog(const char* message)
::strcpy(buffer + 11U, message);
return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, (uint8_t*)buffer, (uint32_t)len + 12U,
0U, 0U, false, m_useAlternatePortForDiagnostics);
RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics);
}
/* Writes the local diagnostics log to the network. */
@ -144,7 +144,7 @@ bool BaseNetwork::writeDiagLog(const char* message)
::strcpy(buffer + 11U, message);
return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG }, (uint8_t*)buffer, (uint32_t)len + 12U,
0U, 0U, false, m_useAlternatePortForDiagnostics);
RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics);
}
/* Writes the local status to the network. */
@ -184,7 +184,7 @@ bool BaseNetwork::announceGroupAffiliation(uint32_t srcId, uint32_t dstId)
__SET_UINT16(srcId, buffer, 0U);
__SET_UINT16(dstId, buffer, 3U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, buffer, MSG_ANNC_GRP_AFFIL, 0U, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, buffer, MSG_ANNC_GRP_AFFIL, RTP_END_OF_CALL_SEQ, 0U);
}
/* Writes a unit registration to the network. */
@ -198,7 +198,7 @@ bool BaseNetwork::announceUnitRegistration(uint32_t srcId)
__SET_UINT16(srcId, buffer, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, buffer, MSG_ANNC_UNIT_REG, 0U, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, buffer, MSG_ANNC_UNIT_REG, RTP_END_OF_CALL_SEQ, 0U);
}
/* Writes a unit deregistration to the network. */
@ -212,7 +212,7 @@ bool BaseNetwork::announceUnitDeregistration(uint32_t srcId)
__SET_UINT16(srcId, buffer, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, buffer, MSG_ANNC_UNIT_REG, 0U, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, buffer, MSG_ANNC_UNIT_REG, RTP_END_OF_CALL_SEQ, 0U);
}
/* Writes a complete update of the peer affiliation list to the network. */
@ -235,7 +235,7 @@ bool BaseNetwork::announceAffiliationUpdate(const std::unordered_map<uint32_t, u
offs += 8U;
}
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, buffer, 4U + (affs.size() * 8U), 0U, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, buffer, 4U + (affs.size() * 8U), RTP_END_OF_CALL_SEQ, 0U);
}
/* Writes a complete update of the peer's voice channel list to the network. */
@ -257,7 +257,7 @@ bool BaseNetwork::announceSiteVCs(const std::vector<uint32_t> peers)
offs += 4U;
}
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, buffer, 4U + (peers.size() * 4U), 0U, 0U);
return writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, buffer, 4U + (peers.size() * 4U), RTP_END_OF_CALL_SEQ, 0U);
}
/* Resets the DMR ring buffer for the given slot. */

@ -107,7 +107,8 @@ namespace p25
namespace StreamTypeFlag {
/** @brief V.24 Data Stream Type */
enum E : uint8_t {
VOICE = 0x0BU //! Voice
VOICE = 0x0BU, //! Voice
TSBK = 0x0FU //! TSBK
};
}

@ -25,6 +25,7 @@
#include "common/p25/dfsi/frames/MotStartVoiceFrame.h"
#include "common/p25/dfsi/frames/MotVoiceHeader1.h"
#include "common/p25/dfsi/frames/MotVoiceHeader2.h"
#include "common/p25/dfsi/frames/MotTSBKFrame.h"
// FSC
#include "common/p25/dfsi/frames/fsc/FSCMessage.h"

@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "common/p25/P25Defines.h"
#include "common/p25/dfsi/frames/MotTSBKFrame.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace p25::defines;
using namespace p25::dfsi;
using namespace p25::dfsi::defines;
using namespace p25::dfsi::frames;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a instance of the MotTSBKFrame class. */
MotTSBKFrame::MotTSBKFrame() :
startOfStream(nullptr),
tsbkData(nullptr)
{
tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES];
::memset(tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES);
startOfStream = new MotStartOfStream();
}
/* Initializes a instance of the MotTSBKFrame class. */
MotTSBKFrame::MotTSBKFrame(uint8_t* data) :
startOfStream(nullptr),
tsbkData(nullptr)
{
tsbkData = new uint8_t[P25_TSBK_LENGTH_BYTES];
::memset(tsbkData, 0x00U, P25_TSBK_LENGTH_BYTES);
decode(data);
}
/* Finalizes a instance of the MotTSBKFrame class. */
MotTSBKFrame::~MotTSBKFrame()
{
if (startOfStream != nullptr)
delete startOfStream;
if (tsbkData != nullptr)
delete[] tsbkData;
}
/* Decode a TSBK frame. */
bool MotTSBKFrame::decode(const uint8_t* data)
{
assert(data != nullptr);
// create a new start of stream
if (startOfStream != nullptr)
delete startOfStream;
startOfStream = new MotStartOfStream();
// create a buffer to decode the start record skipping the 10th byte (adjMM)
uint8_t startBuffer[MotStartOfStream::LENGTH];
::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH);
::memcpy(startBuffer + 1U, data, 4U);
// decode start of stream
startOfStream->decode(startBuffer);
::memcpy(tsbkData, data + 8U, P25_TSBK_LENGTH_BYTES);
return true;
}
/* Encode a TSBK frame. */
void MotTSBKFrame::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
// encode start of stream - scope is intentional
{
uint8_t buffer[MotStartOfStream::LENGTH];
startOfStream->encode(buffer);
// copy to data array (skipping first and last bytes)
::memcpy(data + 1U, buffer + 1U, 4U);
}
// encode TSBK - scope is intentional
{
data[0U] = DFSIFrameType::TSBK;
::memcpy(data + 8U, tsbkData, P25_TSBK_LENGTH_BYTES);
}
}

@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file MotTSBKFrame.h
* @ingroup dfsi_frames
* @file MotTSBKFrame.cpp
* @ingroup dfsi_frames
*/
#if !defined(__MOT_TSBK_FRAME_H__)
#define __MOT_TSBK_FRAME_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "common/p25/dfsi/frames/FrameDefines.h"
#include "common/p25/dfsi/frames/MotStartOfStream.h"
#include "common/p25/dfsi/frames/MotFullRateVoice.h"
namespace p25
{
namespace dfsi
{
namespace frames
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements a P25 Motorola TSBK frame.
* \code{.unparsed}
* Byte 0 1 2 3
* Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Encoded Motorola Start of Stream |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved ? |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | TSBK |
* + +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Unknown ? |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @ingroup dfsi_frames
*/
class HOST_SW_API MotTSBKFrame {
public:
static const uint8_t LENGTH = 24;
/**
* @brief Initializes a copy instance of the MotTSBKFrame class.
*/
MotTSBKFrame();
/**
* @brief Initializes a copy instance of the MotTSBKFrame class.
* @param data Buffer to containing MotTSBKFrame to decode.
*/
MotTSBKFrame(uint8_t* data);
/**
* @brief Finalizes a instance of the MotTSBKFrame class.
*/
~MotTSBKFrame();
/**
* @brief Decode a TSBK frame.
* @param[in] data Buffer to containing MotTSBKFrame to decode.
*/
bool decode(const uint8_t* data);
/**
* @brief Encode a TSBK frame.
* @param[out] data Buffer to encode a MotTSBKFrame.
*/
void encode(uint8_t* data);
public:
MotStartOfStream* startOfStream; // ?? - this should probably be private with getters/setters
uint8_t* tsbkData; // ?? - this should probably be private with getters/setters
};
} // namespace frames
} // namespace dfsi
} // namespace p25
#endif // __MOT_TSBK_FRAME_H__

@ -256,7 +256,7 @@ void FNENetwork::clock(uint32_t ms)
return;
}
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
if (m_forceListUpdate) {
for (auto peer : m_peers) {
@ -274,7 +274,7 @@ void FNENetwork::clock(uint32_t ms)
FNEPeerConnection* connection = peer.second;
if (connection != nullptr) {
if (connection->connected()) {
uint64_t dt = connection->lastPing() + (m_host->m_pingTime * m_host->m_maxMissedPings);
uint64_t dt = connection->lastPing() + ((m_host->m_pingTime * 1000) * m_host->m_maxMissedPings);
if (dt < now) {
LogInfoEx(LOG_NET, "PEER %u (%s) timed out, dt = %u, now = %u", id, connection->identity().c_str(),
dt, now);
@ -389,7 +389,7 @@ void* FNENetwork::threadedNetworkRx(void* arg)
if (req != nullptr) {
::pthread_detach(req->thread);
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
FNENetwork* network = static_cast<FNENetwork*>(req->obj);
if (network == nullptr) {
@ -414,8 +414,12 @@ void* FNENetwork::threadedNetworkRx(void* arg)
if (connection != nullptr) {
if (pktSeq == RTP_END_OF_CALL_SEQ) {
// only reset packet sequences if we're a PROTOCOL or RPTC function
if ((req->fneHeader.getFunction() == NET_FUNC::PROTOCOL) ||
(req->fneHeader.getFunction() == NET_FUNC::RPTC)) {
connection->pktLastSeq(pktSeq);
connection->pktNextSeq(0U);
}
} else {
if ((connection->currStreamId() == streamId) && (pktSeq != connection->pktNextSeq()) && (pktSeq != (RTP_END_OF_CALL_SEQ - 1U))) {
LogWarning(LOG_NET, "PEER %u (%s) stream %u out-of-sequence; %u != %u", peerId, connection->identity().c_str(),
@ -847,19 +851,33 @@ void* FNENetwork::threadedNetworkRx(void* arg)
connection->pingsReceived(pingsRx);
connection->lastPing(now);
connection->pktLastSeq(connection->pktLastSeq() + 1);
// does this peer need an ACL update?
uint64_t dt = connection->lastACLUpdate() + network->m_updateLookupTime;
uint64_t dt = connection->lastACLUpdate() + (network->m_updateLookupTime * 1000);
if (dt < now) {
LogInfoEx(LOG_NET, "PEER %u (%s) updating ACL list, dt = %u, now = %u", peerId, connection->identity().c_str(),
dt, now);
if (connection->pktLastSeq() == RTP_END_OF_CALL_SEQ) {
network->peerACLUpdate(peerId);
connection->lastACLUpdate(now);
}
}
uint8_t payload[8U];
::memset(payload, 0x00U, 8U);
// split ulong64_t (8 byte) value into bytes
payload[0U] = (uint8_t)((now >> 56) & 0xFFU);
payload[1U] = (uint8_t)((now >> 48) & 0xFFU);
payload[2U] = (uint8_t)((now >> 40) & 0xFFU);
payload[3U] = (uint8_t)((now >> 32) & 0xFFU);
payload[4U] = (uint8_t)((now >> 24) & 0xFFU);
payload[5U] = (uint8_t)((now >> 16) & 0xFFU);
payload[6U] = (uint8_t)((now >> 8) & 0xFFU);
payload[7U] = (uint8_t)((now >> 0) & 0xFFU);
network->m_peers[peerId] = connection;
network->writePeerCommand(peerId, { NET_FUNC::PONG, NET_SUBFUNC::NOP });
network->writePeerCommand(peerId, { NET_FUNC::PONG, NET_SUBFUNC::NOP }, payload, 8U);
if (network->m_reportPeerPing) {
LogInfoEx(LOG_NET, "PEER %u (%s) ping, pingsReceived = %u, lastPing = %u", peerId, connection->identity().c_str(),
@ -1351,10 +1369,15 @@ void* FNENetwork::threadedACLUpdate(void* arg)
std::string peerIdentity = network->resolvePeerIdentity(req->peerId);
LogInfoEx(LOG_NET, "PEER %u (%s) sending ACL list updates", req->peerId, peerIdentity.c_str());
FNEPeerConnection* connection = network->m_peers[req->peerId];
if (connection != nullptr) {
network->writeWhitelistRIDs(req->peerId);
network->writeBlacklistRIDs(req->peerId);
network->writeTGIDs(req->peerId);
connection->pktLastSeq(RTP_END_OF_CALL_SEQ - 1U);
network->writeDeactiveTGIDs(req->peerId);
}
delete req;
}
@ -1366,7 +1389,7 @@ void* FNENetwork::threadedACLUpdate(void* arg)
void FNENetwork::writeWhitelistRIDs(uint32_t peerId)
{
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// send radio ID white/black lists
std::vector<uint32_t> ridWhitelist;
@ -1439,7 +1462,7 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId)
void FNENetwork::writeBlacklistRIDs(uint32_t peerId)
{
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// send radio ID blacklist
std::vector<uint32_t> ridBlacklist;

@ -116,7 +116,7 @@ namespace network
m_lastACLUpdate(0U),
m_isExternalPeer(false),
m_config(),
m_pktLastSeq(0U),
m_pktLastSeq(RTP_END_OF_CALL_SEQ),
m_pktNextSeq(1U)
{
/* stub */
@ -143,7 +143,7 @@ namespace network
m_lastACLUpdate(0U),
m_isExternalPeer(false),
m_config(),
m_pktLastSeq(0U),
m_pktLastSeq(RTP_END_OF_CALL_SEQ),
m_pktNextSeq(1U)
{
assert(id > 0U);

@ -46,6 +46,8 @@ file(GLOB dvmhost_SRC
"src/host/modem/*.cpp"
"src/host/modem/port/*.h"
"src/host/modem/port/*.cpp"
"src/host/modem/port/specialized/*.h"
"src/host/modem/port/specialized/*.cpp"
"src/host/network/*.h"
"src/host/network/*.cpp"
"src/remote/RESTClient.cpp"

@ -16,6 +16,7 @@
#include "modem/port/UARTPort.h"
#include "modem/port/PseudoPTYPort.h"
#include "modem/port/UDPPort.h"
#include "modem/port/specialized/V24UDPPort.h"
#include "Host.h"
#include "HostMain.h"
@ -395,6 +396,7 @@ bool Host::createModem()
yaml::Node modemProtocol = modemConf["protocol"];
std::string portType = modemProtocol["type"].as<std::string>("null");
std::string modemMode = modemProtocol["mode"].as<std::string>("air");
yaml::Node uartProtocol = modemProtocol["uart"];
std::string uartPort = uartProtocol["port"].as<std::string>();
uint32_t uartSpeed = uartProtocol["speed"].as<uint32_t>(115200);
@ -449,6 +451,14 @@ bool Host::createModem()
uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as<uint32_t>(P25_TX_BUFFER_LEN);
uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as<uint32_t>(NXDN_TX_BUFFER_LEN);
yaml::Node dfsiParams = modemConf["dfsi"];
bool rtrt = dfsiParams["rtrt"].as<bool>(true);
bool diu = dfsiParams["diu"].as<bool>(true);
uint16_t jitter = dfsiParams["jitter"].as<uint16_t>(200U);
bool useFSCForUDP = dfsiParams["useFSC"].as<bool>(false);
uint16_t dfsiCallTimeout = dfsiParams["callTimeout"].as<uint16_t>(200U);
// clamp fifo sizes
if (dmrFifoLength < DMR_TX_BUFFER_LEN) {
LogWarning(LOG_HOST, "DMR FIFO size must be greater then %u bytes, defaulting to %u bytes!", DMR_TX_BUFFER_LEN, DMR_TX_BUFFER_LEN);
@ -492,6 +502,7 @@ bool Host::createModem()
LogInfo("Modem Parameters");
LogInfo(" Port Type: %s", portType.c_str());
LogInfo(" Interface Mode: %s", modemMode.c_str());
port::IModemPort* modemPort = nullptr;
std::transform(portType.begin(), portType.end(), portType.begin(), ::tolower);
@ -551,16 +562,33 @@ bool Host::createModem()
return false;
}
std::transform(modemMode.begin(), modemMode.end(), modemMode.begin(), ::tolower);
if (modemMode == MODEM_MODE_DFSI) {
m_isModemDFSI = true;
LogInfo(" DFSI RT/RT: %s", rtrt ? "yes" : "no");
LogInfo(" DFSI DIU Flag: %s", diu ? "yes" : "no");
LogInfo(" DFSI Jitter Size: %u ms", jitter);
if (g_remoteModemMode) {
LogInfo(" DFSI Use FSC: %s", useFSCForUDP ? "yes" : "no");
}
}
if (g_remoteModemMode) {
if (portType == UART_PORT || portType == PTY_PORT) {
m_modemRemotePort = new port::UDPPort(g_remoteAddress, g_remotePort);
m_modemRemote = true;
ignoreModemConfigArea = true;
}
else {
delete modemPort;
if (modemMode == MODEM_MODE_DFSI) {
yaml::Node networkConf = m_conf["network"];
uint32_t id = networkConf["id"].as<uint32_t>(1000U);
modemPort = new port::specialized::V24UDPPort(id, g_remoteAddress, g_remotePort, g_remotePort, useFSCForUDP, debug);
m_udpDSFIRemotePort = modemPort;
} else {
modemPort = new port::UDPPort(g_remoteAddress, g_remotePort);
}
m_modemRemote = false;
}
@ -614,8 +642,14 @@ bool Host::createModem()
LogInfo(" Debug: yes");
}
if (m_isModemDFSI) {
m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, m_p25QueueSizeBytes, rtrt, diu, jitter,
dumpModemStatus, trace, debug);
((ModemV24*)m_modem)->setCallTimeout(dfsiCallTimeout);
} else {
m_modem = new Modem(modemPort, m_duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, fdmaPreamble, dmrRxDelay, p25CorrCount,
m_dmrQueueSizeBytes, m_p25QueueSizeBytes, m_nxdnQueueSizeBytes, disableOFlowReset, ignoreModemConfigArea, dumpModemStatus, trace, debug);
}
if (!m_modemRemote) {
m_modem->setModeParams(m_dmrEnabled, m_p25Enabled, m_nxdnEnabled);
m_modem->setLevels(rxLevel, cwIdTXLevel, dmrTXLevel, p25TXLevel, nxdnTXLevel);

@ -16,6 +16,7 @@
#include "common/StopWatch.h"
#include "common/Thread.h"
#include "common/Utils.h"
#include "modem/port/specialized/V24UDPPort.h"
#include "remote/RESTClient.h"
#include "host/Host.h"
#include "ActivityLog.h"
@ -59,6 +60,8 @@ Host::Host(const std::string& confFile) :
m_conf(),
m_modem(nullptr),
m_modemRemote(false),
m_isModemDFSI(false),
m_udpDSFIRemotePort(nullptr),
m_network(nullptr),
m_modemRemotePort(nullptr),
m_state(STATE_IDLE),
@ -585,6 +588,16 @@ int Host::run()
g_killed = true;
}
if (m_isModemDFSI && m_dmrEnabled) {
::LogError(LOG_HOST, "Cannot use V.24/DFSI modem with DMR protocol!");
g_killed = true;
}
if (m_isModemDFSI && m_nxdnEnabled) {
::LogError(LOG_HOST, "Cannot use V.24/DFSI modem with NXDN protocol!");
g_killed = true;
}
// P25 CC checks
if (m_dmrEnabled && m_p25CtrlChannel) {
::LogError(LOG_HOST, "Cannot have DMR enabled when using dedicated P25 control!");
@ -681,6 +694,11 @@ int Host::run()
setState(STATE_P25);
}
if (m_isModemDFSI) {
m_fixedMode = true;
setState(STATE_P25);
}
if (m_nxdnCtrlChannel) {
m_fixedMode = true;
setState(STATE_NXDN);
@ -738,6 +756,8 @@ int Host::run()
}
stopWatch.start();
} else {
return EXIT_SUCCESS;
}
bool hasTxShutdown = false;
@ -906,6 +926,13 @@ int Host::run()
}
}
if (m_udpDSFIRemotePort != nullptr) {
m_mainLoopStage = 11U; // intentional magic number
modem::port::specialized::V24UDPPort* udpPort = dynamic_cast<modem::port::specialized::V24UDPPort*>(m_udpDSFIRemotePort);
udpPort->clock(ms);
}
// ------------------------------------------------------
// -- Timer Clocking --
// ------------------------------------------------------

@ -40,6 +40,7 @@
#include "network/Network.h"
#include "network/RESTAPI.h"
#include "modem/Modem.h"
#include "modem/ModemV24.h"
#include <string>
#include <unordered_map>
@ -92,6 +93,8 @@ private:
modem::Modem* m_modem;
bool m_modemRemote;
bool m_isModemDFSI;
modem::port::IModemPort* m_udpDSFIRemotePort;
network::Network* m_network;
modem::port::IModemPort* m_modemRemotePort;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,365 @@
// 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 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file ModemV24.h
* @ingroup modem
* @file ModemV24.cpp
* @ingroup modem
*/
#if !defined(__MODEM_V24_H__)
#define __MODEM_V24_H__
#include "Defines.h"
#include "common/edac/RS634717.h"
#include "common/p25/dfsi/frames/MotVoiceHeader1.h"
#include "common/p25/dfsi/frames/MotVoiceHeader2.h"
#include "common/p25/lc/LC.h"
#include "common/p25/Audio.h"
#include "common/p25/NID.h"
#include "modem/Modem.h"
namespace modem
{
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/**
* @addtogroup modem
* @{
*/
/**
* @brief DFSI serial tx flags used to determine proper jitter handling of data in ringbuffer.
* @ingroup modem
*/
enum SERIAL_TX_TYPE {
STT_NO_DATA, //! No Data
STT_NON_IMBE, //! Non-IMBE Data/Signalling Frame
STT_NON_IMBE_NO_JITTER, //! Non-IMBE Data/Signalling Frame with Jitter Disabled
STT_IMBE //! IMBE Voice Frame
};
/** @} */
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents DFSI call data.
* @ingroup modem
*/
class HOST_SW_API DFSICallData {
public:
auto operator=(DFSICallData&) -> DFSICallData& = delete;
auto operator=(DFSICallData&&) -> DFSICallData& = delete;
DFSICallData(DFSICallData&) = delete;
/**
* @brief Initializes a new instance of the DFSICallData class.
*/
DFSICallData() :
srcId(0U),
dstId(0U),
lco(0U),
mfId(P25DEF::MFG_STANDARD),
serviceOptions(0U),
lsd1(0U),
lsd2(0U),
MI(nullptr),
algoId(P25DEF::ALGO_UNENCRYPT),
kId(0U),
VHDR1(nullptr),
VHDR2(nullptr),
netLDU1(nullptr),
netLDU2(nullptr)
{
MI = new uint8_t[P25DEF::MI_LENGTH_BYTES];
VHDR1 = new uint8_t[p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH];
VHDR2 = new uint8_t[p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH];
netLDU1 = new uint8_t[9U * 25U];
netLDU2 = new uint8_t[9U * 25U];
::memset(netLDU1, 0x00U, 9U * 25U);
::memset(netLDU2, 0x00U, 9U * 25U);
resetCallData();
}
/**
* @brief Finalizes a instance of the DFSICallData class.
*/
~DFSICallData()
{
if (MI != nullptr)
delete[] MI;
if (VHDR1 != nullptr)
delete[] VHDR1;
if (VHDR2 != nullptr)
delete[] VHDR2;
if (netLDU1 != nullptr)
delete[] netLDU1;
if (netLDU2 != nullptr)
delete[] netLDU2;
}
/**
* @brief Helper to reset the call data associated with this connection.
*/
void resetCallData()
{
srcId = 0U;
dstId = 0U;
lco = 0U;
mfId = P25DEF::MFG_STANDARD;
serviceOptions = 0U;
lsd1 = 0U;
lsd2 = 0U;
if (MI != nullptr)
::memset(MI, 0x00U, P25DEF::MI_LENGTH_BYTES);
algoId = P25DEF::ALGO_UNENCRYPT;
kId = 0U;
if (VHDR1 != nullptr)
::memset(VHDR1, 0x00U, p25::dfsi::frames::MotVoiceHeader1::HCW_LENGTH);
if (VHDR2 != nullptr)
::memset(VHDR2, 0x00U, p25::dfsi::frames::MotVoiceHeader2::HCW_LENGTH);
if (netLDU1 != nullptr)
::memset(netLDU1, 0x00U, 9U * 25U);
if (netLDU2 != nullptr)
::memset(netLDU2, 0x00U, 9U * 25U);
n = 0U;
seqNo = 0U;
}
public:
/** @name Call Data */
/**
* @brief Source Radio ID.
*/
uint32_t srcId;
/**
* @brief Destination ID.
*/
uint32_t dstId;
/**
* @brief Link Control Opcode.
*/
uint8_t lco;
/**
* @brief Manufacturer ID.
*/
uint8_t mfId;
/**
* @brief Call Service Options.
*/
uint8_t serviceOptions;
/**
* @brief Low Speed Data 1.
*/
uint8_t lsd1;
/**
* @brief Low Speed Data 2.
*/
uint8_t lsd2;
/**
* @brief Encryption Message Indicator.
*/
uint8_t* MI;
/**
* @brief Encryption Algorithm ID.
*/
uint8_t algoId;
/**
* @brief Encryption Key ID.
*/
uint32_t kId;
/**
* @brief Voice Header 1.
*/
uint8_t* VHDR1;
/**
* @brief Voice Header 2.
*/
uint8_t* VHDR2;
/**
* @brief Sequence Number.
*/
uint32_t seqNo;
/**
* @brief
*/
uint8_t n;
/**
* @brief LDU1 Buffer.
*/
uint8_t* netLDU1;
/**
* @brief LDU2 Buffer.
*/
uint8_t* netLDU2;
/** @} */
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements the core interface to the V.24 modem hardware.
* @ingroup modem
*/
class HOST_SW_API ModemV24 : public Modem {
public:
/**
* @brief Initializes a new instance of the ModemV24 class.
* @param port Port the air interface modem is connected to.
* @param duplex Flag indicating the modem is operating in duplex mode.
* @param p25QueueSize Modem P25 Rx frame buffer queue size (bytes).
* @param p25TxQueueSize Modem P25 Tx frame buffer queue size (bytes).
* @param rtrt Flag indicating whether or not RT/RT is enabled.
* @param diu Flag indicating whether or not V.24 communications are to a DIU.
* @param jitter
* @param dumpModemStatus Flag indicating whether the modem status is dumped to the log.
* @param trace Flag indicating whether air interface modem trace is enabled.
* @param debug Flag indicating whether air interface modem debug is enabled.
*/
ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, uint32_t p25TxQueueSize,
bool rtrt, bool diu, uint16_t jitter, bool dumpModemStatus, bool trace, bool debug);
/**
* @brief Finalizes a instance of the ModemV24 class.
*/
~ModemV24();
/**
* @brief Sets the call timeout.
* @param timeout Timeout.
*/
void setCallTimeout(uint16_t timeout);
/**
* @brief Sets the P25 NAC.
* @param nac NAC.
*/
void setP25NAC(uint32_t nac) override;
/**
* @brief Opens connection to the air interface modem.
* @returns bool True, if connection to modem is made, otherwise false.
*/
bool open() override;
/**
* @brief Updates the modem by the passed number of milliseconds.
* @param ms Number of milliseconds.
*/
void clock(uint32_t ms) override;
/**
* @brief Closes connection to the air interface modem.
*/
void close() override;
/**
* @brief Writes raw data to the air interface modem.
* @param data Data to write to modem.
* @param length Length of data to write.
* @returns int Actual length of data written.
*/
int write(const uint8_t* data, uint32_t length) override;
private:
bool m_rtrt;
bool m_diu;
p25::Audio m_audio;
p25::NID* m_nid;
RingBuffer<uint8_t> m_txP25Queue;
DFSICallData* m_txCall;
DFSICallData* m_rxCall;
bool m_txCallInProgress;
bool m_rxCallInProgress;
uint64_t m_txLastFrameTime;
uint64_t m_rxLastFrameTime;
uint16_t m_callTimeout;
uint16_t m_jitter;
uint64_t m_lastP25Tx;
edac::RS634717 m_rs;
/**
* @brief Helper to write data from the P25 Tx queue to the serial interface.
* @return int Actual number of bytes written to the serial interface.
*/
int writeSerial();
/**
* @brief Helper to store converted Rx frames.
* @param buffer Buffer containing converted Rx frame.
* @param length Length of buffer.
*/
void storeConvertedRx(const uint8_t* buffer, uint32_t length);
/**
* @brief Helper to generate a P25 TDU packet.
* @param buffer Buffer to create TDU.
*/
void create_TDU(uint8_t* buffer);
/**
* @brief Internal helper to convert from V.24/DFSI to TIA-102 air interface.
* @param data Buffer containing data to convert.
* @param length Length of buffer.
*/
void convertToAir(const uint8_t *data, uint32_t length);
/**
* @brief Helper to add a V.24 data frame to the P25 Tx queue with the proper timestamp and formatting.
* @param data Buffer containing V.24 data frame to send.
* @param len Length of buffer.
* @param msgType Type of message to send (used for proper jitter clocking).
*/
void queueP25Frame(uint8_t* data, uint16_t length, SERIAL_TX_TYPE msgType);
/**
* @brief Send a start of stream sequence (HDU, etc) to the connected serial V24 device.
* @param[in] control Instance of p25::lc::LC containing link control data.
*/
void startOfStream(const p25::lc::LC& control);
/**
* @brief Send an end of stream sequence (TDU, etc) to the connected serial V24 device.
*/
void endOfStream();
/**
* @brief Internal helper to convert from TIA-102 air interface to V.24/DFSI.
* @param data Buffer containing data to convert.
* @param length Length of buffer.
*/
void convertFromAir(uint8_t* data, uint32_t length);
};
} // namespace modem
#endif // __MODEM_V24_H__

@ -0,0 +1,639 @@
// 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 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "common/network/RTPHeader.h"
#include "common/p25/dfsi/frames/Frames.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "modem/port/specialized/V24UDPPort.h"
#include "modem/Modem.h"
using namespace p25::dfsi::defines;
using namespace p25::dfsi::frames;
using namespace p25::dfsi::frames::fsc;
using namespace modem::port;
using namespace modem::port::specialized;
using namespace network;
using namespace network::frame;
using namespace network::udp;
#include <cstring>
#include <cassert>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define RTP_END_OF_CALL_SEQ 65535
const uint32_t BUFFER_LENGTH = 2000U;
const char* V24_UDP_HARDWARE = "V.24 UDP Modem Controller";
const uint8_t V24_UDP_PROTOCOL_VERSION = 4U;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the V24UDPPort class. */
V24UDPPort::V24UDPPort(uint32_t peerId, const std::string& address, uint16_t modemPort, uint16_t controlPort, bool useFSC, bool debug) :
m_socket(nullptr),
m_localPort(modemPort),
m_controlSocket(nullptr),
m_ctrlFrameQueue(nullptr),
m_address(address),
m_addr(),
m_controlAddr(),
m_addrLen(0U),
m_ctrlAddrLen(0U),
m_buffer(2000U, "UDP Port Ring Buffer"),
m_reqConnectionTimer(1000U, 30U),
m_heartbeatTimer(1000U, 5U),
m_reqConnectionToPeer(true),
m_establishedConnection(false),
m_random(),
m_peerId(peerId),
m_streamId(0U),
m_timestamp(INVALID_TS),
m_pktSeq(0U),
m_modemState(STATE_P25),
m_tx(false),
m_debug(debug)
{
assert(peerId > 0U);
assert(!address.empty());
assert(modemPort > 0U);
assert(controlPort > 0U);
if (controlPort > 0U && useFSC) {
m_controlSocket = new Socket(controlPort);
m_ctrlFrameQueue = new RawFrameQueue(m_controlSocket, m_debug);
if (udp::Socket::lookup(address, controlPort, m_controlAddr, m_ctrlAddrLen) != 0)
m_ctrlAddrLen = 0U;
if (m_ctrlAddrLen > 0U) {
std::string ctrlAddrStr = udp::Socket::address(m_controlAddr);
LogWarning(LOG_HOST, "SECURITY: Remote modem expects V.24 control channel IP address; %s for remote modem control", ctrlAddrStr.c_str());
}
} else {
createVCPort(modemPort);
}
std::random_device rd;
std::mt19937 mt(rd());
m_random = mt;
m_streamId = createStreamId();
}
/* Finalizes a instance of the V24UDPPort class. */
V24UDPPort::~V24UDPPort()
{
if (m_ctrlFrameQueue != nullptr)
delete m_ctrlFrameQueue;
if (m_controlSocket != nullptr)
delete m_controlSocket;
if (m_socket != nullptr)
delete m_socket;
}
/* Updates the timer by the passed number of milliseconds. */
void V24UDPPort::clock(uint32_t ms)
{
// if we have a FSC control socket
if (m_controlSocket != nullptr) {
if (m_reqConnectionToPeer) {
if (!m_reqConnectionTimer.isRunning()) {
// make initial request
writeConnect();
m_reqConnectionTimer.start();
} else {
m_reqConnectionTimer.clock(ms);
if (m_reqConnectionTimer.isRunning() && m_reqConnectionTimer.hasExpired()) {
// make another request
writeConnect();
}
}
}
if (m_establishedConnection) {
m_heartbeatTimer.clock(ms);
if (m_heartbeatTimer.isRunning() && m_heartbeatTimer.hasExpired()) {
writeHeartbeat();
m_heartbeatTimer.start();
}
}
processCtrlNetwork();
}
// if we have a RTP voice socket
if (m_socket != nullptr) {
uint8_t data[BUFFER_LENGTH];
::memset(data, 0x00U, BUFFER_LENGTH);
sockaddr_storage addr;
uint32_t addrLen;
int ret = m_socket->read(data, BUFFER_LENGTH, addr, addrLen);
if (ret != 0) {
// An error occurred on the socket
if (ret < 0)
return;
// Add new data to the ring buffer
if (ret > 0) {
RTPHeader rtpHeader = RTPHeader();
rtpHeader.decode(data);
// ensure payload type is correct
if (rtpHeader.getPayloadType() != DFSI_RTP_PAYLOAD_TYPE)
{
LogError(LOG_MODEM, "Invalid RTP header received from network");
return;
}
// copy message
uint32_t messageLength = ret - RTP_HEADER_LENGTH_BYTES;
uint8_t message[messageLength];
::memset(message, 0x00U, messageLength);
::memcpy(message, data + RTP_HEADER_LENGTH_BYTES, messageLength);
if (udp::Socket::match(addr, m_addr)) {
uint8_t reply[messageLength + 4U];
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = messageLength & 0xFFU;
reply[2U] = CMD_P25_DATA;
reply[3U] = 0x00U;
::memcpy(reply + 4U, message, messageLength);
m_buffer.addData(reply, messageLength + 4U);
}
else {
std::string addrStr = udp::Socket::address(addr);
LogWarning(LOG_HOST, "SECURITY: Remote modem mode encountered invalid IP address; %s", addrStr.c_str());
}
}
}
}
}
/* Resets the RTP packet sequence and stream ID. */
void V24UDPPort::reset()
{
m_pktSeq = 0U;
m_timestamp = INVALID_TS;
m_streamId = createStreamId();
}
/* Opens a connection to the port. */
bool V24UDPPort::open()
{
if (m_addrLen == 0U && m_ctrlAddrLen == 0U) {
LogError(LOG_NET, "Unable to resolve the address of the modem");
return false;
}
if (m_controlSocket != nullptr) {
return m_controlSocket->open(m_controlAddr);
} else {
if (m_socket != nullptr) {
return m_socket->open(m_addr);
}
}
return false;
}
/* Reads data from the port. */
int V24UDPPort::read(uint8_t* buffer, uint32_t length)
{
assert(buffer != nullptr);
assert(length > 0U);
// Get required data from the ring buffer
uint32_t avail = m_buffer.dataSize();
if (avail < length)
length = avail;
if (length > 0U)
m_buffer.get(buffer, length);
return int(length);
}
/* Writes data to the port. */
int V24UDPPort::write(const uint8_t* buffer, uint32_t length)
{
assert(buffer != nullptr);
assert(length > 0U);
switch (buffer[2U]) {
case CMD_GET_VERSION:
getVersion();
return int(length);
case CMD_GET_STATUS:
getStatus();
return int(length);
case CMD_SET_CONFIG:
case CMD_SET_MODE:
writeAck(buffer[2U]);
return int(length);
case CMD_P25_DATA:
{
if (m_socket != nullptr) {
uint32_t messageLen = 0U;
uint8_t* message = generateMessage(buffer + 3U, length - 3U, m_streamId, m_peerId, m_pktSeq, &messageLen);
bool written = m_socket->write(message, messageLen, m_addr, m_addrLen);
if (written)
return length;
} else {
writeNAK(CMD_P25_DATA, RSN_INVALID_REQUEST);
return int(length);
}
}
break;
case CMD_FLSH_READ:
writeNAK(CMD_FLSH_READ, RSN_NO_INTERNAL_FLASH);
return int(length);
default:
return int(length);
}
return -1;
}
/* Closes the connection to the port. */
void V24UDPPort::close()
{
if (m_controlSocket != nullptr)
m_controlSocket->close();
if (m_socket != nullptr)
m_socket->close();
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Process FSC control frames from the network. */
void V24UDPPort::processCtrlNetwork()
{
sockaddr_storage address;
uint32_t addrLen;
int length = 0U;
// read message
UInt8Array buffer = m_ctrlFrameQueue->read(length, address, addrLen);
if (length > 0) {
if (m_debug)
Utils::dump(1U, "FSC Control Network Message", buffer.get(), length);
V24PacketRequest* req = new V24PacketRequest();
req->address = address;
req->addrLen = addrLen;
req->length = length;
req->buffer = new uint8_t[length];
::memcpy(req->buffer, buffer.get(), length);
if (!Thread::runAsThread(this, threadedCtrlNetworkRx, req)) {
delete[] req->buffer;
delete req;
return;
}
}
}
/* Process a data frames from the network. */
void* V24UDPPort::threadedCtrlNetworkRx(void* arg)
{
V24PacketRequest* req = (V24PacketRequest*)arg;
if (req != nullptr) {
::pthread_detach(req->thread);
V24UDPPort* network = static_cast<V24UDPPort*>(req->obj);
if (network == nullptr) {
delete req;
return nullptr;
}
if (req->length > 0) {
if (network->m_reqConnectionToPeer) {
// FSC_CONNECT response -- is ... strange
if (req->buffer[0] == 1U) {
network->m_reqConnectionToPeer = false;
network->m_reqConnectionTimer.stop();
network->m_establishedConnection = true;
FSCConnectResponse resp = FSCConnectResponse(req->buffer);
uint16_t vcBasePort = resp.getVCBasePort();
network->m_localPort = vcBasePort;
network->createVCPort(vcBasePort);
network->m_heartbeatTimer.start();
uint8_t buffer[FSCConnectResponse::LENGTH];
::memset(buffer, 0x00U, FSCConnectResponse::LENGTH);
resp.setVCBasePort(network->m_localPort);
resp.encode(buffer);
network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen);
}
}
else
{
std::unique_ptr<FSCMessage> message = FSCMessage::createMessage(req->buffer);
if (message != nullptr) {
switch (message->getMessageId())
{
case FSCMessageType::FSC_ACK:
{
FSCACK* ackMessage = static_cast<FSCACK*>(message.get());
switch (ackMessage->getResponseCode())
{
case FSCAckResponseCode::CONTROL_NAK:
case FSCAckResponseCode::CONTROL_NAK_CONNECTED:
case FSCAckResponseCode::CONTROL_NAK_M_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_V_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_F_UNSUPP:
case FSCAckResponseCode::CONTROL_NAK_PARMS:
case FSCAckResponseCode::CONTROL_NAK_BUSY:
LogError(LOG_MODEM, "V.24 UDP, ACK, ackMessageId = $%02X, ackResponseCode = $%02X", ackMessage->getAckMessageId(), ackMessage->getResponseCode());
break;
case FSCAckResponseCode::CONTROL_ACK:
{
if (ackMessage->getAckMessageId() == FSCMessageType::FSC_DISCONNECT) {
network->m_reqConnectionTimer.stop();
network->m_reqConnectionToPeer = false;
network->m_establishedConnection = false;
network->m_heartbeatTimer.stop();
}
}
break;
default:
LogError(LOG_MODEM, "V.24 UDP, unknown ACK opcode, ackMessageId = $%02X", ackMessage->getAckMessageId());
break;
}
}
break;
case FSCMessageType::FSC_CONNECT:
{
network->createVCPort(network->m_localPort);
network->m_heartbeatTimer.start();
uint8_t buffer[FSCConnectResponse::LENGTH];
::memset(buffer, 0x00U, FSCConnectResponse::LENGTH);
FSCConnectResponse resp = FSCConnectResponse(req->buffer);
resp.setVCBasePort(network->m_localPort);
resp.encode(buffer);
network->m_ctrlFrameQueue->write(buffer, FSCConnectResponse::LENGTH, network->m_controlAddr, network->m_ctrlAddrLen);
}
break;
case FSCMessageType::FSC_DISCONNECT:
{
network->m_reqConnectionTimer.stop();
network->m_reqConnectionToPeer = false;
network->m_establishedConnection = false;
network->m_heartbeatTimer.stop();
}
break;
case FSCMessageType::FSC_HEARTBEAT:
{
if (network->m_establishedConnection) {
network->writeHeartbeat();
}
}
break;
default:
break;
}
}
}
}
if (req->buffer != nullptr)
delete[] req->buffer;
delete req;
}
return nullptr;
}
/* Internal helper to setup the voice channel port. */
void V24UDPPort::createVCPort(uint16_t port)
{
m_socket = new Socket(port);
if (udp::Socket::lookup(m_address, port, m_addr, m_addrLen) != 0)
m_addrLen = 0U;
if (m_addrLen > 0U) {
std::string addrStr = udp::Socket::address(m_addr);
LogWarning(LOG_HOST, "SECURITY: Remote modem expects V.24 voice channel IP address; %s for remote modem control", addrStr.c_str());
}
}
/* Internal helper to write a FSC connect packet. */
void V24UDPPort::writeConnect()
{
FSCConnect connect = FSCConnect();
connect.setFSHeartbeatPeriod(5U); // hardcoded?
connect.setHostHeartbeatPeriod(5U); // hardcoded?
connect.setVCBasePort(m_localPort);
connect.setVCSSRC(m_peerId);
uint8_t buffer[FSCConnect::LENGTH];
::memset(buffer, 0x00U, FSCConnect::LENGTH);
connect.encode(buffer);
m_ctrlFrameQueue->write(buffer, FSCConnect::LENGTH, m_controlAddr, m_ctrlAddrLen);
}
/* Internal helper to write a FSC heartbeat packet. */
void V24UDPPort::writeHeartbeat()
{
uint8_t buffer[FSCHeartbeat::LENGTH];
::memset(buffer, 0x00U, FSCHeartbeat::LENGTH);
FSCHeartbeat hb = FSCHeartbeat();
hb.encode(buffer);
m_ctrlFrameQueue->write(buffer, FSCHeartbeat::LENGTH, m_controlAddr, m_ctrlAddrLen);
}
/* Generate RTP message. */
uint8_t* V24UDPPort::generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId,
uint32_t ssrc, uint16_t rtpSeq, uint32_t* outBufferLen)
{
assert(message != nullptr);
assert(length > 0U);
uint32_t timestamp = m_timestamp;
if (timestamp != INVALID_TS) {
timestamp += (RTP_GENERIC_CLOCK_RATE / 133);
if (m_debug)
LogDebug(LOG_NET, "V24UDPPort::generateMessage() RTP streamId = %u, previous TS = %u, TS = %u, rtpSeq = %u", streamId, m_timestamp, timestamp, rtpSeq);
m_timestamp = timestamp;
}
uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + length;
uint8_t* buffer = new uint8_t[bufferLen];
::memset(buffer, 0x00U, bufferLen);
RTPHeader header = RTPHeader();
header.setExtension(false);
header.setPayloadType(DFSI_RTP_PAYLOAD_TYPE);
header.setTimestamp(timestamp);
header.setSequence(rtpSeq);
header.setSSRC(ssrc);
header.encode(buffer);
if (streamId != 0U && timestamp == INVALID_TS && rtpSeq != RTP_END_OF_CALL_SEQ) {
if (m_debug)
LogDebug(LOG_NET, "V24UDPPort::generateMessage() RTP streamId = %u, initial TS = %u, rtpSeq = %u", streamId, header.getTimestamp(), rtpSeq);
m_timestamp = header.getTimestamp();
}
if (streamId != 0U && rtpSeq == RTP_END_OF_CALL_SEQ) {
m_timestamp = INVALID_TS;
if (m_debug)
LogDebug(LOG_NET, "V24UDPPort::generateMessage() RTP streamId = %u, rtpSeq = %u", streamId, rtpSeq);
}
::memcpy(buffer + RTP_HEADER_LENGTH_BYTES, message, length);
if (m_debug)
Utils::dump(1U, "V24UDPPort::generateMessage() Buffered Message", buffer, bufferLen);
if (outBufferLen != nullptr) {
*outBufferLen = bufferLen;
}
return buffer;
}
/* Helper to return a faked modem version. */
void V24UDPPort::getVersion()
{
uint8_t reply[200U];
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = 0U;
reply[2U] = CMD_GET_VERSION;
reply[3U] = V24_UDP_PROTOCOL_VERSION;
reply[4U] = 15U;
// Reserve 16 bytes for the UDID
::memset(reply + 5U, 0x00U, 16U);
uint8_t count = 21U;
for (uint8_t i = 0U; V24_UDP_HARDWARE[i] != 0x00U; i++, count++)
reply[count] = V24_UDP_HARDWARE[i];
reply[1U] = count;
m_buffer.addData(reply, count);
}
/* Helper to return a faked modem status. */
void V24UDPPort::getStatus()
{
uint8_t reply[15U];
// Send all sorts of interesting internal values
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = 12U;
reply[2U] = CMD_GET_STATUS;
reply[3U] = 0x00U;
reply[3U] |= 0x08U; // P25 enable flag
reply[4U] = uint8_t(m_modemState);
reply[5U] = m_tx ? 0x01U : 0x00U;
reply[6U] = 0U;
reply[7U] = 0U;
reply[8U] = 0U;
reply[9U] = 0U;
reply[10U] = 5U; // always report 5 P25 frames worth of space
reply[11U] = 0U;
m_buffer.addData(reply, 12U);
}
/* Helper to write a faked modem acknowledge. */
void V24UDPPort::writeAck(uint8_t type)
{
uint8_t reply[4U];
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = 4U;
reply[2U] = CMD_ACK;
reply[3U] = type;
m_buffer.addData(reply, 4U);
}
/* Helper to write a faked modem negative acknowledge. */
void V24UDPPort::writeNAK(uint8_t opcode, uint8_t err)
{
uint8_t reply[5U];
reply[0U] = DVM_SHORT_FRAME_START;
reply[1U] = 5U;
reply[2U] = CMD_NAK;
reply[3U] = opcode;
reply[4U] = err;
m_buffer.addData(reply, 5U);
}

@ -0,0 +1,229 @@
// 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.
*
* @package DVM / Modem Host Software
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file V24UDPPort.h
* @ingroup port
* @file V24UDPPort.cpp
* @ingroup port
*/
#if !defined(__V24_UDP_PORT_H__)
#define __V24_UDP_PORT_H__
#include "Defines.h"
#include "common/network/udp/Socket.h"
#include "common/network/RawFrameQueue.h"
#include "common/network/RTPHeader.h"
#include "common/RingBuffer.h"
#include "common/Timer.h"
#include "common/Thread.h"
#include "modem/port/IModemPort.h"
#include <string>
#include <random>
namespace modem
{
namespace port
{
namespace specialized
{
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents the data required for a V.24 network packet handler thread.
* @ingroup fne_network
*/
struct V24PacketRequest : thread_t {
sockaddr_storage address; //! IP Address and Port.
uint32_t addrLen; //!
network::frame::RTPHeader rtpHeader; //! RTP Header
int length = 0U; //! Length of raw data buffer
uint8_t *buffer; //! Raw data buffer
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements low-level routines to communicate over UDP for V.24.
* @ingroup port
*/
class HOST_SW_API V24UDPPort : public IModemPort {
public:
/**
* @brief Initializes a new instance of the V24UDPPort class.
* @param peerId Unique ID of this modem on the network.
* @param address Hostname/IP address to connect to.
* @param modemPort Port number.
* @param controlPort Control Port number.
* @param useFSC Flag indicating whether or not FSC handshakes are used to setup communications.
* @param debug Flag indicating whether network debug is enabled.
*/
V24UDPPort(uint32_t peerId, const std::string& modemAddress, uint16_t modemPort, uint16_t controlPort = 0U, bool useFSC = false, bool debug = false);
/**
* @brief Finalizes a instance of the V24UDPPort class.
*/
~V24UDPPort() override;
/**
* @brief Updates the timer by the passed number of milliseconds.
* @param ms Number of milliseconds.
*/
void clock(uint32_t ms);
/**
* @brief Resets the RTP packet sequence and stream ID.
*/
void reset();
/**
* @brief Opens a connection to the serial port.
* @returns bool True, if connection is opened, otherwise false.
*/
bool open() override;
/**
* @brief Reads data from the serial port.
* @param[out] buffer Buffer to read data from the port to.
* @param length Length of data to read from the port.
* @returns int Actual length of data read from serial port.
*/
int read(uint8_t* buffer, uint32_t length) override;
/**
* @brief Writes data to the serial port.
* @param[in] buffer Buffer containing data to write to port.
* @param length Length of data to write to port.
* @returns int Actual length of data written to the port.
*/
int write(const uint8_t* buffer, uint32_t length) override;
/**
* @brief Closes the connection to the serial port.
*/
void close() override;
private:
network::udp::Socket* m_socket;
uint16_t m_localPort;
network::udp::Socket* m_controlSocket;
network::RawFrameQueue* m_ctrlFrameQueue;
std::string m_address;
sockaddr_storage m_addr;
sockaddr_storage m_controlAddr;
uint32_t m_addrLen;
uint32_t m_ctrlAddrLen;
RingBuffer<uint8_t> m_buffer;
Timer m_reqConnectionTimer;
Timer m_heartbeatTimer;
bool m_reqConnectionToPeer;
bool m_establishedConnection;
std::mt19937 m_random;
uint32_t m_peerId;
uint32_t m_streamId;
uint32_t m_timestamp;
uint16_t m_pktSeq;
uint8_t m_modemState;
bool m_tx;
bool m_debug;
/**
* @brief Process FSC control frames from the network.
*/
void processCtrlNetwork();
/**
* @brief Entry point to process a given network packet.
* @param arg Instance of the NetPacketRequest structure.
* @returns void* (Ignore)
*/
static void* threadedCtrlNetworkRx(void* arg);
/**
* @brief Internal helper to setup the voice channel port.
* @param port Port number.
*/
void createVCPort(uint16_t port);
/**
* @brief Internal helper to write a FSC connect packet.
*/
void writeConnect();
/**
* @brief Internal helper to write a FSC heartbeat packet.
*/
void writeHeartbeat();
/**
* @brief Helper to update the RTP packet sequence.
* @param reset Flag indicating the current RTP packet sequence value should be reset.
* @returns uint16_t RTP packet sequence.
*/
uint16_t pktSeq(bool reset = false);
/**
* @brief Generates a new stream ID.
* @returns uint32_t New stream ID.
*/
uint32_t createStreamId() { std::uniform_int_distribution<uint32_t> dist(0x00000001, 0xfffffffe); return dist(m_random); }
/**
* @brief Generate RTP message.
* @param[in] message Message buffer to frame and queue.
* @param length Length of message.
* @param streamId Message stream ID.
* @param ssrc RTP SSRC ID.
* @param rtpSeq RTP Sequence.
* @param[out] outBufferLen Length of buffer generated.
* @returns uint8_t* Buffer containing RTP message.
*/
uint8_t* generateMessage(const uint8_t* message, uint32_t length, uint32_t streamId,
uint32_t ssrc, uint16_t rtpSeq, uint32_t* outBufferLen);
/**
* @brief Helper to return a faked modem version.
*/
void getVersion();
/**
* @brief Helper to return a faked modem status.
*/
void getStatus();
/**
* @brief Helper to write a faked modem acknowledge.
* @param type
*/
void writeAck(uint8_t type);
/**
* @brief Helper to write a faked modem negative acknowledge.
* @param opcode
* @param err
*/
void writeNAK(uint8_t opcode, uint8_t err);
};
} // namespace specialized
} // namespace port
} // namespace modem
#endif // __V24_UDP_PORT_H__

@ -22,6 +22,12 @@ using namespace network;
#include <cstdio>
#include <cassert>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define MAX_SERVER_DIFF 250 // maximum difference in time between a server timestamp and local timestamp in milliseconds
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
@ -193,6 +199,8 @@ void Network::clock(uint32_t ms)
return;
}
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// roll the RTP timestamp if no call is in progress
if ((m_status == NET_STAT_RUNNING) &&
(m_rxDMRStreamId[0U] == 0U && m_rxDMRStreamId[1U] == 0U) &&
@ -580,6 +588,24 @@ void Network::clock(uint32_t ms)
break;
case NET_FUNC::PONG: // Master Ping Response
m_timeoutTimer.start();
if (length >= 14) {
ulong64_t serverNow = 0U;
// combine bytes into ulong64_t (8 byte) value
serverNow = buffer[6U];
serverNow = (serverNow << 8) + buffer[7U];
serverNow = (serverNow << 8) + buffer[8U];
serverNow = (serverNow << 8) + buffer[9U];
serverNow = (serverNow << 8) + buffer[10U];
serverNow = (serverNow << 8) + buffer[11U];
serverNow = (serverNow << 8) + buffer[12U];
serverNow = (serverNow << 8) + buffer[13U];
// check the ping RTT and report any over the maximum defined time
uint64_t dt = now - serverNow;
if (dt > MAX_SERVER_DIFF)
LogWarning(LOG_NET, "PEER %u pong, time delay greater than %ums, now = %u, server = %u, dt = %u", m_peerId, MAX_SERVER_DIFF, now, serverNow, dt);
}
break;
default:
Utils::dump("unknown opcode from the master", buffer.get(), length);

@ -17,6 +17,7 @@
#include "common/Log.h"
#include "common/Utils.h"
#include "p25/Control.h"
#include "modem/ModemV24.h"
#include "remote/RESTClient.h"
#include "ActivityLog.h"
#include "HostMain.h"
@ -55,6 +56,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_txNAC(nac),
m_timeout(timeout),
m_modem(modem),
m_isModemDFSI(false),
m_network(network),
m_inhibitUnauth(false),
m_legacyGroupGrnt(true),
@ -126,6 +128,11 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
assert(idenTable != nullptr);
assert(rssiMapper != nullptr);
// bryanb: this is a hacky check to see if the modem is a ModemV24 or not...
modem::ModemV24* modemV24 = dynamic_cast<modem::ModemV24*>(modem);
if (modemV24 != nullptr)
m_isModemDFSI = true;
m_interval.start();
acl::AccessControl::init(m_ridLookup, m_tidLookup);
@ -282,6 +289,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
m_ackTSBKRequests = control["ackRequests"].as<bool>(true);
m_control->m_ctrlTSDUMBF = !control["disableTSDUMBF"].as<bool>(false);
if (m_isModemDFSI)
m_control->m_ctrlTSDUMBF = false; // force single block TSDUs for DFSI mode
m_control->m_ctrlTimeDateAnn = control["enableTimeDateAnn"].as<bool>(false);
m_control->m_redundantImmediate = control["redundantImmediate"].as<bool>(true);
m_control->m_redundantGrant = control["redundantGrantTransmit"].as<bool>(false);
@ -1631,6 +1640,9 @@ void Control::writeRF_Preamble(uint32_t preambleCount, bool force)
if (!m_duplex && !force)
return;
if (m_isModemDFSI)
return;
if (preambleCount == 0) {
preambleCount = m_tduPreambleCount;
}

@ -283,6 +283,7 @@ namespace p25
uint32_t m_timeout;
modem::Modem* m_modem;
bool m_isModemDFSI;
network::Network* m_network;
bool m_inhibitUnauth;

@ -513,7 +513,7 @@ bool Voice::process(uint8_t* data, uint32_t len)
writeNetwork(buffer, DUID::HDU);
if (m_p25->m_duplex) {
if (m_p25->m_duplex && !m_p25->m_isModemDFSI) {
buffer[0U] = modem::TAG_DATA;
buffer[1U] = 0x00U;
@ -735,7 +735,7 @@ bool Voice::process(uint8_t* data, uint32_t len)
writeNetwork(data + 2U, DUID::LDU1, frameType);
if (m_p25->m_duplex) {
if (m_p25->m_duplex && !m_p25->m_isModemDFSI) {
data[0U] = modem::TAG_DATA;
data[1U] = 0x00U;
@ -852,7 +852,7 @@ bool Voice::process(uint8_t* data, uint32_t len)
writeNetwork(data + 2U, DUID::LDU2);
if (m_p25->m_duplex) {
if (m_p25->m_duplex && !m_p25->m_isModemDFSI) {
data[0U] = modem::TAG_DATA;
data[1U] = 0x00U;

Loading…
Cancel
Save

Powered by TurnKey Linux.