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.
704 lines
23 KiB
704 lines
23 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-2026 Bryan Biedenkapp, N2PLL
|
|
*
|
|
*/
|
|
#include "host/Defines.h"
|
|
#include "common/p25/lc/tsbk/OSP_SCCB.h"
|
|
#include "common/p25/lc/tsbk/OSP_SCCB_EXP.h"
|
|
#include "common/p25/lc/tsbk/OSP_ADJ_STS_BCAST.h"
|
|
#include "common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h"
|
|
#include "common/p25/lc/tsbk/OSP_GRP_AFF_Q.h"
|
|
#include "common/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h"
|
|
#include "common/p25/lc/tsbk/OSP_TSBK_RAW.h"
|
|
#include "common/p25/lc/tsbk/TSBKFactory.h"
|
|
#include "common/p25/data/DataBlock.h"
|
|
#include "common/p25/data/DataHeader.h"
|
|
#include "common/p25/P25Defines.h"
|
|
#include "common/edac/CRC.h"
|
|
#include "common/Log.h"
|
|
#include "common/Utils.h"
|
|
|
|
using namespace p25;
|
|
using namespace p25::defines;
|
|
using namespace p25::lc;
|
|
using namespace p25::lc::tsbk;
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
TEST_CASE("TSBK", "[p25][tsbk]") {
|
|
SECTION("Constants_Valid") {
|
|
// Verify TSBK length constants
|
|
REQUIRE(P25_TSBK_LENGTH_BYTES == 12);
|
|
REQUIRE(P25_TSBK_FEC_LENGTH_BYTES == 25);
|
|
REQUIRE(P25_TSBK_FEC_LENGTH_BITS == (P25_TSBK_FEC_LENGTH_BYTES * 8 - 4)); // 196 bits (Trellis)
|
|
}
|
|
|
|
SECTION("System_Service_Broadcast_Known_Bytes") {
|
|
const SiteData previousSiteData = TSBK::getSiteData();
|
|
SiteData siteData;
|
|
siteData.setNetActive(true);
|
|
siteData.setWuidValidity(0x02U);
|
|
TSBK::setSiteData(siteData);
|
|
|
|
OSP_SYS_SRV_BCAST source;
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
source.encode(raw, true, true);
|
|
|
|
const uint32_t expectedServices = SYS_SRV_TRUNK | SystemService::NET_ACTIVE;
|
|
const uint32_t servicesAvailable = GET_UINT24(raw, 3U)
|
|
const uint32_t servicesSupported = GET_UINT24(raw, 6U)
|
|
CHECK(raw[2U] == 0x02U);
|
|
CHECK(servicesAvailable == expectedServices);
|
|
CHECK(servicesSupported == expectedServices);
|
|
CHECK(raw[9U] == 0x00U);
|
|
|
|
OSP_SYS_SRV_BCAST broadcast;
|
|
REQUIRE(broadcast.decode(raw, true));
|
|
CHECK(broadcast.getWUIDValidity() == 0x02U);
|
|
CHECK(broadcast.getServicesAvailable() == expectedServices);
|
|
CHECK(broadcast.getServicesSupported() == expectedServices);
|
|
CHECK(broadcast.getRequestPriority() == 0x00U);
|
|
|
|
TSBK::setSiteData(previousSiteData);
|
|
}
|
|
|
|
SECTION("Secondary_Control_Channel_Broadcast_Known_Bytes") {
|
|
OSP_SCCB source;
|
|
source.setSCCBRFSSId(0x21U);
|
|
source.setSCCBSiteId(0x43U);
|
|
source.setSCCBChnId1(0x1567U);
|
|
source.setSCCBSvcClass1(0xA5U);
|
|
source.setSCCBChnId2(0x2ABCU);
|
|
source.setSCCBSvcClass2(0x5AU);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES] = { 0U };
|
|
source.encode(raw, true, true);
|
|
|
|
const uint8_t expected[] = { 0x21U, 0x43U, 0x15U, 0x67U, 0xA5U, 0x2AU, 0xBCU, 0x5AU };
|
|
CHECK(::memcmp(raw + 2U, expected, sizeof(expected)) == 0);
|
|
|
|
OSP_SCCB decoded;
|
|
REQUIRE(decoded.decode(raw, true));
|
|
CHECK(decoded.getSCCBRFSSId() == 0x21U);
|
|
CHECK(decoded.getSCCBSiteId() == 0x43U);
|
|
CHECK(decoded.getSCCBChnId1() == 0x1567U);
|
|
CHECK(decoded.getSCCBSvcClass1() == 0xA5U);
|
|
CHECK(decoded.getSCCBChnId2() == 0x2ABCU);
|
|
CHECK(decoded.getSCCBSvcClass2() == 0x5AU);
|
|
}
|
|
|
|
SECTION("Secondary_Control_Channel_Broadcast_Explicit_Known_Bytes") {
|
|
OSP_SCCB_EXP source;
|
|
source.setSCCBRFSSId(0x21U);
|
|
source.setSCCBSiteId(0x43U);
|
|
source.setSCCBChnId1(0x01U);
|
|
source.setSCCBChnNo(0x567U);
|
|
source.setSCCBChnId2(0x02U);
|
|
source.setSCCBChnNo2(0xABCU);
|
|
source.setSCCBSvcClass(0xA5U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES] = { 0U };
|
|
source.encode(raw, true, true);
|
|
|
|
const uint8_t expected[] = { 0x21U, 0x43U, 0x15U, 0x67U, 0x00U, 0x2AU, 0xBCU, 0xA5U };
|
|
CHECK(::memcmp(raw + 2U, expected, sizeof(expected)) == 0);
|
|
|
|
OSP_SCCB_EXP decoded;
|
|
REQUIRE(decoded.decode(raw, true));
|
|
CHECK(decoded.getSCCBRFSSId() == 0x21U);
|
|
CHECK(decoded.getSCCBSiteId() == 0x43U);
|
|
CHECK(decoded.getSCCBChnId1() == 0x01U);
|
|
CHECK(decoded.getSCCBChnNo() == 0x567U);
|
|
CHECK(decoded.getSCCBChnId2() == 0x02U);
|
|
CHECK(decoded.getSCCBChnNo2() == 0xABCU);
|
|
CHECK(decoded.getSCCBSvcClass() == 0xA5U);
|
|
}
|
|
|
|
SECTION("Adjacent_Status_Broadcast_Known_Bytes") {
|
|
OSP_ADJ_STS_BCAST source;
|
|
source.setAdjSiteLRA(0x12U);
|
|
source.setAdjSiteCFVA(0x0BU);
|
|
source.setAdjSiteSysId(0x345U);
|
|
source.setAdjSiteRFSSId(0x67U);
|
|
source.setAdjSiteId(0x89U);
|
|
source.setAdjSiteChnId(0x0AU);
|
|
source.setAdjSiteChnNo(0xBCDU);
|
|
source.setAdjSiteSvcClass(0x5AU);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES] = { 0U };
|
|
source.encode(raw, true, true);
|
|
|
|
const uint8_t expected[] = { 0x12U, 0xB3U, 0x45U, 0x67U, 0x89U, 0xABU, 0xCDU, 0x5AU };
|
|
CHECK(::memcmp(raw + 2U, expected, sizeof(expected)) == 0);
|
|
|
|
OSP_ADJ_STS_BCAST decoded;
|
|
REQUIRE(decoded.decode(raw, true));
|
|
CHECK(decoded.getAdjSiteLRA() == 0x12U);
|
|
CHECK(decoded.getAdjSiteCFVA() == 0x0BU);
|
|
CHECK(decoded.getAdjSiteSysId() == 0x345U);
|
|
CHECK(decoded.getAdjSiteRFSSId() == 0x67U);
|
|
CHECK(decoded.getAdjSiteId() == 0x89U);
|
|
CHECK(decoded.getAdjSiteChnId() == 0x0AU);
|
|
CHECK(decoded.getAdjSiteChnNo() == 0xBCDU);
|
|
CHECK(decoded.getAdjSiteSvcClass() == 0x5AU);
|
|
}
|
|
|
|
SECTION("Group_Affiliation_Query_Known_Bytes") {
|
|
OSP_GRP_AFF_Q query;
|
|
query.setDstId(0x123456U);
|
|
query.setSrcId(WUID_FNE);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
query.encode(raw, true, true);
|
|
|
|
CHECK(raw[2U] == 0x00U);
|
|
CHECK(raw[3U] == 0x00U);
|
|
CHECK(raw[4U] == 0x12U);
|
|
CHECK(raw[5U] == 0x34U);
|
|
CHECK(raw[6U] == 0x56U);
|
|
CHECK(raw[7U] == 0xFFU);
|
|
CHECK(raw[8U] == 0xFFU);
|
|
CHECK(raw[9U] == 0xFCU);
|
|
|
|
OSP_GRP_AFF_Q decoded;
|
|
REQUIRE(decoded.decode(raw, true));
|
|
CHECK(decoded.getDstId() == 0x123456U);
|
|
CHECK(decoded.getSrcId() == WUID_FNE);
|
|
}
|
|
|
|
SECTION("Group_Affiliation_Query_Response_Known_Bytes") {
|
|
ISP_GRP_AFF_Q_RSP response;
|
|
response.setEmergency(true);
|
|
response.setAnnounceGroup(0x2468U);
|
|
response.setDstId(0x1357U);
|
|
response.setSrcId(0x123456U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
response.encode(raw, true, true);
|
|
|
|
CHECK(raw[2U] == 0x80U);
|
|
CHECK(raw[3U] == 0x24U);
|
|
CHECK(raw[4U] == 0x68U);
|
|
CHECK(raw[5U] == 0x13U);
|
|
CHECK(raw[6U] == 0x57U);
|
|
CHECK(raw[7U] == 0x12U);
|
|
CHECK(raw[8U] == 0x34U);
|
|
CHECK(raw[9U] == 0x56U);
|
|
|
|
ISP_GRP_AFF_Q_RSP decoded;
|
|
REQUIRE(decoded.decode(raw, true));
|
|
CHECK(decoded.getEmergency());
|
|
CHECK(decoded.getAnnounceGroup() == 0x2468U);
|
|
CHECK(decoded.getDstId() == 0x1357U);
|
|
CHECK(decoded.getSrcId() == 0x123456U);
|
|
}
|
|
|
|
SECTION("Emergency_Alarm_Request_Roundtrip") {
|
|
ISP_EMERG_ALRM_REQ source;
|
|
source.setSpecialInfo1(0x01U);
|
|
source.setSpecialInfo2(0x00U);
|
|
source.setDstId(0x2345U);
|
|
source.setSrcId(0x654321U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
source.encode(raw, true, true);
|
|
|
|
const uint16_t groupId = GET_UINT16(raw, 5U);
|
|
const uint32_t srcId = GET_UINT24(raw, 7U);
|
|
REQUIRE((raw[0U] & 0x3FU) == TSBKO::ISP_EMERG_ALRM_REQ);
|
|
REQUIRE(raw[2U] == 0x01U);
|
|
REQUIRE(raw[3U] == 0x00U);
|
|
REQUIRE(raw[4U] == 0x00U);
|
|
REQUIRE(groupId == 0x2345U);
|
|
REQUIRE(srcId == 0x654321U);
|
|
|
|
std::unique_ptr<TSBK> decoded = TSBKFactory::createTSBK(raw, true);
|
|
REQUIRE(decoded != nullptr);
|
|
ISP_EMERG_ALRM_REQ* alarm = dynamic_cast<ISP_EMERG_ALRM_REQ*>(decoded.get());
|
|
REQUIRE(alarm != nullptr);
|
|
CHECK(alarm->getEmergency());
|
|
CHECK(alarm->getSpecialInfo1() == 0x01U);
|
|
CHECK(alarm->getSpecialInfo2() == 0x00U);
|
|
CHECK(alarm->getDstId() == 0x2345U);
|
|
CHECK(alarm->getSrcId() == 0x654321U);
|
|
}
|
|
|
|
SECTION("Emergency_Alarm_Cancel_Roundtrip") {
|
|
ISP_CAN_SRV_REQ source;
|
|
source.setAIV(true);
|
|
source.setService(TSBKO::ISP_EMERG_ALRM_REQ);
|
|
source.setResponse(0U);
|
|
source.setDstId(0x800000U | 0x2345U);
|
|
source.setSrcId(0x654321U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
source.encode(raw, true, true);
|
|
|
|
std::unique_ptr<TSBK> decoded = TSBKFactory::createTSBK(raw, true);
|
|
REQUIRE(decoded != nullptr);
|
|
ISP_CAN_SRV_REQ* cancel = dynamic_cast<ISP_CAN_SRV_REQ*>(decoded.get());
|
|
REQUIRE(cancel != nullptr);
|
|
CHECK(cancel->getAIV());
|
|
CHECK(cancel->getService() == TSBKO::ISP_EMERG_ALRM_REQ);
|
|
CHECK(cancel->getResponse() == 0U);
|
|
CHECK(cancel->getDstId() == (0x800000U | 0x2345U));
|
|
CHECK(cancel->getSrcId() == 0x654321U);
|
|
}
|
|
|
|
SECTION("Emergency_Alarm_ACK_Uses_System_Default_Source") {
|
|
IOSP_ACK_RSP ack;
|
|
ack.setAIV(true);
|
|
ack.setEX(false);
|
|
ack.setService(TSBKO::ISP_EMERG_ALRM_REQ);
|
|
ack.setDstId(WUID_SYSTEM);
|
|
ack.setSrcId(0x654321U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
ack.encode(raw, true, true);
|
|
|
|
const uint32_t sourceId = GET_UINT24(raw, 4U);
|
|
const uint32_t targetId = GET_UINT24(raw, 7U);
|
|
CHECK((raw[2U] & 0xC0U) == 0x80U);
|
|
CHECK(targetId == 0x654321U);
|
|
CHECK(sourceId == WUID_SYSTEM);
|
|
}
|
|
|
|
SECTION("Message_Update_Uses_16_Bit_Message_Value") {
|
|
const uint16_t messageValues[] = { 0x0000U, 0x1234U, 0xFFFFU };
|
|
for (uint16_t messageValue : messageValues) {
|
|
IOSP_MSG_UPDT source;
|
|
source.setMessage(messageValue);
|
|
source.setDstId(0x123456U);
|
|
source.setSrcId(0x654321U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
source.encode(raw, true, true);
|
|
|
|
const uint16_t rawMessage = GET_UINT16(raw, 2U);
|
|
const uint32_t rawDstId = GET_UINT24(raw, 4U);
|
|
const uint32_t rawSrcId = GET_UINT24(raw, 7U);
|
|
CHECK(rawMessage == messageValue);
|
|
CHECK(rawDstId == 0x123456U);
|
|
CHECK(rawSrcId == 0x654321U);
|
|
|
|
std::unique_ptr<TSBK> decoded = TSBKFactory::createTSBK(raw, true);
|
|
REQUIRE(decoded != nullptr);
|
|
IOSP_MSG_UPDT* message = dynamic_cast<IOSP_MSG_UPDT*>(decoded.get());
|
|
REQUIRE(message != nullptr);
|
|
CHECK(message->getMessage() == messageValue);
|
|
CHECK(message->getDstId() == 0x123456U);
|
|
CHECK(message->getSrcId() == 0x654321U);
|
|
}
|
|
}
|
|
|
|
SECTION("Status_Update_Uses_16_Bit_Status_Value") {
|
|
const uint16_t statusValues[] = { 0x0000U, 0x1234U, 0xFFFFU };
|
|
for (uint16_t statusValue : statusValues) {
|
|
IOSP_STS_UPDT source;
|
|
source.setStatus(statusValue);
|
|
source.setDstId(0x123456U);
|
|
source.setSrcId(0x654321U);
|
|
|
|
uint8_t raw[P25_TSBK_LENGTH_BYTES];
|
|
::memset(raw, 0x00U, sizeof(raw));
|
|
source.encode(raw, true, true);
|
|
|
|
const uint16_t rawStatus = GET_UINT16(raw, 2U);
|
|
const uint32_t rawDstId = GET_UINT24(raw, 4U);
|
|
const uint32_t rawSrcId = GET_UINT24(raw, 7U);
|
|
CHECK(rawStatus == statusValue);
|
|
CHECK(rawDstId == 0x123456U);
|
|
CHECK(rawSrcId == 0x654321U);
|
|
|
|
std::unique_ptr<TSBK> decoded = TSBKFactory::createTSBK(raw, true);
|
|
REQUIRE(decoded != nullptr);
|
|
IOSP_STS_UPDT* status = dynamic_cast<IOSP_STS_UPDT*>(decoded.get());
|
|
REQUIRE(status != nullptr);
|
|
CHECK(status->getStatus() == statusValue);
|
|
CHECK(status->getDstId() == 0x123456U);
|
|
CHECK(status->getSrcId() == 0x654321U);
|
|
}
|
|
}
|
|
|
|
SECTION("Standard_Unconfirmed_MBT_TELE_INT_DIAL_REQ") {
|
|
const uint8_t counts[] = { 1U, 10U, 11U, 34U };
|
|
for (uint8_t count : counts) {
|
|
MBT_ISP_TELE_INT_DIAL_REQ source;
|
|
source.setSrcId(0x654321U);
|
|
source.setEmergency(true);
|
|
source.setEncrypted(true);
|
|
source.setDuplex(true);
|
|
source.setPacketMode(false);
|
|
source.setPriority(5U);
|
|
|
|
uint8_t expectedDigits[34U];
|
|
for (uint8_t i = 0U; i < count; i++)
|
|
expectedDigits[i] = i % 12U;
|
|
REQUIRE(source.setDialDigits(expectedDigits, count));
|
|
|
|
data::DataHeader header;
|
|
uint8_t raw[2U * P25_PDU_UNCONFIRMED_LENGTH_BYTES] = { 0U };
|
|
source.encodeMBT(header, raw);
|
|
|
|
CHECK(header.getFormat() == PDUFormatType::UNCONFIRMED);
|
|
CHECK(header.getSAP() == PDUSAP::TRUNK_CTRL);
|
|
CHECK_FALSE(header.getOutbound());
|
|
CHECK(header.getBlocksToFollow() == ((count <= 10U) ? 1U : 2U));
|
|
|
|
data::DataBlock blocks[2U];
|
|
for (uint8_t i = 0U; i < header.getBlocksToFollow(); i++) {
|
|
blocks[i].setFormat(header);
|
|
blocks[i].setData(raw + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES));
|
|
}
|
|
|
|
std::unique_ptr<AMBT> decoded = TSBKFactory::createAMBT(header, blocks);
|
|
REQUIRE(decoded != nullptr);
|
|
MBT_ISP_TELE_INT_DIAL_REQ* dial = dynamic_cast<MBT_ISP_TELE_INT_DIAL_REQ*>(decoded.get());
|
|
REQUIRE(dial != nullptr);
|
|
CHECK(dial->getLCO() == TSBKO::IOSP_TELE_INT_DIAL);
|
|
CHECK(dial->getSrcId() == source.getSrcId());
|
|
CHECK(dial->getDigitCount() == count);
|
|
CHECK(dial->getEmergency());
|
|
CHECK(dial->getEncrypted());
|
|
CHECK(dial->getDuplex());
|
|
CHECK_FALSE(dial->getPacketMode());
|
|
CHECK(dial->getPriority() == 5U);
|
|
|
|
uint8_t actualDigits[34U] = { 0U };
|
|
CHECK(dial->getDialDigits(actualDigits) == count);
|
|
CHECK(::memcmp(actualDigits, expectedDigits, count) == 0);
|
|
}
|
|
}
|
|
|
|
SECTION("RawTSBK_Encode_Decode_NoTrellis") {
|
|
g_logDisplayLevel = 1U;
|
|
|
|
// Test raw TSBK encoding/decoding without Trellis
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
// Create a test TSBK payload
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
// Set LCO (Link Control Opcode)
|
|
testTSBK[0] = 0x34; // Example LCO (OSP_SCCB)
|
|
testTSBK[1] = 0x00; // Mfg ID (standard)
|
|
|
|
tsbk1.setLCO(0x34U);
|
|
tsbk1.setMFId(0x00U);
|
|
|
|
// Set some payload data
|
|
for (uint32_t i = 2; i < P25_TSBK_LENGTH_BYTES - 2; i++) {
|
|
testTSBK[i] = (uint8_t)(i * 0x11);
|
|
}
|
|
|
|
// Add CRC
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
Utils::dump(2U, "testTSBK", testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
// Set the TSBK
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
// Encode (raw, no Trellis)
|
|
uint8_t encoded[P25_TSBK_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
Utils::dump(2U, "encoded", encoded, P25_TSBK_LENGTH_BYTES);
|
|
|
|
// Decode back and verify roundtrip
|
|
OSP_TSBK_RAW tsbk2;
|
|
bool result = tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(result == true);
|
|
REQUIRE(tsbk2.getLCO() == (testTSBK[0] & 0x3F));
|
|
REQUIRE(tsbk2.getMFId() == testTSBK[1]);
|
|
|
|
// Encode again and verify it matches the first encode
|
|
uint8_t encoded2[P25_TSBK_LENGTH_BYTES];
|
|
tsbk2.encode(encoded2, true, true);
|
|
|
|
for (uint32_t i = 0; i < P25_TSBK_LENGTH_BYTES; i++) {
|
|
REQUIRE(encoded2[i] == encoded[i]);
|
|
}
|
|
}
|
|
|
|
SECTION("RawTSBK_Encode_Decode_WithTrellis") {
|
|
g_logDisplayLevel = 1U;
|
|
|
|
// Test raw TSBK encoding/decoding with Trellis FEC
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
testTSBK[0] = 0x34; // LCO
|
|
testTSBK[1] = 0x00; // Mfg ID
|
|
|
|
// Set payload
|
|
testTSBK[2] = 0xAA;
|
|
testTSBK[3] = 0x55;
|
|
testTSBK[4] = 0xF0;
|
|
testTSBK[5] = 0x0F;
|
|
testTSBK[6] = 0xCC;
|
|
testTSBK[7] = 0x33;
|
|
testTSBK[8] = 0x12;
|
|
testTSBK[9] = 0x34;
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
Utils::dump(2U, "testTSBK", testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
// Encode with Trellis
|
|
uint8_t encoded[P25_TSDU_FRAME_LENGTH_BYTES];
|
|
tsbk1.encode(encoded);
|
|
|
|
// Decode with Trellis
|
|
OSP_TSBK_RAW tsbk2;
|
|
bool result = tsbk2.decode(encoded);
|
|
|
|
REQUIRE(result == true);
|
|
REQUIRE(tsbk2.getLCO() == (testTSBK[0] & 0x3F));
|
|
REQUIRE(tsbk2.getMFId() == testTSBK[1]);
|
|
}
|
|
|
|
SECTION("LastBlock_Flag") {
|
|
// Test Last Block Marker flag
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
// Set Last Block flag (bit 7 of byte 0)
|
|
testTSBK[0] = 0x80 | 0x34; // Last Block + LCO
|
|
testTSBK[1] = 0x00;
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
uint8_t encoded[P25_TSBK_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(tsbk2.getLastBlock() == true);
|
|
REQUIRE(tsbk2.getLCO() == 0x34);
|
|
}
|
|
|
|
SECTION("MfgId_Preservation") {
|
|
// Test Manufacturer ID preservation
|
|
uint8_t mfgIds[] = { 0x00, 0x01, 0x90, 0xFF };
|
|
|
|
for (auto mfgId : mfgIds) {
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
testTSBK[0] = 0x34; // LCO
|
|
testTSBK[1] = mfgId;
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
uint8_t encoded[P25_TSBK_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(tsbk2.getMFId() == mfgId);
|
|
}
|
|
}
|
|
|
|
SECTION("CRC_CCITT16_Validation") {
|
|
// Test CRC-CCITT16 validation
|
|
OSP_TSBK_RAW tsbk;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
testTSBK[0] = 0x34;
|
|
testTSBK[1] = 0x00;
|
|
testTSBK[2] = 0xAB;
|
|
testTSBK[3] = 0xCD;
|
|
|
|
// Add valid CRC
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
// Verify CRC is valid
|
|
bool crcValid = edac::CRC::checkCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
REQUIRE(crcValid == true);
|
|
|
|
// Corrupt the CRC
|
|
testTSBK[P25_TSBK_LENGTH_BYTES - 1] ^= 0xFF;
|
|
|
|
// Verify CRC is now invalid
|
|
crcValid = edac::CRC::checkCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
REQUIRE(crcValid == false);
|
|
}
|
|
|
|
SECTION("Payload_RoundTrip") {
|
|
// Test payload data round-trip
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
testTSBK[0] = 0x34;
|
|
testTSBK[1] = 0x00;
|
|
|
|
// Payload is bytes 2-9 (8 bytes)
|
|
uint8_t expectedPayload[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
|
|
::memcpy(testTSBK, expectedPayload, 8);
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
// Encode and decode
|
|
uint8_t encoded[P25_TSBK_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
tsbk2.decode(encoded, true);
|
|
|
|
// Get decoded raw data and verify payload
|
|
uint8_t* decoded = tsbk2.getDecodedRaw();
|
|
REQUIRE(decoded != nullptr);
|
|
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
REQUIRE(decoded[i + 2] == expectedPayload[i]);
|
|
}
|
|
}
|
|
|
|
SECTION("AllZeros_Pattern") {
|
|
// Test all-zeros pattern
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
uint8_t encoded[P25_TSBK_FEC_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
bool result = tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(result == true);
|
|
REQUIRE(tsbk2.getLCO() == 0x00);
|
|
REQUIRE(tsbk2.getMFId() == 0x00);
|
|
}
|
|
|
|
SECTION("RawTSBK_Reports_Invalid_CRC_But_Decodes") {
|
|
OSP_TSBK_RAW tsbk;
|
|
|
|
uint8_t rawTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(rawTSBK, 0x00U, P25_TSBK_LENGTH_BYTES);
|
|
rawTSBK[0] = 0x34;
|
|
rawTSBK[1] = 0x00;
|
|
edac::CRC::addCCITT162(rawTSBK, P25_TSBK_LENGTH_BYTES);
|
|
rawTSBK[P25_TSBK_LENGTH_BYTES - 1U] ^= 0xFFU;
|
|
|
|
REQUIRE_FALSE(edac::CRC::checkCCITT162(rawTSBK, P25_TSBK_LENGTH_BYTES, true));
|
|
REQUIRE(tsbk.decode(rawTSBK, true));
|
|
REQUIRE(tsbk.getLCO() == 0x34U);
|
|
REQUIRE(tsbk.getMFId() == 0x00U);
|
|
}
|
|
|
|
SECTION("AllOnes_Pattern") {
|
|
// Test all-ones pattern
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0xFF, P25_TSBK_LENGTH_BYTES);
|
|
|
|
// Keep LCO valid (only 6 bits)
|
|
testTSBK[0] = 0xFF; // Last Block + all LCO bits set
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
uint8_t encoded[P25_TSBK_FEC_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
bool result = tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(result == true);
|
|
REQUIRE(tsbk2.getLCO() == 0x3F); // Only 6 bits
|
|
REQUIRE(tsbk2.getLastBlock() == true);
|
|
}
|
|
|
|
SECTION("Alternating_Pattern") {
|
|
// Test alternating bit pattern
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
for (uint32_t i = 0; i < P25_TSBK_LENGTH_BYTES; i++) {
|
|
testTSBK[i] = (i % 2 == 0) ? 0xAA : 0x55;
|
|
}
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
uint8_t encoded[P25_TSBK_FEC_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
bool result = tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(result == true);
|
|
}
|
|
|
|
SECTION("LCO_Values") {
|
|
// Test various LCO values (6 bits)
|
|
uint8_t lcoValues[] = { 0x00, 0x01, 0x0F, 0x20, 0x34, 0x3F };
|
|
|
|
for (auto lco : lcoValues) {
|
|
OSP_TSBK_RAW tsbk1;
|
|
|
|
uint8_t testTSBK[P25_TSBK_LENGTH_BYTES];
|
|
::memset(testTSBK, 0x00, P25_TSBK_LENGTH_BYTES);
|
|
|
|
testTSBK[0] = lco & 0x3F; // Mask to 6 bits
|
|
testTSBK[1] = 0x00;
|
|
|
|
edac::CRC::addCCITT162(testTSBK, P25_TSBK_LENGTH_BYTES);
|
|
|
|
tsbk1.setTSBK(testTSBK);
|
|
|
|
uint8_t encoded[P25_TSBK_LENGTH_BYTES];
|
|
tsbk1.encode(encoded, true, true);
|
|
|
|
OSP_TSBK_RAW tsbk2;
|
|
tsbk2.decode(encoded, true);
|
|
|
|
REQUIRE(tsbk2.getLCO() == (lco & 0x3F));
|
|
}
|
|
}
|
|
}
|