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.
186 lines
5.3 KiB
186 lines
5.3 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) 2026 Bryan Biedenkapp, N2PLL
|
|
*
|
|
*/
|
|
|
|
#include "common/Log.h"
|
|
#include "common/Utils.h"
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <cstring>
|
|
|
|
#include "common/dmr/data/EMB.h"
|
|
|
|
using namespace dmr::data;
|
|
|
|
TEST_CASE("EMB encodes and decodes without errors", "[dmr][emb]") {
|
|
EMB emb;
|
|
emb.setColorCode(7);
|
|
emb.setPI(true);
|
|
emb.setLCSS(2);
|
|
|
|
uint8_t data[24U];
|
|
::memset(data, 0x00U, sizeof(data));
|
|
|
|
emb.encode(data);
|
|
|
|
EMB decoded;
|
|
decoded.decode(data);
|
|
|
|
REQUIRE(decoded.getColorCode() == 7);
|
|
REQUIRE(decoded.getPI() == true);
|
|
REQUIRE(decoded.getLCSS() == 2);
|
|
}
|
|
|
|
TEST_CASE("EMB corrects single-bit errors in embedded signaling", "[dmr][emb]") {
|
|
EMB emb;
|
|
emb.setColorCode(5);
|
|
emb.setPI(false);
|
|
emb.setLCSS(1);
|
|
|
|
uint8_t data[24U];
|
|
::memset(data, 0x00U, sizeof(data));
|
|
|
|
emb.encode(data);
|
|
|
|
// Save the encoded data
|
|
uint8_t original[24U];
|
|
::memcpy(original, data, sizeof(data));
|
|
|
|
// EMB data is stored in nibbles at positions 13, 14, 18, 19
|
|
// This gives us 16 bits total to test
|
|
const uint32_t embPositions[] = {13, 14, 18, 19};
|
|
|
|
// Test single-bit errors in each nibble
|
|
for (auto pos : embPositions) {
|
|
for (uint32_t bit = 0; bit < 8; bit++) {
|
|
::memcpy(data, original, sizeof(data));
|
|
|
|
// Introduce single-bit error
|
|
data[pos] ^= (1U << bit);
|
|
|
|
EMB decoded;
|
|
decoded.decode(data);
|
|
|
|
// QR(16,7,6) should correct single-bit errors
|
|
REQUIRE(decoded.getColorCode() == 5);
|
|
REQUIRE(decoded.getPI() == false);
|
|
REQUIRE(decoded.getLCSS() == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("EMB corrects two-bit errors in embedded signaling", "[dmr][emb]") {
|
|
EMB emb;
|
|
emb.setColorCode(12);
|
|
emb.setPI(true);
|
|
emb.setLCSS(3);
|
|
|
|
uint8_t data[24U];
|
|
::memset(data, 0x00U, sizeof(data));
|
|
|
|
emb.encode(data);
|
|
|
|
// Save the encoded data
|
|
uint8_t original[24U];
|
|
::memcpy(original, data, sizeof(data));
|
|
|
|
// Test two-bit errors in different positions
|
|
const struct {
|
|
uint32_t pos1, bit1, pos2, bit2;
|
|
} errorPairs[] = {
|
|
{13, 0, 13, 7}, // Same byte
|
|
{13, 4, 14, 3}, // Adjacent bytes
|
|
{13, 5, 18, 2}, // Distant bytes
|
|
{14, 1, 19, 6}, // Different nibble pairs
|
|
{18, 0, 19, 7} // Same nibble pair
|
|
};
|
|
|
|
for (auto& pair : errorPairs) {
|
|
::memcpy(data, original, sizeof(data));
|
|
|
|
// Introduce two-bit errors
|
|
data[pair.pos1] ^= (1U << pair.bit1);
|
|
data[pair.pos2] ^= (1U << pair.bit2);
|
|
|
|
EMB decoded;
|
|
decoded.decode(data);
|
|
|
|
// QR(16,7,6) should correct two-bit errors
|
|
REQUIRE(decoded.getColorCode() == 12);
|
|
REQUIRE(decoded.getPI() == true);
|
|
REQUIRE(decoded.getLCSS() == 3);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("EMB tests all color code values", "[dmr][emb]") {
|
|
// Color code is 4 bits (0-15)
|
|
for (uint32_t cc = 0; cc < 16; cc++) {
|
|
EMB emb;
|
|
emb.setColorCode(cc);
|
|
emb.setPI(cc & 1); // Alternate PI
|
|
emb.setLCSS(cc & 3); // Cycle through LCSS values
|
|
|
|
uint8_t data[24U];
|
|
::memset(data, 0x00U, sizeof(data));
|
|
|
|
emb.encode(data);
|
|
|
|
EMB decoded;
|
|
decoded.decode(data);
|
|
|
|
REQUIRE(decoded.getColorCode() == cc);
|
|
REQUIRE(decoded.getPI() == (bool)(cc & 1));
|
|
REQUIRE(decoded.getLCSS() == (cc & 3));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("EMB verifies error correction restores correct values after corruption", "[dmr][emb]") {
|
|
// This test specifically verifies that the bug fix works:
|
|
// Before the fix, decode() would return the corrected value but EMB
|
|
// would read from the uncorrected buffer, causing wrong results.
|
|
|
|
EMB emb;
|
|
emb.setColorCode(9);
|
|
emb.setPI(false);
|
|
emb.setLCSS(2);
|
|
|
|
uint8_t data[24U];
|
|
::memset(data, 0xAAU, sizeof(data)); // Non-zero background
|
|
|
|
emb.encode(data);
|
|
|
|
// Corrupt the EMB data with a single-bit error in position 13, bit 4
|
|
// This should be correctable by QR(16,7,6)
|
|
data[13] ^= 0x10U;
|
|
|
|
EMB decoded;
|
|
decoded.decode(data);
|
|
|
|
// Verify the corrected values are read (not the corrupted buffer)
|
|
REQUIRE(decoded.getColorCode() == 9);
|
|
REQUIRE(decoded.getPI() == false);
|
|
REQUIRE(decoded.getLCSS() == 2);
|
|
|
|
// Now encode again and verify we get the same result as original
|
|
uint8_t reencoded[24U];
|
|
::memset(reencoded, 0xAAU, sizeof(reencoded)); // Same background as original
|
|
decoded.encode(reencoded);
|
|
|
|
// The EMB portions should match the original uncorrupted encoding
|
|
uint8_t original[24U];
|
|
::memset(original, 0xAAU, sizeof(original));
|
|
emb.encode(original);
|
|
|
|
// EMB data is in nibbles, so we need to mask and compare
|
|
REQUIRE((reencoded[13] & 0x0F) == (original[13] & 0x0F));
|
|
REQUIRE((reencoded[14] & 0xF0) == (original[14] & 0xF0));
|
|
REQUIRE((reencoded[18] & 0x0F) == (original[18] & 0x0F));
|
|
REQUIRE((reencoded[19] & 0xF0) == (original[19] & 0xF0));
|
|
}
|