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.
dvmhost/tests/dmr/EMB_Tests.cpp

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));
}

Powered by TurnKey Linux.