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.
195 lines
7.1 KiB
195 lines
7.1 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 <catch2/catch_test_macros.hpp>
|
|
#include <array>
|
|
#include <cstring>
|
|
|
|
#include "common/Defines.h"
|
|
#include "common/dmr/DMRDefines.h"
|
|
#include "common/dmr/data/DataBlock.h"
|
|
#include "common/dmr/data/DataHeader.h"
|
|
#include "common/edac/BPTC19696.h"
|
|
#include "common/edac/CRC.h"
|
|
#include "common/edac/Trellis.h"
|
|
|
|
using namespace dmr::defines;
|
|
using namespace dmr::data;
|
|
|
|
namespace {
|
|
/**
|
|
* @brief Returns the CRC mask for the given data type.
|
|
* @param type The data type for which to retrieve the CRC mask.
|
|
* @return constexpr uint16_t
|
|
*/
|
|
constexpr uint16_t crcMask(DataType::E type)
|
|
{
|
|
return type == DataType::RATE_12_DATA ? 0x0F0U :
|
|
type == DataType::RATE_34_DATA ? 0x1FFU : 0x10FU;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the payload length for the given data type and confirmation status.
|
|
* @param type The data type for which to retrieve the payload length.
|
|
* @param confirmed Whether the data is confirmed.
|
|
* @return constexpr uint32_t The payload length in bytes.
|
|
*/
|
|
constexpr uint32_t payloadLength(DataType::E type, bool confirmed)
|
|
{
|
|
if (type == DataType::RATE_12_DATA)
|
|
return confirmed ? DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES : DMR_PDU_HALFRATE_LENGTH_BYTES;
|
|
if (type == DataType::RATE_34_DATA)
|
|
return confirmed ? DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES : DMR_PDU_THREEQUARTER_LENGTH_BYTES;
|
|
return confirmed ? DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES : DMR_PDU_UNCODED_LENGTH_BYTES;
|
|
}
|
|
|
|
/**
|
|
* @brief Extracts a data block from the given frame based on the data type.
|
|
* @param type The data type of the block to extract.
|
|
* @param frame The input frame containing the encoded data.
|
|
* @param block The output buffer to store the extracted data block.
|
|
*/
|
|
void extractBlock(DataType::E type, const uint8_t* frame, uint8_t* block)
|
|
{
|
|
if (type == DataType::RATE_12_DATA) {
|
|
edac::BPTC19696().decode(frame, block);
|
|
} else if (type == DataType::RATE_34_DATA) {
|
|
REQUIRE(edac::Trellis().decode34(frame, block, true));
|
|
} else {
|
|
::memcpy(block, frame, 12U);
|
|
::memcpy(block + 12U, frame + 21U, 12U);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Injects a data block into the given frame based on the data type.
|
|
* @param type The data type of the block to inject.
|
|
* @param block The input buffer containing the data block to inject.
|
|
* @param frame The output frame where the data block will be injected.
|
|
*/
|
|
void injectBlock(DataType::E type, const uint8_t* block, uint8_t* frame)
|
|
{
|
|
::memset(frame, 0x00U, DMR_FRAME_LENGTH_BYTES);
|
|
if (type == DataType::RATE_12_DATA) {
|
|
edac::BPTC19696().encode(block, frame);
|
|
} else if (type == DataType::RATE_34_DATA) {
|
|
edac::Trellis().encode34(block, frame, true);
|
|
} else {
|
|
::memcpy(frame, block, 12U);
|
|
::memcpy(frame + 21U, block + 12U, 12U);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DMR confirmed data blocks preserve DBSN, payload, and masked CRC-9", "[dmr][datablock]")
|
|
{
|
|
const DataType::E rates[] = {
|
|
DataType::RATE_12_DATA, DataType::RATE_34_DATA, DataType::RATE_1_DATA
|
|
};
|
|
|
|
for (DataType::E rate : rates) {
|
|
CAPTURE(rate);
|
|
const uint32_t length = payloadLength(rate, true);
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> payload{};
|
|
for (uint32_t i = 0U; i < length; ++i)
|
|
payload[i] = uint8_t(0x31U + i);
|
|
|
|
DataBlock encoded;
|
|
encoded.setFormat(DPF::CONFIRMED_DATA);
|
|
encoded.setDataType(rate);
|
|
encoded.setSerialNo(0x35U);
|
|
encoded.setData(payload.data());
|
|
|
|
std::array<uint8_t, DMR_FRAME_LENGTH_BYTES> frame{};
|
|
encoded.encode(frame.data());
|
|
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> raw{};
|
|
extractBlock(rate, frame.data(), raw.data());
|
|
REQUIRE((raw[0U] >> 1) == 0x35U);
|
|
REQUIRE(::memcmp(raw.data() + 2U, payload.data(), length) == 0);
|
|
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> crcInput{};
|
|
::memcpy(crcInput.data(), payload.data(), length);
|
|
for (uint32_t i = 0U; i < 7U; ++i)
|
|
WRITE_BIT(crcInput.data(), length * 8U + i, READ_BIT(raw.data(), i));
|
|
uint16_t expected = ~edac::CRC::createCRC9(crcInput.data(), length * 8U + 7U) & 0x1FFU;
|
|
expected ^= crcMask(rate);
|
|
const uint16_t transmitted = ((raw[0U] & 0x01U) << 8) | raw[1U];
|
|
REQUIRE(transmitted == expected);
|
|
|
|
DataHeader header;
|
|
header.setDPF(DPF::CONFIRMED_DATA);
|
|
DataBlock decoded;
|
|
decoded.setDataType(rate);
|
|
REQUIRE(decoded.decode(frame.data(), header));
|
|
REQUIRE(decoded.getSerialNo() == 0x35U);
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> output{};
|
|
REQUIRE(decoded.getData(output.data()) == length);
|
|
REQUIRE(::memcmp(output.data(), payload.data(), length) == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DMR confirmed data blocks reject CRC-9 failures", "[dmr][datablock]")
|
|
{
|
|
const DataType::E rates[] = {
|
|
DataType::RATE_12_DATA, DataType::RATE_34_DATA, DataType::RATE_1_DATA
|
|
};
|
|
DataHeader header;
|
|
header.setDPF(DPF::CONFIRMED_DATA);
|
|
|
|
for (DataType::E rate : rates) {
|
|
CAPTURE(rate);
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> payload{};
|
|
payload.fill(0xA5U);
|
|
DataBlock encoded;
|
|
encoded.setFormat(header);
|
|
encoded.setDataType(rate);
|
|
encoded.setSerialNo(7U);
|
|
encoded.setData(payload.data());
|
|
std::array<uint8_t, DMR_FRAME_LENGTH_BYTES> frame{};
|
|
encoded.encode(frame.data());
|
|
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> raw{};
|
|
extractBlock(rate, frame.data(), raw.data());
|
|
raw[2U] ^= 0x80U;
|
|
injectBlock(rate, raw.data(), frame.data());
|
|
|
|
DataBlock decoded;
|
|
decoded.setDataType(rate);
|
|
REQUIRE_FALSE(decoded.decode(frame.data(), header));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DMR unconfirmed Rate 1 maps all 24 payload octets around the burst centre", "[dmr][datablock]")
|
|
{
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> payload{};
|
|
for (uint32_t i = 0U; i < payload.size(); ++i)
|
|
payload[i] = uint8_t(i + 1U);
|
|
|
|
DataBlock encoded;
|
|
encoded.setFormat(DPF::UNCONFIRMED_DATA);
|
|
encoded.setDataType(DataType::RATE_1_DATA);
|
|
encoded.setData(payload.data());
|
|
std::array<uint8_t, DMR_FRAME_LENGTH_BYTES> frame{};
|
|
frame.fill(0xFFU);
|
|
encoded.encode(frame.data());
|
|
REQUIRE((frame[12U] & 0xC0U) == 0U);
|
|
REQUIRE((frame[20U] & 0x03U) == 0U);
|
|
|
|
DataHeader header;
|
|
header.setDPF(DPF::UNCONFIRMED_DATA);
|
|
DataBlock decoded;
|
|
decoded.setDataType(DataType::RATE_1_DATA);
|
|
REQUIRE(decoded.decode(frame.data(), header));
|
|
std::array<uint8_t, DMR_PDU_UNCODED_LENGTH_BYTES> output{};
|
|
REQUIRE(decoded.getData(output.data()) == payload.size());
|
|
REQUIRE(output == payload);
|
|
}
|