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
parent
2c640a53c4
commit
617c889d1a
@ -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__
|
||||
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__
|
||||
Loading…
Reference in new issue