You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
10 KiB
325 lines
10 KiB
// 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) 2025 Bryan Biedenkapp, N2PLL
|
|
*
|
|
*/
|
|
#include "host/Defines.h"
|
|
#include "common/p25/lc/tdulc/LC_GROUP.h"
|
|
#include "common/p25/lc/tdulc/LC_PRIVATE.h"
|
|
#include "common/p25/P25Defines.h"
|
|
#include "common/edac/Golay24128.h"
|
|
#include "common/edac/RS634717.h"
|
|
|
|
using namespace p25;
|
|
using namespace p25::defines;
|
|
using namespace p25::lc;
|
|
using namespace p25::lc::tdulc;
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
TEST_CASE("TDULC", "[p25][tdulc]") {
|
|
SECTION("Constants_Valid") {
|
|
// Verify TDULC length constants
|
|
REQUIRE(P25_TDULC_LENGTH_BYTES == 18); // Total length with RS FEC
|
|
REQUIRE(P25_TDULC_PAYLOAD_LENGTH_BYTES == 8); // Payload only
|
|
REQUIRE(P25_TDULC_FEC_LENGTH_BYTES == 36); // After Golay encoding
|
|
REQUIRE(P25_TDULC_FRAME_LENGTH_BYTES == 54); // Full frame with preamble
|
|
}
|
|
|
|
SECTION("Golay_Encode_Decode") {
|
|
// Test Golay (24,12,8) FEC encoding/decoding
|
|
uint8_t input[P25_TDULC_LENGTH_BYTES];
|
|
::memset(input, 0x00, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Set test pattern
|
|
input[0] = 0x12;
|
|
input[1] = 0x34;
|
|
input[2] = 0x56;
|
|
input[3] = 0x78;
|
|
|
|
// Encode with Golay
|
|
uint8_t encoded[P25_TDULC_FEC_LENGTH_BYTES + 1];
|
|
::memset(encoded, 0x00, P25_TDULC_FEC_LENGTH_BYTES + 1);
|
|
edac::Golay24128::encode24128(encoded, input, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Decode with Golay
|
|
uint8_t decoded[P25_TDULC_LENGTH_BYTES];
|
|
::memset(decoded, 0x00, P25_TDULC_LENGTH_BYTES);
|
|
edac::Golay24128::decode24128(decoded, encoded, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Verify round-trip
|
|
for (uint32_t i = 0; i < P25_TDULC_LENGTH_BYTES; i++) {
|
|
REQUIRE(decoded[i] == input[i]);
|
|
}
|
|
}
|
|
|
|
SECTION("RS_241213_Encode_Decode") {
|
|
// Test RS (24,12,13) FEC encoding/decoding
|
|
edac::RS634717 rs;
|
|
|
|
uint8_t input[P25_TDULC_LENGTH_BYTES];
|
|
::memset(input, 0x00, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Set test pattern in first 12 bytes (data portion)
|
|
for (uint32_t i = 0; i < 12; i++) {
|
|
input[i] = (uint8_t)(i * 0x11);
|
|
}
|
|
|
|
// Encode RS (adds 6 parity bytes)
|
|
rs.encode241213(input);
|
|
|
|
// Decode RS
|
|
bool result = rs.decode241213(input);
|
|
|
|
REQUIRE(result == true);
|
|
}
|
|
|
|
SECTION("LCO_Values") {
|
|
// Test various LCO values (6 bits)
|
|
uint8_t lcoValues[] = { 0x00, 0x01, 0x02, 0x03, 0x20, 0x3F };
|
|
|
|
for (auto lco : lcoValues) {
|
|
LC_GROUP tdulc;
|
|
tdulc.setLCO(lco & 0x3F); // Mask to 6 bits
|
|
|
|
REQUIRE(tdulc.getLCO() == (lco & 0x3F));
|
|
}
|
|
}
|
|
|
|
SECTION("Emergency_Flag") {
|
|
// Test emergency flag
|
|
LC_GROUP tdulc;
|
|
|
|
tdulc.setEmergency(false);
|
|
REQUIRE(tdulc.getEmergency() == false);
|
|
|
|
tdulc.setEmergency(true);
|
|
REQUIRE(tdulc.getEmergency() == true);
|
|
}
|
|
|
|
SECTION("Encrypted_Flag") {
|
|
// Test encrypted flag
|
|
LC_GROUP tdulc;
|
|
|
|
tdulc.setEncrypted(false);
|
|
REQUIRE(tdulc.getEncrypted() == false);
|
|
|
|
tdulc.setEncrypted(true);
|
|
REQUIRE(tdulc.getEncrypted() == true);
|
|
}
|
|
|
|
SECTION("Priority_Values") {
|
|
// Test priority values (3 bits: 0-7)
|
|
for (uint8_t priority = 0; priority <= 7; priority++) {
|
|
LC_GROUP tdulc;
|
|
tdulc.setPriority(priority);
|
|
|
|
REQUIRE(tdulc.getPriority() == priority);
|
|
}
|
|
}
|
|
|
|
SECTION("Group_Flag") {
|
|
// Test group flag
|
|
LC_GROUP groupTdulc;
|
|
groupTdulc.setGroup(true);
|
|
REQUIRE(groupTdulc.getGroup() == true);
|
|
|
|
LC_PRIVATE privateTdulc;
|
|
privateTdulc.setGroup(false);
|
|
REQUIRE(privateTdulc.getGroup() == false);
|
|
}
|
|
|
|
SECTION("SrcId_Values") {
|
|
// Test source ID values (24 bits)
|
|
uint32_t srcIds[] = { 0x000000, 0x000001, 0x123456, 0xFFFFFE, 0xFFFFFF };
|
|
|
|
for (auto srcId : srcIds) {
|
|
LC_GROUP tdulc;
|
|
tdulc.setSrcId(srcId & 0xFFFFFF); // Mask to 24 bits
|
|
|
|
REQUIRE(tdulc.getSrcId() == (srcId & 0xFFFFFF));
|
|
}
|
|
}
|
|
|
|
SECTION("DstId_Values") {
|
|
// Test destination ID values (16 bits for group)
|
|
uint32_t dstIds[] = { 0x0000, 0x0001, 0x1234, 0xFFFE, 0xFFFF };
|
|
|
|
for (auto dstId : dstIds) {
|
|
LC_GROUP tdulc;
|
|
tdulc.setDstId(dstId & 0xFFFF); // Mask to 16 bits
|
|
|
|
REQUIRE(tdulc.getDstId() == (dstId & 0xFFFF));
|
|
}
|
|
}
|
|
|
|
SECTION("MfgId_Values") {
|
|
// Test manufacturer ID values
|
|
uint8_t mfgIds[] = { 0x00, 0x01, 0x90, 0xFF };
|
|
|
|
for (auto mfgId : mfgIds) {
|
|
LC_GROUP tdulc;
|
|
tdulc.setMFId(mfgId);
|
|
|
|
REQUIRE(tdulc.getMFId() == mfgId);
|
|
}
|
|
}
|
|
|
|
SECTION("AllZeros_Pattern") {
|
|
// Test all-zeros pattern
|
|
LC_GROUP tdulc;
|
|
|
|
tdulc.setLCO(0x00);
|
|
tdulc.setMFId(0x00);
|
|
tdulc.setSrcId(0x000000);
|
|
tdulc.setDstId(0x0000);
|
|
tdulc.setEmergency(false);
|
|
tdulc.setEncrypted(false);
|
|
tdulc.setPriority(0);
|
|
|
|
REQUIRE(tdulc.getLCO() == 0x00);
|
|
REQUIRE(tdulc.getMFId() == 0x00);
|
|
REQUIRE(tdulc.getSrcId() == 0x000000);
|
|
REQUIRE(tdulc.getDstId() == 0x0000);
|
|
REQUIRE(tdulc.getEmergency() == false);
|
|
REQUIRE(tdulc.getEncrypted() == false);
|
|
REQUIRE(tdulc.getPriority() == 0);
|
|
}
|
|
|
|
SECTION("MaxValues_Pattern") {
|
|
// Test maximum values pattern
|
|
LC_GROUP tdulc;
|
|
|
|
tdulc.setLCO(0x3F); // 6 bits max
|
|
tdulc.setMFId(0xFF); // 8 bits max
|
|
tdulc.setSrcId(0xFFFFFF); // 24 bits max
|
|
tdulc.setDstId(0xFFFF); // 16 bits max
|
|
tdulc.setEmergency(true);
|
|
tdulc.setEncrypted(true);
|
|
tdulc.setPriority(7); // 3 bits max
|
|
|
|
REQUIRE(tdulc.getLCO() == 0x3F);
|
|
REQUIRE(tdulc.getMFId() == 0xFF);
|
|
REQUIRE(tdulc.getSrcId() == 0xFFFFFF);
|
|
REQUIRE(tdulc.getDstId() == 0xFFFF);
|
|
REQUIRE(tdulc.getEmergency() == true);
|
|
REQUIRE(tdulc.getEncrypted() == true);
|
|
REQUIRE(tdulc.getPriority() == 7);
|
|
}
|
|
|
|
SECTION("Group_Copy_Constructor") {
|
|
// Test copy constructor for LC_GROUP
|
|
LC_GROUP tdulc1;
|
|
|
|
tdulc1.setLCO(0x00);
|
|
tdulc1.setMFId(0x90);
|
|
tdulc1.setSrcId(0x123456);
|
|
tdulc1.setDstId(0xABCD);
|
|
tdulc1.setEmergency(true);
|
|
tdulc1.setEncrypted(false);
|
|
tdulc1.setPriority(5);
|
|
|
|
LC_GROUP tdulc2(tdulc1);
|
|
|
|
REQUIRE(tdulc2.getLCO() == tdulc1.getLCO());
|
|
REQUIRE(tdulc2.getMFId() == tdulc1.getMFId());
|
|
REQUIRE(tdulc2.getSrcId() == tdulc1.getSrcId());
|
|
REQUIRE(tdulc2.getDstId() == tdulc1.getDstId());
|
|
REQUIRE(tdulc2.getEmergency() == tdulc1.getEmergency());
|
|
REQUIRE(tdulc2.getEncrypted() == tdulc1.getEncrypted());
|
|
REQUIRE(tdulc2.getPriority() == tdulc1.getPriority());
|
|
}
|
|
|
|
SECTION("Private_Copy_Constructor") {
|
|
// Test copy constructor for LC_PRIVATE
|
|
LC_PRIVATE tdulc1;
|
|
|
|
tdulc1.setLCO(0x03);
|
|
tdulc1.setMFId(0x00);
|
|
tdulc1.setSrcId(0xABCDEF);
|
|
tdulc1.setDstId(0x123456);
|
|
tdulc1.setEmergency(false);
|
|
tdulc1.setEncrypted(true);
|
|
tdulc1.setPriority(3);
|
|
|
|
LC_PRIVATE tdulc2(tdulc1);
|
|
|
|
REQUIRE(tdulc2.getLCO() == tdulc1.getLCO());
|
|
REQUIRE(tdulc2.getMFId() == tdulc1.getMFId());
|
|
REQUIRE(tdulc2.getSrcId() == tdulc1.getSrcId());
|
|
REQUIRE(tdulc2.getDstId() == tdulc1.getDstId());
|
|
REQUIRE(tdulc2.getEmergency() == tdulc1.getEmergency());
|
|
REQUIRE(tdulc2.getEncrypted() == tdulc1.getEncrypted());
|
|
REQUIRE(tdulc2.getPriority() == tdulc1.getPriority());
|
|
}
|
|
|
|
SECTION("Golay_ErrorCorrection") {
|
|
// Test Golay error correction capability
|
|
uint8_t input[P25_TDULC_LENGTH_BYTES];
|
|
::memset(input, 0x00, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Set known pattern
|
|
input[0] = 0xAA;
|
|
input[1] = 0x55;
|
|
input[2] = 0xF0;
|
|
input[3] = 0x0F;
|
|
|
|
// Encode
|
|
uint8_t encoded[P25_TDULC_FEC_LENGTH_BYTES + 1];
|
|
::memset(encoded, 0x00, P25_TDULC_FEC_LENGTH_BYTES + 1);
|
|
edac::Golay24128::encode24128(encoded, input, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Introduce single bit error (Golay can correct up to 3 bit errors)
|
|
encoded[5] ^= 0x01;
|
|
|
|
// Decode (should correct the error)
|
|
uint8_t decoded[P25_TDULC_LENGTH_BYTES];
|
|
::memset(decoded, 0x00, P25_TDULC_LENGTH_BYTES);
|
|
edac::Golay24128::decode24128(decoded, encoded, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Verify correction
|
|
REQUIRE(decoded[0] == input[0]);
|
|
REQUIRE(decoded[1] == input[1]);
|
|
REQUIRE(decoded[2] == input[2]);
|
|
REQUIRE(decoded[3] == input[3]);
|
|
}
|
|
|
|
SECTION("RS_ErrorCorrection") {
|
|
// Test RS error correction capability
|
|
edac::RS634717 rs;
|
|
|
|
uint8_t data[P25_TDULC_LENGTH_BYTES];
|
|
::memset(data, 0x00, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Set known data pattern in first 12 bytes
|
|
for (uint32_t i = 0; i < 12; i++) {
|
|
data[i] = (uint8_t)(0xAA - i);
|
|
}
|
|
|
|
// Encode RS
|
|
rs.encode241213(data);
|
|
|
|
// Save original
|
|
uint8_t original[P25_TDULC_LENGTH_BYTES];
|
|
::memcpy(original, data, P25_TDULC_LENGTH_BYTES);
|
|
|
|
// Introduce errors (RS can correct up to 6 byte errors with (24,12,13))
|
|
data[2] ^= 0xFF;
|
|
data[5] ^= 0xFF;
|
|
|
|
// Decode (should correct the errors)
|
|
bool result = rs.decode241213(data);
|
|
|
|
REQUIRE(result == true);
|
|
|
|
// Verify correction
|
|
for (uint32_t i = 0; i < 12; i++) {
|
|
REQUIRE(data[i] == original[i]);
|
|
}
|
|
}
|
|
}
|