From 3ba67a1506cf0cc0282bd70fdf73c728d4a079cb Mon Sep 17 00:00:00 2001 From: Dev_Ranger <30966416+DevRanger@users.noreply.github.com> Date: Fri, 6 Nov 2026 18:26:50 -0800 Subject: [PATCH] = Fix Bridge vocoder integration, framing, and buffer bounds - Fix half-rate encoder input overread - Bound NXDN FACCH1 and UDCH puncture-table access - Fix P25 receive buffer bounds - Correct NXDN transmit buffer strides and SACCH fragment assembly - Fix DMR sync, burst sequencing, call termination, and buffer cleanup - Add focused regression tests and correct static vocoder linkage - Rename packet-state enum value to avoid a Windows macro collision --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 1 + src/bridge/HostBridge.DMR.cpp | 11 +++- src/bridge/HostBridge.NXDN.cpp | 13 +++-- src/bridge/HostBridge.P25.cpp | 33 ++++------- src/bridge/HostBridge.cpp | 10 +++- src/bridge/HostBridge.h | 1 + src/bridge/network/PeerNetwork.cpp | 57 ++++++++----------- src/common/nxdn/channel/FACCH1.cpp | 4 +- src/common/nxdn/channel/UDCH.cpp | 4 +- .../callhandler/packetdata/P25PacketData.cpp | 2 +- .../packetdata/p25/PacketDataState.cpp | 2 +- .../packetdata/p25/PacketDataState.h | 2 +- src/vocoder/MBEEncoder.cpp | 7 +-- tests/dmr/MBEEncoder_Tests.cpp | 43 ++++++++++++++ tests/nxdn/FACCH1_Tests.cpp | 26 +++++++++ tests/nxdn/SACCH_Tests.cpp | 4 +- tests/p25/P25_PacketDataState_Tests.cpp | 4 +- 18 files changed, 148 insertions(+), 78 deletions(-) create mode 100644 tests/dmr/MBEEncoder_Tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8795f5ca..11582d5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -292,7 +292,7 @@ if (ENABLE_TESTS) list(FILTER dvmfnetest_SRC EXCLUDE REGEX "/HostFNE\\.cpp$") add_executable(dvmtests ${common_INCLUDE} ${dvmhost_SRC} ${dvmfnetest_SRC} ${dvmtests_SRC} src/remote/RESTClient.cpp) target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION) - target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) + target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) target_include_directories(dvmtests PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/fne tests) if (SHARED_LIBS AND NOT COMPILE_WIN32) set_target_properties(dvmtests PROPERTIES diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b2f215f2..9c6f5f83 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,7 @@ if (SHARED_LIBS) target_link_libraries(vocoder PRIVATE common) else () target_compile_definitions(vocoder PUBLIC VOCODER_STATIC) + target_link_libraries(vocoder PRIVATE common) endif (SHARED_LIBS) target_include_directories(vocoder PRIVATE src src/vocoder) diff --git a/src/bridge/HostBridge.DMR.cpp b/src/bridge/HostBridge.DMR.cpp index 7e2fbe28..bf7a0a06 100644 --- a/src/bridge/HostBridge.DMR.cpp +++ b/src/bridge/HostBridge.DMR.cpp @@ -17,6 +17,7 @@ #include "common/dmr/data/NetData.h" #include "common/dmr/lc/FullLC.h" #include "common/dmr/SlotType.h" +#include "common/dmr/Sync.h" #include "common/Log.h" #include "common/Utils.h" #include "bridge/ActivityLog.h" @@ -348,12 +349,13 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ srcId = m_srcId; uint8_t* data = nullptr; - m_dmrN = (uint8_t)(m_dmrSeqNo % 6); if (m_ambeCount == AMBE_PER_SLOT) { // is this the intitial sequence? if (m_dmrSeqNo == 0) { // send DMR voice header data = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + ::memset(data, 0x00U, DMR_FRAME_LENGTH_BYTES); + Sync::addDMRDataSync(data, true); // generate DMR LC lc::LC dmrLC = lc::LC(); @@ -408,6 +410,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ // send DMR voice data = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + ::memset(data, 0x00U, DMR_FRAME_LENGTH_BYTES); ::memcpy(data, m_ambeBuffer, 13U); data[13U] = (uint8_t)(m_ambeBuffer[13U] & 0xF0); @@ -415,8 +418,10 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ ::memcpy(data + 20U, m_ambeBuffer + 14U, 13U); DataType::E dataType = DataType::VOICE_SYNC; - if (m_dmrN == 0) + if (m_dmrN == 0) { dataType = DataType::VOICE_SYNC; + Sync::addDMRAudioSync(data, true); + } else { dataType = DataType::VOICE; @@ -446,9 +451,11 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ dmrData.setData(data); m_network->writeDMR(dmrData, false); + delete[] data; m_txStreamId = m_network->getDMRStreamId(m_slot); m_dmrSeqNo++; + m_dmrN = (m_dmrN + 1U) % 6U; ::memset(m_ambeBuffer, 0x00U, 27U); m_ambeCount = 0U; } diff --git a/src/bridge/HostBridge.NXDN.cpp b/src/bridge/HostBridge.NXDN.cpp index ca3c66fb..2705571e 100644 --- a/src/bridge/HostBridge.NXDN.cpp +++ b/src/bridge/HostBridge.NXDN.cpp @@ -198,6 +198,7 @@ void HostBridge::processNXDNNetwork(uint8_t* buffer, uint32_t length) m_nxdnCrypto->resetKeystream(); m_rxNXDNSACCHLC = lc::RTCH(); + ::memset(m_rxNXDNSACCHData, 0x00U, sizeof(m_rxNXDNSACCHData)); m_rxNXDNSACCHMask = 0U; m_rxNXDNPendingMI = false; @@ -230,13 +231,17 @@ void HostBridge::processNXDNNetwork(uint8_t* buffer, uint32_t length) uint8_t quarter = 3U - (uint8_t)voiceSACCH.getStructure(); if (quarter == 0U) { m_rxNXDNSACCHLC = lc::RTCH(); + ::memset(m_rxNXDNSACCHData, 0x00U, sizeof(m_rxNXDNSACCHData)); m_rxNXDNSACCHMask = 0U; } - m_rxNXDNSACCHLC.decode(fragment, 18U, quarter * 18U); + // RTCH::decode's offset addresses its input; assemble the fragments first. + for (uint32_t bit = 0U; bit < 18U; bit++) + WRITE_BIT(m_rxNXDNSACCHData, quarter * 18U + bit, READ_BIT(fragment, bit)); m_rxNXDNSACCHMask |= (uint8_t)(1U << quarter); if (quarter == 3U && m_rxNXDNSACCHMask == 0x0FU) { + m_rxNXDNSACCHLC.decode(m_rxNXDNSACCHData, 72U); uint8_t sacchType = m_rxNXDNSACCHLC.getMessageType(); if (sacchType == MessageType::RTCH_VCALL) { m_rxNXDNLC = m_rxNXDNSACCHLC; @@ -559,8 +564,8 @@ void HostBridge::encodeNXDNAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32 m_nxdnN = 0U; } - uint8_t* packedAMBE = m_nxdnAMBE + (m_nxdnN * dmr::defines::DMR_AMBE_LENGTH_BYTES); - ::memset(packedAMBE, 0x00U, dmr::defines::DMR_AMBE_LENGTH_BYTES); + uint8_t* packedAMBE = m_nxdnAMBE + (m_nxdnN * dmr::defines::RAW_AMBE_LENGTH_BYTES); + ::memset(packedAMBE, 0x00U, dmr::defines::RAW_AMBE_LENGTH_BYTES); for (uint32_t b = 0U; b < 49U; b++) WRITE_BIT(packedAMBE, b, rawBits[b] != 0U); m_nxdnN++; @@ -579,7 +584,7 @@ void HostBridge::encodeNXDNAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32 ::memset(packedBits, 0x00U, 13U); for (uint8_t half = 0U; half < 2U; half++) { - uint8_t* ambe = m_nxdnAMBE + ((pair * 2U + half) * dmr::defines::DMR_AMBE_LENGTH_BYTES); + uint8_t* ambe = m_nxdnAMBE + ((pair * 2U + half) * dmr::defines::RAW_AMBE_LENGTH_BYTES); for (uint32_t b = 0U; b < 49U; b++) { WRITE_BIT(packedBits, (half * 49U) + b, READ_BIT(ambe, b)); } diff --git a/src/bridge/HostBridge.P25.cpp b/src/bridge/HostBridge.P25.cpp index b503d62a..7b31af6e 100644 --- a/src/bridge/HostBridge.P25.cpp +++ b/src/bridge/HostBridge.P25.cpp @@ -64,6 +64,9 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) return; } + if (length < 24U) + return; + bool grantDemand = (buffer[14U] & network::NET_CTRL_GRANT_DEMAND) == network::NET_CTRL_GRANT_DEMAND; bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; @@ -75,26 +78,14 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) if (duid == DUID::HDU || duid == DUID::TSDU || duid == DUID::PDU) return; - // process raw P25 data bytes - UInt8Array data; - uint8_t frameLength = buffer[23U]; - if (duid == DUID::PDU) { - frameLength = length; - data = std::unique_ptr(new uint8_t[length]); - ::memset(data.get(), 0x00U, length); - ::memcpy(data.get(), buffer, length); - } - else { - if (frameLength <= 24) { - data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - } - else { - data = std::unique_ptr(new uint8_t[frameLength]); - ::memset(data.get(), 0x00U, frameLength); - ::memcpy(data.get(), buffer + 24U, frameLength); - } - } + // LDU counts include the 24-byte network header; terminators need no voice payload. + uint32_t frameLength = std::min(buffer[23U], length); + frameLength = frameLength > 24U ? frameLength - 24U : 0U; + if ((duid == DUID::LDU1 || duid == DUID::LDU2) && frameLength < 91U) + return; + UInt8Array data = std::make_unique(frameLength); + if (frameLength > 0U) + ::memcpy(data.get(), buffer + 24U, frameLength); // handle LDU, TDU or TSDU frame uint8_t lco = buffer[4U]; @@ -159,7 +150,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) m_callAlgoId = ALGO_UNENCRYPT; // if this is the beginning of a call and we have a valid HDU frame, extract the algo ID - uint8_t frameType = buffer[180U]; + uint8_t frameType = length >= (184U + MI_LENGTH_BYTES) ? buffer[180U] : FrameType::DATA_UNIT; if (frameType == FrameType::HDU_VALID) { m_callAlgoId = buffer[181U]; if (m_callAlgoId != ALGO_UNENCRYPT) { diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index b188ac36..1637bc2d 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -15,6 +15,7 @@ #include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" #include "common/dmr/DMRDefines.h" +#include "common/dmr/Sync.h" #include "common/dmr/data/EMB.h" #include "common/nxdn/NXDNDefines.h" #include "common/nxdn/NXDNUtils.h" @@ -244,6 +245,7 @@ HostBridge::HostBridge(const std::string& confFile) : m_p25N(0U), m_rxNXDNLC(), m_rxNXDNSACCHLC(), + m_rxNXDNSACCHData{}, m_rxNXDNSACCHMask(0U), m_rxNXDNPendingMI(false), m_rxNXDNNextMI(), @@ -2877,10 +2879,9 @@ void HostBridge::padSilenceAudio(uint32_t srcId, uint32_t dstId) using namespace dmr::defines; using namespace dmr::data; - m_dmrN = (uint8_t)(m_dmrSeqNo % 6); - // send DMR voice uint8_t data[DMR_FRAME_LENGTH_BYTES]; + ::memset(data, 0x00U, DMR_FRAME_LENGTH_BYTES); m_ambeCount = 0U; @@ -2897,8 +2898,10 @@ void HostBridge::padSilenceAudio(uint32_t srcId, uint32_t dstId) ::memcpy(data + 20U, m_ambeBuffer + 14U, 13U); DataType::E dataType = DataType::VOICE_SYNC; - if (m_dmrN == 0) + if (m_dmrN == 0) { dataType = DataType::VOICE_SYNC; + Sync::addDMRAudioSync(data, true); + } else { dataType = DataType::VOICE; @@ -2931,6 +2934,7 @@ void HostBridge::padSilenceAudio(uint32_t srcId, uint32_t dstId) m_txStreamId = m_network->getDMRStreamId(m_slot); m_dmrSeqNo++; + m_dmrN = (m_dmrN + 1U) % 6U; } break; case TX_MODE_P25: diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 463a2334..2a49e4df 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -266,6 +266,7 @@ private: nxdn::lc::RTCH m_rxNXDNLC; nxdn::lc::RTCH m_rxNXDNSACCHLC; + uint8_t m_rxNXDNSACCHData[9U]; uint8_t m_rxNXDNSACCHMask; bool m_rxNXDNPendingMI; uint8_t m_rxNXDNNextMI[nxdn::defines::MI_LENGTH_BYTES]; diff --git a/src/bridge/network/PeerNetwork.cpp b/src/bridge/network/PeerNetwork.cpp index 8332483f..afbdd9ad 100644 --- a/src/bridge/network/PeerNetwork.cpp +++ b/src/bridge/network/PeerNetwork.cpp @@ -11,6 +11,7 @@ #include "common/dmr/data/EMB.h" #include "common/dmr/lc/FullLC.h" #include "common/dmr/SlotType.h" +#include "common/dmr/Sync.h" #include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/LC.h" #include "common/json/json.h" @@ -89,36 +90,27 @@ void PeerNetwork::writeDMRTerminator(dmr::data::NetData& data, uint32_t* seqNo, using namespace dmr; using namespace dmr::defines; - uint8_t n = (uint8_t)((*seqNo - 3U) % 6U); - uint32_t fill = 6U - n; - - uint8_t* buffer = nullptr; - if (n > 0U) { - for (uint32_t i = 0U; i < fill; i++) { - // generate DMR AMBE data - buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; - ::memcpy(buffer, SILENCE_DATA, DMR_FRAME_LENGTH_BYTES); - - uint8_t lcss = embeddedData.getData(buffer, n); - - // generated embedded signalling - data::EMB emb = data::EMB(); - emb.setColorCode(0U); - emb.setLCSS(lcss); - emb.encode(buffer); - - // generate DMR network frame - data.setData(buffer); - - writeDMR(data); - - seqNo++; - dmrN++; - delete[] buffer; - } + // Complete the current six-burst superframe before sending the terminator. + uint8_t buffer[DMR_FRAME_LENGTH_BYTES]; + while (*dmrN != 0U) { + ::memcpy(buffer, SILENCE_DATA, DMR_FRAME_LENGTH_BYTES); + uint8_t lcss = embeddedData.getData(buffer, *dmrN); + data::EMB emb = data::EMB(); + emb.setColorCode(0U); + emb.setLCSS(lcss); + emb.encode(buffer); + + data.setDataType(DataType::VOICE); + data.setN(*dmrN); + data.setSeqNo(*seqNo); + data.setData(buffer); + writeDMR(data); + (*seqNo)++; + *dmrN = (*dmrN + 1U) % 6U; } - buffer = new uint8_t[DMR_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, DMR_FRAME_LENGTH_BYTES); + Sync::addDMRDataSync(buffer, true); // generate DMR LC lc::LC dmrLC = lc::LC(); @@ -134,13 +126,14 @@ void PeerNetwork::writeDMRTerminator(dmr::data::NetData& data, uint32_t* seqNo, lc::FullLC fullLC = lc::FullLC(); fullLC.encode(dmrLC, buffer, DataType::TERMINATOR_WITH_LC); - // generate DMR network frame + data.setDataType(DataType::TERMINATOR_WITH_LC); + data.setN(0U); + data.setSeqNo(*seqNo); data.setData(buffer); - writeDMR(data); - seqNo = 0; - dmrN = 0; + *seqNo = 0U; + *dmrN = 0U; } // --------------------------------------------------------------------------- diff --git a/src/common/nxdn/channel/FACCH1.cpp b/src/common/nxdn/channel/FACCH1.cpp index 881ded0d..c4324e70 100644 --- a/src/common/nxdn/channel/FACCH1.cpp +++ b/src/common/nxdn/channel/FACCH1.cpp @@ -106,7 +106,7 @@ bool FACCH1::decode(const uint8_t* data, uint32_t offset) uint8_t puncture[210U]; uint32_t n = 0U, index = 0U; for (uint32_t i = 0U; i < NXDN_FACCH1_FEC_LENGTH_BITS; i++) { - if (n == PUNCTURE_LIST[index]) { + if (index < (sizeof(PUNCTURE_LIST) / sizeof(PUNCTURE_LIST[0U])) && n == PUNCTURE_LIST[index]) { puncture[n++] = 1U; index++; } @@ -179,7 +179,7 @@ void FACCH1::encode(uint8_t* data, uint32_t offset) const uint32_t n = 0U, index = 0U; for (uint32_t i = 0U; i < NXDN_FACCH1_FEC_CONV_LENGTH_BITS; i++) { - if (i != PUNCTURE_LIST[index]) { + if (index >= (sizeof(PUNCTURE_LIST) / sizeof(PUNCTURE_LIST[0U])) || i != PUNCTURE_LIST[index]) { bool b = READ_BIT(convolution, i); WRITE_BIT(puncture, n, b); n++; diff --git a/src/common/nxdn/channel/UDCH.cpp b/src/common/nxdn/channel/UDCH.cpp index f596638f..dbd28b10 100644 --- a/src/common/nxdn/channel/UDCH.cpp +++ b/src/common/nxdn/channel/UDCH.cpp @@ -130,7 +130,7 @@ bool UDCH::decode(const uint8_t* data) uint8_t puncture[420U]; uint32_t n = 0U, index = 0U; for (uint32_t i = 0U; i < NXDN_UDCH_FEC_LENGTH_BITS; i++) { - if (n == PUNCTURE_LIST[index]) { + if (index < (sizeof(PUNCTURE_LIST) / sizeof(PUNCTURE_LIST[0U])) && n == PUNCTURE_LIST[index]) { puncture[n++] = 1U; index++; } @@ -207,7 +207,7 @@ void UDCH::encode(uint8_t* data) const uint32_t n = 0U, index = 0U; for (uint32_t i = 0U; i < NXDN_UDCH_FEC_CONV_LENGTH_BITS; i++) { - if (i != PUNCTURE_LIST[index]) { + if (index >= (sizeof(PUNCTURE_LIST) / sizeof(PUNCTURE_LIST[0U])) || i != PUNCTURE_LIST[index]) { bool b = READ_BIT(convolution, i); WRITE_BIT(puncture, n, b); n++; diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index c8f30b61..80c3c9f3 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -810,7 +810,7 @@ void P25PacketData::dispatch(uint32_t peerId) ReceiveSequenceResult sequenceResult = m_dataLinkManager.acceptReceiveSequence(srcLlId, receivedNs, synchronize, packetFingerprint, expectedNs); - if (sequenceResult == ReceiveSequenceResult::DUPLICATE) { + if (sequenceResult == ReceiveSequenceResult::DUPLICATE_FRAME) { // re-ACK a duplicate, but never deliver the IP datagram twice if (status->assembler.getExtendedAddress()) { write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, receivedNs, diff --git a/src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp b/src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp index 86b34f0a..10578871 100644 --- a/src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp +++ b/src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp @@ -103,7 +103,7 @@ ReceiveSequenceResult DataLinkManager::acceptReceiveSequence(uint32_t llId, uint } if (sequence == state.receiveSequence && packetFingerprint == state.receiveFingerprint) - return ReceiveSequenceResult::DUPLICATE; + return ReceiveSequenceResult::DUPLICATE_FRAME; // BAED permits gaps: record the newly received N(S), allowing the upper // layer to observe that intervening logical packets were lost diff --git a/src/fne/network/callhandler/packetdata/p25/PacketDataState.h b/src/fne/network/callhandler/packetdata/p25/PacketDataState.h index 7f402542..cc8d896a 100644 --- a/src/fne/network/callhandler/packetdata/p25/PacketDataState.h +++ b/src/fne/network/callhandler/packetdata/p25/PacketDataState.h @@ -71,7 +71,7 @@ namespace network */ enum class ReceiveSequenceResult : uint8_t { ACCEPTED, //!< Accepted receive sequence result - DUPLICATE, //!< Duplicate receive sequence result + DUPLICATE_FRAME, //!< Duplicate receive sequence result OUT_OF_SEQUENCE //!< Out of sequence receive sequence result }; /* @} */ diff --git a/src/vocoder/MBEEncoder.cpp b/src/vocoder/MBEEncoder.cpp index 5537db81..c24f95d3 100644 --- a/src/vocoder/MBEEncoder.cpp +++ b/src/vocoder/MBEEncoder.cpp @@ -604,10 +604,9 @@ void MBEEncoder::encodeBits(uint8_t* bits, uint8_t* codeword) uint8_t rawAmbe[9U]; ::memset(rawAmbe, 0x00U, 9U); - for (int i = 0; i < 9; ++i) { - for (int j = 0; j < 8; ++j) { - rawAmbe[i] |= (bits[(i * 8) + j] << (7 - j)); - } + // Only 49 input entries are provided; the remaining packed bits stay zero. + for (uint32_t i = 0U; i < 49U; i++) { + WRITE_BIT(rawAmbe, i, (bits[i] & 1U) != 0U); } // build DMR AMBE bytes diff --git a/tests/dmr/MBEEncoder_Tests.cpp b/tests/dmr/MBEEncoder_Tests.cpp new file mode 100644 index 00000000..dcc82902 --- /dev/null +++ b/tests/dmr/MBEEncoder_Tests.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "vocoder/MBEEncoder.h" +#include "vocoder/MBEDecoder.h" + +#include +#include +#include + +TEST_CASE("Half-rate FEC consumes exactly 49 source bits", "[dmr][vocoder][bounds]") { + vocoder::MBEEncoder encoder(vocoder::ENCODE_DMR_AMBE); + vocoder::MBEDecoder decoder(vocoder::DECODE_DMR_AMBE); + + // Independent vectors from Console's existing native FEC runtime, with a zero-padded input. + const uint8_t known[2U][9U] = { + {0x22U, 0x00U, 0x02U, 0x04U, 0x02U, 0x20U, 0x40U, 0x44U, 0x00U}, + {0xDDU, 0xDDU, 0xFFU, 0xFFU, 0xDFU, 0xBFU, 0xBBU, 0xFFU, 0xBBU} + }; + for (uint32_t pattern = 0U; pattern < 51U; pattern++) { + // An exact heap allocation makes a one-past-end read visible to ASAN. + auto bits = std::make_unique(49U); + for (uint32_t i = 0U; i < 49U; i++) + bits[i] = pattern == 1U || (pattern >= 2U && i == pattern - 2U) ? 1U : 0U; + uint8_t output[11U] = {}; + output[0U] = output[10U] = 0xA5U; + encoder.encodeBits(bits.get(), output + 1U); + REQUIRE(output[0U] == 0xA5U); + REQUIRE(output[10U] == 0xA5U); + if (pattern < 2U) + REQUIRE(::memcmp(output + 1U, known[pattern], 9U) == 0); + char decoded[49U] = {}; + REQUIRE(decoder.decodeBits(output + 1U, decoded) == 0); + for (uint32_t i = 0U; i < 49U; i++) + REQUIRE(decoded[i] == bits[i]); + + // Data beyond the API's 49 source entries must not change the last valid bit. + uint8_t padded[72U]; + ::memset(padded, 0xFFU, sizeof(padded)); + ::memcpy(padded, bits.get(), 49U); + uint8_t repeated[9U]; + encoder.encodeBits(padded, repeated); + REQUIRE(::memcmp(output + 1U, repeated, 9U) == 0); + } +} diff --git a/tests/nxdn/FACCH1_Tests.cpp b/tests/nxdn/FACCH1_Tests.cpp index 604f3760..250b121a 100644 --- a/tests/nxdn/FACCH1_Tests.cpp +++ b/tests/nxdn/FACCH1_Tests.cpp @@ -189,3 +189,29 @@ TEST_CASE("FACCH1 golden test for voice call header", "[nxdn][facch1][golden]") decoded.getData(dataOut); REQUIRE(::memcmp(dataIn, dataOut, 10U) == 0); } + +TEST_CASE("FACCH1 matches independently encoded Console header", "[nxdn][facch1][bounds]") { + // Clear group VCALL, source 1234, destination 5678; Console's managed channel coder. + const uint8_t raw[10U] = {0x01U, 0x00U, 0x20U, 0x04U, 0xD2U, 0x16U, 0x2EU, 0x00U, 0x00U, 0x00U}; + const uint8_t encoded[18U] = { + 0x48U, 0xB4U, 0x05U, 0x01U, 0x00U, 0x08U, 0x00U, 0x30U, 0x38U, + 0x08U, 0x80U, 0xA6U, 0x5CU, 0x02U, 0x02U, 0xCAU, 0x23U, 0xE1U + }; + FACCH1 tx; + tx.setData(raw); + for (uint32_t offset : {96U, 240U}) { + uint8_t frame[NXDN_FRAME_LENGTH_BYTES]; + ::memset(frame, 0xA5U, sizeof(frame)); + tx.encode(frame, offset); + REQUIRE(::memcmp(frame + offset / 8U, encoded, sizeof(encoded)) == 0); + for (uint32_t i = 0U; i < sizeof(frame); i++) { + if (i < offset / 8U || i >= offset / 8U + sizeof(encoded)) + REQUIRE(frame[i] == 0xA5U); + } + FACCH1 rx; + REQUIRE(rx.decode(frame, offset)); + uint8_t decoded[10U]; + rx.getData(decoded); + REQUIRE(::memcmp(decoded, raw, sizeof(raw)) == 0); + } +} diff --git a/tests/nxdn/SACCH_Tests.cpp b/tests/nxdn/SACCH_Tests.cpp index 7e4f2801..9623820a 100644 --- a/tests/nxdn/SACCH_Tests.cpp +++ b/tests/nxdn/SACCH_Tests.cpp @@ -39,7 +39,7 @@ TEST_CASE("SACCH encodes and decodes idle pattern", "[nxdn][sacch]") { REQUIRE(decoded.getStructure() == ChStructure::SR_SINGLE); // Verify data matches - uint8_t decodedData[3U]; + uint8_t decodedData[3U] = {}; decoded.getData(decodedData); REQUIRE(::memcmp(decodedData, SACCH_IDLE, 3U) == 0); } @@ -157,7 +157,7 @@ TEST_CASE("SACCH handles multi-part structures", "[nxdn][sacch]") { REQUIRE(decoded.getStructure() == structure); REQUIRE(decoded.getRAN() == 7U); - uint8_t decodedData[3U]; + uint8_t decodedData[3U] = {}; decoded.getData(decodedData); Utils::dump(2U, "decodedData", decodedData, 3U); REQUIRE(::memcmp(decodedData, testData, 3U) == 0); diff --git a/tests/p25/P25_PacketDataState_Tests.cpp b/tests/p25/P25_PacketDataState_Tests.cpp index 99c7158e..5196f10b 100644 --- a/tests/p25/P25_PacketDataState_Tests.cpp +++ b/tests/p25/P25_PacketDataState_Tests.cpp @@ -162,7 +162,7 @@ TEST_CASE("P25 LLC state is isolated by LLID", "[p25][packet-data][llc]") uint8_t expected = 0U; CHECK(links.acceptReceiveSequence(1001U, 0U, true, 11U, expected) == ReceiveSequenceResult::ACCEPTED); - CHECK(links.acceptReceiveSequence(1001U, 0U, false, 11U, expected) == ReceiveSequenceResult::DUPLICATE); + CHECK(links.acceptReceiveSequence(1001U, 0U, false, 11U, expected) == ReceiveSequenceResult::DUPLICATE_FRAME); CHECK(links.acceptReceiveSequence(1001U, 1U, false, 12U, expected) == ReceiveSequenceResult::ACCEPTED); CHECK(links.acceptReceiveSequence(1001U, 7U, false, 13U, expected) == ReceiveSequenceResult::ACCEPTED); CHECK(expected == 2U); @@ -181,7 +181,7 @@ TEST_CASE("P25 LLC receive sequence wraps and suppresses duplicates", "[p25][pac REQUIRE(links.acceptReceiveSequence(77U, 7U, false, 21U, expected) == ReceiveSequenceResult::ACCEPTED); CHECK(links.acceptReceiveSequence(77U, 0U, false, 22U, expected) == ReceiveSequenceResult::ACCEPTED); - CHECK(links.acceptReceiveSequence(77U, 0U, false, 22U, expected) == ReceiveSequenceResult::DUPLICATE); + CHECK(links.acceptReceiveSequence(77U, 0U, false, 22U, expected) == ReceiveSequenceResult::DUPLICATE_FRAME); CHECK(links.acceptReceiveSequence(77U, 2U, false, 23U, expected) == ReceiveSequenceResult::ACCEPTED); CHECK(expected == 1U);