// SPDX-License-Identifier: GPL-2.0-only /* * Digital Voice Modem - Modem Host Software * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** * @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 Helper to set the TIA-102 format DFSI frame flag. * @param set */ void setTIAFormat(bool set); /** * @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 Helper to test if the P25 ring buffer has free space. * @returns bool True, if the P25 ring buffer has free space, otherwise false. */ bool hasP25Space(uint32_t length) const 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; uint8_t m_superFrameCnt; p25::Audio m_audio; p25::NID* m_nid; RingBuffer 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; bool m_useTIAFormat; /** * @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 Internal helper to convert from TIA-102 DFSI to TIA-102 air interface. * @param data Buffer containing data to convert. * @param length Length of buffer. */ void convertToAirTIA(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 Helper to generate the NID value. * @param duid P25 DUID. * @returns uint16_t P25 NID. */ uint16_t generateNID(P25DEF::DUID::E duid = P25DEF::DUID::LDU1); /** * @brief Send a start of stream sequence (HDU, etc) to the connected UDP TIA-102 device. * @param[in] control Instance of p25::lc::LC containing link control data. */ void startOfStreamTIA(const p25::lc::LC& control); /** * @brief Send an end of stream sequence (TDU, etc) to the connected UDP TIA-102 device. */ void endOfStreamTIA(); /** * @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); /** * @brief Internal helper to convert from TIA-102 air interface to TIA-102 DFSI. * @param data Buffer containing data to convert. * @param length Length of buffer. */ void convertFromAirTIA(uint8_t* data, uint32_t length); }; } // namespace modem #endif // __MODEM_V24_H__