diff --git a/src/common/nxdn/channel/LICH.cpp b/src/common/nxdn/channel/LICH.cpp index 95e20a2f..11a92e74 100644 --- a/src/common/nxdn/channel/LICH.cpp +++ b/src/common/nxdn/channel/LICH.cpp @@ -160,11 +160,8 @@ void LICH::copy(const LICH& data) bool LICH::getParity() const { - switch (m_lich & 0xF0U) { - case 0x80U: - case 0xB0U: - return true; - default: - return false; - } + // TS 1-A section 4.5.3 defines even parity over the four most + // significant LICH information bits (RFCT and FCT). + const uint8_t parityBits = (m_lich >> 4U) & 0x0FU; + return ((parityBits >> 3U) ^ (parityBits >> 2U) ^ (parityBits >> 1U) ^ parityBits) & 0x01U; } diff --git a/src/common/nxdn/lc/PacketInformation.cpp b/src/common/nxdn/lc/PacketInformation.cpp index de87b62e..40ee1667 100644 --- a/src/common/nxdn/lc/PacketInformation.cpp +++ b/src/common/nxdn/lc/PacketInformation.cpp @@ -112,8 +112,8 @@ void PacketInformation::encode(const uint8_t messageType, uint8_t* data) break; case MessageType::RTCH_DCALL_ACK: { - data[0U] = (m_rspClass & 0x03U << 4) + // Response Class - (m_rspType & 0x07U << 1); // Response Type + data[0U] = ((m_rspClass & 0x03U) << 4) | // Response Class + ((m_rspType & 0x07U) << 1); // Response Type bool highFragCount = (m_fragmentCount & 0x100U) == 0x100U; data[0U] += (highFragCount ? 0x01U : 0x00U); // Fragment Count - bit 8 diff --git a/tests/nxdn/LICH_Tests.cpp b/tests/nxdn/LICH_Tests.cpp index 88c0b15c..b0018ddc 100644 --- a/tests/nxdn/LICH_Tests.cpp +++ b/tests/nxdn/LICH_Tests.cpp @@ -225,3 +225,30 @@ TEST_CASE("LICH rejects invalid parity", "[nxdn][lich]") { LICH decoded; REQUIRE_FALSE(decoded.decode(data)); } + +TEST_CASE("LICH uses even parity over every RFCT and FCT combination", "[nxdn][lich][golden]") { + for (uint8_t rfct = 0U; rfct < 4U; rfct++) { + for (uint8_t fct = 0U; fct < 4U; fct++) { + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U] = {}; + + LICH lich; + lich.setRFCT(static_cast(rfct)); + lich.setFCT(static_cast(fct)); + lich.setOption(ChOption::DATA_NORMAL); + lich.setOutbound(false); + lich.encode(data); + + uint8_t encodedLICH[1U] = {}; + for (uint32_t bit = 0U; bit < 8U; bit++) { + const uint32_t offset = NXDN_FSW_LENGTH_BITS + bit * 2U; + WRITE_BIT(encodedLICH, bit, READ_BIT(data, offset)); + REQUIRE(READ_BIT(data, offset + 1U)); + } + + const uint8_t highNibble = static_cast((rfct << 2U) | fct); + const bool expectedParity = ((highNibble >> 3U) ^ (highNibble >> 2U) ^ + (highNibble >> 1U) ^ highNibble) & 0x01U; + REQUIRE((encodedLICH[0U] & 0x01U) == (expectedParity ? 0x01U : 0x00U)); + } + } +} diff --git a/tests/nxdn/PacketInformation_Tests.cpp b/tests/nxdn/PacketInformation_Tests.cpp new file mode 100644 index 00000000..252b4989 --- /dev/null +++ b/tests/nxdn/PacketInformation_Tests.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2026 Bryan Biedenkapp, N2PLL + */ + +#include + +#include "common/nxdn/NXDNDefines.h" +#include "common/nxdn/lc/PacketInformation.h" + +using namespace nxdn::defines; +using namespace nxdn::lc; + +TEST_CASE("PacketInformation encodes DCALL_ACK response fields", "[nxdn][packet-information][golden]") +{ + PacketInformation packet; + uint8_t data[2U] = {}; + + SECTION("ACK receive success") { + packet.setResponseClass(PDUResponseClass::ACK); + packet.setResponseType(0x01U); + packet.setFragmentCount(0x000U); + + packet.encode(MessageType::RTCH_DCALL_ACK, data); + + REQUIRE(data[0U] == 0x02U); + REQUIRE(data[1U] == 0x00U); + } + + SECTION("NACK abort with maximum fragment count") { + packet.setResponseClass(PDUResponseClass::NACK); + packet.setResponseType(0x03U); + packet.setFragmentCount(0x1FFU); + + packet.encode(MessageType::RTCH_DCALL_ACK, data); + + REQUIRE(data[0U] == 0x37U); + REQUIRE(data[1U] == 0xFFU); + } +}