// 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/data/DataHeader.h" #include "common/p25/data/DataBlock.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_START_STOP, //!< Start/Stop Signalling Frame STT_START_STOP_NO_JITTER, //!< Start/Stop Signalling Frame with Jitter Disabled STT_DATA, //!< Paced Data/Signalling Frame or IMBE Voice Frame STT_DATA_FAST //!< Fast Paced Data/Signalling 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), LDULC(nullptr), seqNo(0U), n(0U), netLDU1(nullptr), netLDU2(nullptr), pduUserData(nullptr), dataHeader(nullptr), dataCall(false), pduUserDataOffset(0U), pduTotalBlocks(0U), errors(0U) { MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; VHDR1 = new uint8_t[P25DFSIDEF::DFSI_MOT_VHDR_1_LEN]; VHDR2 = new uint8_t[P25DFSIDEF::DFSI_MOT_VHDR_2_LEN]; LDULC = new uint8_t[P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES]; netLDU1 = new uint8_t[9U * 25U]; netLDU2 = new uint8_t[9U * 25U]; ::memset(netLDU1, 0x00U, 9U * 25U); ::memset(netLDU2, 0x00U, 9U * 25U); pduUserData = new uint8_t[P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); resetCallData(); } /** * @brief Finalizes a instance of the DFSICallData class. */ ~DFSICallData() { if (MI != nullptr) { delete[] MI; MI = nullptr; } if (VHDR1 != nullptr) { delete[] VHDR1; VHDR1 = nullptr; } if (VHDR2 != nullptr) { delete[] VHDR2; VHDR2 = nullptr; } if (LDULC != nullptr) { delete[] LDULC; LDULC = nullptr; } if (netLDU1 != nullptr) { delete[] netLDU1; netLDU1 = nullptr; } if (netLDU2 != nullptr) { delete[] netLDU2; netLDU2 = nullptr; } if (pduUserData != nullptr) { delete[] pduUserData; pduUserData = nullptr; } if (dataHeader != nullptr) { delete dataHeader; dataHeader = nullptr; } } /** * @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, P25DFSIDEF::DFSI_MOT_VHDR_1_LEN); if (VHDR2 != nullptr) ::memset(VHDR2, 0x00U, P25DFSIDEF::DFSI_MOT_VHDR_2_LEN); if (LDULC != nullptr) ::memset(LDULC, 0x00U, P25DEF::P25_LDU_LC_FEC_LENGTH_BYTES); if (netLDU1 != nullptr) ::memset(netLDU1, 0x00U, 9U * 25U); if (netLDU2 != nullptr) ::memset(netLDU2, 0x00U, 9U * 25U); n = 0U; seqNo = 0U; if (pduUserData != nullptr) ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); if (dataHeader != nullptr) dataHeader->reset(); dataCall = false; pduUserDataOffset = 0U; pduTotalBlocks = 0U; errors = 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 LDU LC. */ uint8_t* LDULC; /** * @brief Sequence Number. */ uint32_t seqNo; /** * @brief */ uint8_t n; /** * @brief LDU1 Buffer. */ uint8_t* netLDU1; /** * @brief LDU2 Buffer. */ uint8_t* netLDU2; /** * @brief User data associated with this call. */ uint8_t* pduUserData; /** * @brief Data call header. */ p25::data::DataHeader* dataHeader; /** * @brief Flag indicating the current call is a data call. */ bool dataCall; /** * @brief Offset index when populating the user data buffer. */ uint32_t pduUserDataOffset; /** * @brief Total count of PDU blocks. */ uint32_t pduTotalBlocks; /** * @brief Total errors for a given call sequence. */ uint32_t errors; /** @} */ }; // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- /** * @brief Implements the core interface to the V.24 modem hardware. * \code{.unparsed} * This is the format of the Motorola V.24 DFSI Voice Headers: * * Voice Header 1 (VHDR1): * Bit 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+ * | FT | 0 * +-+-+-+-+-+-+-+-+ * | Start of Strm | 1 - 9 * +-+-+-+-+-+-+-+-+ * |S0 | G0 | 10 * +-+-+-+-+-+-+-+-+ * |S1 | G1 | 11 * +-+-+-+-+-+-+-+-+ * |S2 | G2 | 12 * +-+-+-+-+-+-+-+-+ * |S3 | G3 | 13 * +-+-+-+-+-+-+-+-+ * |S4 | G4 | 14 * +-+-+-+-+-+-+-+-+ * |S5 | G5 | 15 * +-+-+-+-+-+-+-+-+ * |S6 | G6 | 16 * +-+-+-+-+-+-+-+-+ * |S7 | G7 | 17 * +-+-+-+-+-+-+-+-+ * | S7...S0 | 18 * +-+-+-+-+-+-+-+-+ * |S8 | G8 | 19 * +-+-+-+-+-+-+-+-+ * |S9 | G9 | 20 * +-+-+-+-+-+-+-+-+ * |S10| G10 | 21 * +-+-+-+-+-+-+-+-+ * |S11| G11 | 22 * +-+-+-+-+-+-+-+-+ * |S12| G12 | 23 * +-+-+-+-+-+-+-+-+ * |S13| G13 | 24 * +-+-+-+-+-+-+-+-+ * |S14| G14 | 25 * +-+-+-+-+-+-+-+-+ * |S15| G15 | 26 * +-+-+-+-+-+-+-+-+ * | S15...S8 | 27 * +-+-+-+-+-+-+-+-+ * |S16| G16 | 26 * +-+-+-+-+-+-+-+-+ * |S17| G17 | 28 * +-+-+-+-+-+-+-+-+ * |7|6| Rsvd |Bsy| 29 * +-+-+-+-+-+-+-+-+ * | Rsvd | 30 * +-+-+-+-+-+-+-+-+ * * Voice Header 2 (VHDR2): * Bit 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+ * | FT | 0 * +-+-+-+-+-+-+-+-+ * |S18| G18 | 1 * +-+-+-+-+-+-+-+-+ * |S19| G19 | 2 * +-+-+-+-+-+-+-+-+ * |S20| G20 | 3 * +-+-+-+-+-+-+-+-+ * |S21| G21 | 4 * +-+-+-+-+-+-+-+-+ * |S22| G22 | 5 * +-+-+-+-+-+-+-+-+ * |S23| G23 | 6 * +-+-+-+-+-+-+-+-+ * |S24| G24 | 7 * +-+-+-+-+-+-+-+-+ * |S25| G25 | 8 * +-+-+-+-+-+-+-+-+ * | S25...S18 | 9 * +-+-+-+-+-+-+-+-+ * |S26| G26 | 10 * +-+-+-+-+-+-+-+-+ * |S27| G27 | 11 * +-+-+-+-+-+-+-+-+ * |S28| G28 | 12 * +-+-+-+-+-+-+-+-+ * |S29| G29 | 13 * +-+-+-+-+-+-+-+-+ * |S30| G30 | 14 * +-+-+-+-+-+-+-+-+ * |S31| G31 | 15 * +-+-+-+-+-+-+-+-+ * |S32| G32 | 16 * +-+-+-+-+-+-+-+-+ * |S33| G33 | 17 * +-+-+-+-+-+-+-+-+ * | S33...S26 | 18 * +-+-+-+-+-+-+-+-+ * |S34| G34 | 19 * +-+-+-+-+-+-+-+-+ * |S35| G35 | 20 * +-+-+-+-+-+-+-+-+ * |5|4| Rsvd |Bsy| 21 * +-+-+-+-+-+-+-+-+ * * This is the format of the TIA-102.BAHA DFSI Voice Headers: * * TIA-102.BAHA Voice Header 1 (VHDR1): * Bit 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+ * | FT | 0 * +-+-+-+-+-+-+-+-+ * |S0 | G0 | 1 * +-+-+-+-+-+-+-+-+ * |S1 | G1 | 2 * +-+-+-+-+-+-+-+-+ * |S2 | G2 | 3 * +-+-+-+-+-+-+-+-+ * |S3 | G3 | 4 * +-+-+-+-+-+-+-+-+ * |S4 | G4 | 5 * +-+-+-+-+-+-+-+-+ * |S5 | G5 | 6 * +-+-+-+-+-+-+-+-+ * |S6 | G6 | 7 * +-+-+-+-+-+-+-+-+ * |S7 | G7 | 8 * +-+-+-+-+-+-+-+-+ * |S8 | G8 | 9 * +-+-+-+-+-+-+-+-+ * |S9 | G9 | 10 * +-+-+-+-+-+-+-+-+ * |S10| G10 | 11 * +-+-+-+-+-+-+-+-+ * |S11| G11 | 12 * +-+-+-+-+-+-+-+-+ * |S12| G12 | 13 * +-+-+-+-+-+-+-+-+ * |S13| G13 | 14 * +-+-+-+-+-+-+-+-+ * |S14| G14 | 15 * +-+-+-+-+-+-+-+-+ * |S15| G15 | 16 * +-+-+-+-+-+-+-+-+ * |S16| G16 | 17 * +-+-+-+-+-+-+-+-+ * |S17| G17 | 18 * +-+-+-+-+-+-+-+-+ * | Rsvd |7|6| 19 * +-+-+-+-+-+-+-+-+ * | S15...S0 | 20 * +-+-+-+-+-+-+-+-+ * | S15...S0 | 21 * +-+-+-+-+-+-+-+-+ * * TIA-102.BAHA Voice Header 2 (VHDR2): * Bit 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+ * | FT | 0 * +-+-+-+-+-+-+-+-+ * |S18| G18 | 1 * +-+-+-+-+-+-+-+-+ * |S19| G19 | 2 * +-+-+-+-+-+-+-+-+ * |S20| G20 | 3 * +-+-+-+-+-+-+-+-+ * |S21| G21 | 4 * +-+-+-+-+-+-+-+-+ * |S22| G22 | 5 * +-+-+-+-+-+-+-+-+ * |S23| G23 | 6 * +-+-+-+-+-+-+-+-+ * |S24| G24 | 7 * +-+-+-+-+-+-+-+-+ * |S25| G25 | 8 * +-+-+-+-+-+-+-+-+ * |S26| G26 | 9 * +-+-+-+-+-+-+-+-+ * |S27| G27 | 10 * +-+-+-+-+-+-+-+-+ * |S28| G28 | 11 * +-+-+-+-+-+-+-+-+ * |S29| G29 | 12 * +-+-+-+-+-+-+-+-+ * |S30| G30 | 13 * +-+-+-+-+-+-+-+-+ * |S31| G31 | 14 * +-+-+-+-+-+-+-+-+ * |S32| G32 | 15 * +-+-+-+-+-+-+-+-+ * |S33| G33 | 16 * +-+-+-+-+-+-+-+-+ * |S34| G34 | 17 * +-+-+-+-+-+-+-+-+ * |S35| G35 | 18 * +-+-+-+-+-+-+-+-+ * | Rsvd |5|4| 19 * +-+-+-+-+-+-+-+-+ * | S33...S18 | 20 * +-+-+-+-+-+-+-+-+ * | S33...S18 | 21 * +-+-+-+-+-+-+-+-+ * \endcode * @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 displayDebugMessages Flag indicating whether or not modem debug messages are displayed in 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, uint16_t jitter, bool dumpModemStatus, bool displayDebugMessages, 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. * @param imm Flag indicating whether the frame is immediate. * @returns int Actual length of data written. */ int write(const uint8_t* data, uint32_t length, bool imm = false) override; private: bool m_rtrt; uint8_t m_superFrameCnt; p25::Audio m_audio; p25::NID* m_nid; RingBuffer m_txP25Queue; RingBuffer m_txImmP25Queue; 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; std::mutex m_txP25QueueLock; /** * @brief Helper to write data from the P25 Tx queue to the serial interface. * @param[in] queue Pointer to the ring buffer containing data to write. * @return int Actual number of bytes written to the serial interface. */ int writeSerial(RingBuffer* queue); /** * @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 Internal helper to store converted PDU Rx frames. * @param dataHeader Instance of a PDU data header. * @param pduUserData Buffer containing user data to transmit. */ void storeConvertedRxPDU(p25::data::DataHeader* dataHeader, uint8_t* pduUserData); /** * @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 convertToAirV24(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). * @param imm Flag indicating whether the frame is immediate. * @returns bool True, if data is queued, otherwise false. */ bool queueP25Frame(uint8_t* data, uint16_t length, SERIAL_TX_TYPE msgType, bool imm = false); /** * @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 startOfStreamV24(const p25::lc::LC& control); /** * @brief Send an end of stream sequence (TDU, etc) to the connected serial V24 device. */ void endOfStreamV24(); /** * @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 Send a start of stream ACK. */ void ackStartOfStreamTIA(); /** * @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. * @param imm Flag indicating whether the frame is immediate. */ void convertFromAirV24(uint8_t* data, uint32_t length, bool imm); /** * @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. * @param imm Flag indicating whether the frame is immediate. */ void convertFromAirTIA(uint8_t* data, uint32_t length, bool imm); }; } // namespace modem #endif // __MODEM_V24_H__