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/CSBK_Tests.cpp

385 lines
12 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/dmr/DMRDefines.h"
#include "common/dmr/lc/csbk/CSBK_RAW.h"
#include "common/edac/CRC.h"
#include "common/edac/BPTC19696.h"
using namespace dmr;
using namespace dmr::defines;
using namespace dmr::lc;
using namespace dmr::lc::csbk;
#include <catch2/catch_test_macros.hpp>
TEST_CASE("CSBK", "[dmr][csbk]") {
SECTION("Constants_Valid") {
// Verify CSBK length constants
REQUIRE(DMR_CSBK_LENGTH_BYTES == 12);
REQUIRE(DMR_FRAME_LENGTH_BYTES == 33);
}
SECTION("Encode_Decode_RoundTrip") {
// Test basic encoding/decoding round trip
CSBK_RAW csbk1;
// Create a test CSBK payload (12 bytes)
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
// Set CSBKO (Control Signalling Block Opcode) - byte 0, bits 0-5
testCSBK[0] = CSBKO::RAND; // Random Access opcode
testCSBK[1] = 0x00; // FID (Feature ID) - standard
// Set some payload data (bytes 2-9)
for (uint32_t i = 2; i < 10; i++) {
testCSBK[i] = (uint8_t)(i * 0x11);
}
// Add CRC-CCITT16 (bytes 10-11) with mask
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
// Set the CSBK
csbk1.setCSBK(testCSBK);
// Encode with BPTC (196,96) FEC
uint8_t encoded[DMR_FRAME_LENGTH_BYTES];
::memset(encoded, 0x00, DMR_FRAME_LENGTH_BYTES);
csbk1.encode(encoded);
// Decode back
CSBK_RAW csbk2;
csbk2.setDataType(DataType::CSBK);
bool result = csbk2.decode(encoded);
REQUIRE(result == true);
REQUIRE(csbk2.getCSBKO() == (testCSBK[0] & 0x3F));
REQUIRE(csbk2.getFID() == testCSBK[1]);
}
SECTION("LastBlock_Flag") {
// Test Last Block Marker flag
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
// Set Last Block flag (bit 7 of byte 0)
testCSBK[0] = 0x80 | CSBKO::RAND; // Last Block + CSBKO
testCSBK[1] = 0x00;
// Add CRC
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
REQUIRE(csbk.getLastBlock() == true);
REQUIRE(csbk.getCSBKO() == CSBKO::RAND);
}
SECTION("FID_Preservation") {
// Test Feature ID preservation
uint8_t fids[] = { 0x00, 0x01, 0x10, 0xFF };
for (auto fid : fids) {
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[0] = CSBKO::RAND;
testCSBK[1] = fid;
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
REQUIRE(csbk.getFID() == fid);
}
}
SECTION("CRC_CCITT16_With_Mask") {
// Test CRC-CCITT16 with DMR mask
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[0] = CSBKO::RAND;
testCSBK[1] = 0x00;
testCSBK[2] = 0xAB;
testCSBK[3] = 0xCD;
// Apply mask before CRC
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
// Add CRC
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
// Verify CRC is valid with mask applied
bool crcValid = edac::CRC::checkCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
REQUIRE(crcValid == true);
// Remove mask
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
// Corrupt the CRC
testCSBK[11] ^= 0xFF;
// Apply mask again
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
// Verify CRC is now invalid
crcValid = edac::CRC::checkCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
REQUIRE(crcValid == false);
}
SECTION("Payload_RoundTrip") {
// Test payload data round-trip (bytes 2-9, 8 bytes)
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[0] = CSBKO::RAND;
testCSBK[1] = 0x00;
// Payload is bytes 2-9 (8 bytes)
uint8_t expectedPayload[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
::memcpy(testCSBK + 2, expectedPayload, 8);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
// Encode and verify it can be encoded without errors
uint8_t encoded[DMR_FRAME_LENGTH_BYTES];
::memset(encoded, 0x00, DMR_FRAME_LENGTH_BYTES);
csbk.encode(encoded);
// Verify BPTC encoding produced non-zero data
bool hasData = false;
for (uint32_t i = 0; i < DMR_FRAME_LENGTH_BYTES; i++) {
if (encoded[i] != 0x00) {
hasData = true;
break;
}
}
REQUIRE(hasData == true);
}
SECTION("BPTC_FEC_Encoding") {
// Test BPTC (196,96) FEC encoding
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[0] = CSBKO::RAND;
testCSBK[1] = 0x00;
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
// Encode with BPTC FEC
uint8_t encoded[DMR_FRAME_LENGTH_BYTES];
::memset(encoded, 0x00, DMR_FRAME_LENGTH_BYTES);
csbk.encode(encoded);
// Verify encoding produced data
bool hasData = false;
for (uint32_t i = 0; i < DMR_FRAME_LENGTH_BYTES; i++) {
if (encoded[i] != 0x00) {
hasData = true;
break;
}
}
REQUIRE(hasData == true);
}
SECTION("AllZeros_Pattern") {
// Test all-zeros pattern
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
uint8_t encoded[DMR_FRAME_LENGTH_BYTES];
csbk.encode(encoded);
CSBK_RAW csbk2;
csbk2.setDataType(DataType::CSBK);
bool result = csbk2.decode(encoded);
REQUIRE(result == true);
}
SECTION("AllOnes_Pattern") {
// Test all-ones pattern (with valid structure)
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0xFF, DMR_CSBK_LENGTH_BYTES);
// Set CSBKO to DVM_GIT_HASH (0x3F) with Last Block flag
testCSBK[0] = 0xBF; // Last Block (0x80) + CSBKO 0x3F = 0xBF
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
uint8_t encoded[DMR_FRAME_LENGTH_BYTES];
csbk.encode(encoded);
// Verify encoding succeeded
bool hasData = false;
for (uint32_t i = 0; i < DMR_FRAME_LENGTH_BYTES; i++) {
if (encoded[i] != 0x00) {
hasData = true;
break;
}
}
REQUIRE(hasData == true);
// Verify the setCSBK extracted values correctly
REQUIRE(csbk.getCSBKO() == 0x3F); // DVM_GIT_HASH
REQUIRE(csbk.getLastBlock() == true);
}
SECTION("Alternating_Pattern") {
// Test alternating bit pattern
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
for (uint32_t i = 0; i < DMR_CSBK_LENGTH_BYTES; i++) {
testCSBK[i] = (i % 2 == 0) ? 0xAA : 0x55;
}
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
uint8_t encoded[DMR_FRAME_LENGTH_BYTES];
csbk.encode(encoded);
CSBK_RAW csbk2;
csbk2.setDataType(DataType::CSBK);
bool result = csbk2.decode(encoded);
REQUIRE(result == true);
}
SECTION("CSBKO_Values") {
// Test various CSBKO values (6 bits)
uint8_t csbkoValues[] = {
CSBKO::RAND,
CSBKO::BSDWNACT,
CSBKO::PRECCSBK,
0x00, 0x01, 0x0F, 0x20, 0x3F
};
for (uint32_t i = 0; i < sizeof(csbkoValues); i++) {
uint8_t csbko = csbkoValues[i];
CSBK_RAW csbk;
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[0] = csbko & 0x3F; // Mask to 6 bits
testCSBK[1] = 0x00;
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
testCSBK[10] ^= CSBK_CRC_MASK[0];
testCSBK[11] ^= CSBK_CRC_MASK[1];
csbk.setCSBK(testCSBK);
REQUIRE(csbk.getCSBKO() == (csbko & 0x3F));
}
}
SECTION("MBC_CRC_Mask") {
// Test MBC (Multi-Block Control) CRC mask variant
uint8_t testCSBK[DMR_CSBK_LENGTH_BYTES];
::memset(testCSBK, 0x00, DMR_CSBK_LENGTH_BYTES);
testCSBK[0] = CSBKO::PRECCSBK; // Preamble CSBK uses MBC header
testCSBK[1] = 0x00;
// Apply MBC mask before CRC
testCSBK[10] ^= CSBK_MBC_CRC_MASK[0];
testCSBK[11] ^= CSBK_MBC_CRC_MASK[1];
// Add CRC
edac::CRC::addCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
// Verify CRC is valid with MBC mask applied
bool crcValid = edac::CRC::checkCCITT162(testCSBK, DMR_CSBK_LENGTH_BYTES);
REQUIRE(crcValid == true);
}
SECTION("DataType_CSBK") {
// Test with DataType::CSBK
CSBK_RAW csbk;
csbk.setDataType(DataType::CSBK);
REQUIRE(csbk.getDataType() == DataType::CSBK);
}
SECTION("DataType_MBC_HEADER") {
// Test with DataType::MBC_HEADER
CSBK_RAW csbk;
csbk.setDataType(DataType::MBC_HEADER);
REQUIRE(csbk.getDataType() == DataType::MBC_HEADER);
}
}

Powered by TurnKey Linux.