rebuild r05a04_dev from nasty Git merge bullshit; implement handling of SNDCP on the FNE instead of dvmhost; add quick sanity Catch2 testcases; BUGFIX: NXDN SACCH was incorrectly handling the RAN and structure causing the structure value to become overwritten; correct badly set example IP range in FNE config; add AI generated documentation for the network statck, FNE REST and DVMHost REST; update version number for next dev version;

r05a04_dev
Bryan Biedenkapp 2 months ago
parent 274a8f23fc
commit c7b9f80335

@ -326,3 +326,12 @@ vtun:
netmask: 255.255.255.0
# Broadcast address of the tunnel network interface
broadcast: 192.168.1.255
#
# P25 SNDCP Dynamic IP Allocation
#
sndcp:
# Starting IP address for dynamic IP allocation pool
startAddress: 192.168.1.10
# Ending IP address for dynamic IP allocation pool
endAddress: 192.168.1.200

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -110,7 +110,7 @@ typedef unsigned long long ulong64_t;
#define __EXE_NAME__ ""
#define VERSION_MAJOR "05"
#define VERSION_MINOR "02"
#define VERSION_MINOR "04"
#define VERSION_REV "A"
#define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR

@ -136,18 +136,7 @@ CAC::~CAC()
CAC& CAC::operator=(const CAC& data)
{
if (&data != this) {
::memcpy(m_data, data.m_data, NXDN_CAC_CRC_LENGTH_BYTES);
m_ran = m_data[0U] & 0x3FU;
m_structure = (ChStructure::E)((m_data[0U] >> 6) & 0x03U);
m_longInbound = data.m_longInbound;
m_idleBusy = data.m_idleBusy;
m_txContinuous = data.m_txContinuous;
m_receive = data.m_receive;
m_rxCRC = data.m_rxCRC;
copy(data);
}
return *this;
@ -427,11 +416,15 @@ void CAC::setData(const uint8_t* data)
void CAC::copy(const CAC& data)
{
m_data = new uint8_t[NXDN_CAC_CRC_LENGTH_BYTES];
if (m_data == nullptr)
m_data = new uint8_t[NXDN_CAC_CRC_LENGTH_BYTES];
::memcpy(m_data, data.m_data, NXDN_CAC_CRC_LENGTH_BYTES);
m_ran = m_data[0U] & 0x3FU;
m_structure = (ChStructure::E)((m_data[0U] >> 6) & 0x03U);
m_ran = data.m_ran;
m_structure = data.m_structure;
m_data[0U] = m_ran;
m_data[0U] |= ((m_structure << 6) & 0xC0U);
m_longInbound = data.m_longInbound;

@ -76,7 +76,7 @@ FACCH1::~FACCH1()
FACCH1& FACCH1::operator=(const FACCH1& data)
{
if (&data != this) {
::memcpy(m_data, data.m_data, NXDN_FACCH1_CRC_LENGTH_BYTES);
copy(data);
}
return *this;
@ -226,6 +226,7 @@ void FACCH1::setData(const uint8_t* data)
void FACCH1::copy(const FACCH1& data)
{
m_data = new uint8_t[NXDN_FACCH1_CRC_LENGTH_BYTES];
if (m_data == nullptr)
m_data = new uint8_t[NXDN_FACCH1_CRC_LENGTH_BYTES];
::memcpy(m_data, data.m_data, NXDN_FACCH1_CRC_LENGTH_BYTES);
}

@ -57,12 +57,7 @@ LICH::~LICH() = default;
LICH& LICH::operator=(const LICH& data)
{
if (&data != this) {
m_lich = data.m_lich;
m_rfct = data.m_rfct;
m_fct = data.m_fct;
m_option = data.m_option;
m_outbound = data.m_outbound;
copy(data);
}
return *this;
@ -155,10 +150,10 @@ void LICH::copy(const LICH& data)
{
m_lich = data.m_lich;
m_rfct = (RFChannelType::E)((m_lich >> 6) & 0x03U);
m_fct = (FuncChannelType::E)((m_lich >> 4) & 0x03U);
m_option = (ChOption::E)((m_lich >> 2) & 0x03U);
m_outbound = ((m_lich >> 1) & 0x01U) == 0x01U;
m_rfct = data.m_rfct;
m_fct = data.m_fct;
m_option = data.m_option;
m_outbound = data.m_outbound;
}
/* Internal helper to generate the parity bit for the LICH. */

@ -73,10 +73,7 @@ SACCH::~SACCH()
SACCH& SACCH::operator=(const SACCH& data)
{
if (&data != this) {
::memcpy(m_data, data.m_data, NXDN_SACCH_CRC_LENGTH_BYTES);
m_ran = m_data[0U] & 0x3FU;
m_structure = (ChStructure::E)((m_data[0U] >> 6) & 0x03U);
copy(data);
}
return *this;
@ -161,11 +158,9 @@ void SACCH::encode(uint8_t* data) const
{
assert(data != nullptr);
m_data[0U] &= 0xC0U;
m_data[0U] |= m_ran;
m_data[0U] &= 0x3FU;
m_data[0U] |= (m_structure << 6) & 0xC0U;
// rebuild byte 0 from member variables: upper 2 bits = structure, lower 6 bits = RAN
m_data[0U] = (m_structure << 6) & 0xC0U; // set structure in upper 2 bits
m_data[0U] |= m_ran & 0x3FU; // set RAN in lower 6 bits
uint8_t buffer[NXDN_SACCH_CRC_LENGTH_BYTES];
::memset(buffer, 0x00U, NXDN_SACCH_CRC_LENGTH_BYTES);
@ -249,9 +244,14 @@ void SACCH::setData(const uint8_t* data)
void SACCH::copy(const SACCH& data)
{
m_data = new uint8_t[NXDN_SACCH_CRC_LENGTH_BYTES];
if (m_data == nullptr)
m_data = new uint8_t[NXDN_SACCH_CRC_LENGTH_BYTES];
::memcpy(m_data, data.m_data, NXDN_SACCH_CRC_LENGTH_BYTES);
m_ran = m_data[0U] & 0x3FU;
m_structure = (ChStructure::E)((m_data[0U] >> 6) & 0x03U);
m_ran = data.m_ran;
m_structure = data.m_structure;
// rebuild byte 0 from member variables: upper 2 bits = structure, lower 6 bits = RAN
m_data[0U] = (m_structure << 6) & 0xC0U; // set structure in upper 2 bits
m_data[0U] |= m_ran & 0x3FU; // set RAN in lower 6 bits
}

@ -100,9 +100,7 @@ UDCH::~UDCH()
UDCH& UDCH::operator=(const UDCH& data)
{
if (&data != this) {
::memcpy(m_data, data.m_data, NXDN_UDCH_CRC_LENGTH_BYTES);
m_ran = m_data[0U] & 0x3FU;
copy(data);
}
return *this;
@ -256,8 +254,10 @@ void UDCH::setData(const uint8_t* data)
void UDCH::copy(const UDCH& data)
{
m_data = new uint8_t[NXDN_UDCH_CRC_LENGTH_BYTES];
if (m_data == nullptr)
m_data = new uint8_t[NXDN_UDCH_CRC_LENGTH_BYTES];
::memcpy(m_data, data.m_data, NXDN_UDCH_CRC_LENGTH_BYTES);
m_ran = data.m_ran;
m_ran = m_data[0U] & 0x3FU;
}

@ -66,7 +66,7 @@ void OSP_TSBK_RAW::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
/* stub */
TSBK::encode(data, m_tsbk, rawTSBK, noTrellis);
TSBK::encode(data, m_tsbk + 2U, rawTSBK, noTrellis);
}
/* Sets the TSBK to encode. */

@ -138,6 +138,8 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port,
m_disablePacketData(false),
m_dumpPacketData(false),
m_verbosePacketData(false),
m_sndcpStartAddr(__IP_FROM_STR("10.10.1.10")),
m_sndcpEndAddr(__IP_FROM_STR("10.10.1.254")),
m_logDenials(false),
m_logUpstreamCallStartEnd(true),
m_reportPeerPing(reportPeerPing),
@ -259,6 +261,27 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
m_dumpPacketData = conf["dumpPacketData"].as<bool>(false);
m_verbosePacketData = conf["verbosePacketData"].as<bool>(false);
// SNDCP IP allocation configuration
m_sndcpStartAddr = __IP_FROM_STR("10.10.1.10");
m_sndcpEndAddr = __IP_FROM_STR("10.10.1.254");
yaml::Node& vtun = conf["vtun"];
if (vtun.size() > 0U) {
yaml::Node& sndcp = vtun["sndcp"];
if (sndcp.size() > 0U) {
std::string startAddrStr = sndcp["startAddress"].as<std::string>("10.10.1.10");
std::string endAddrStr = sndcp["endAddress"].as<std::string>("10.10.1.254");
m_sndcpStartAddr = __IP_FROM_STR(startAddrStr);
m_sndcpEndAddr = __IP_FROM_STR(endAddrStr);
if (m_sndcpStartAddr > m_sndcpEndAddr) {
LogWarning(LOG_MASTER, "SNDCP start address (%s) is greater than end address (%s), using defaults",
startAddrStr.c_str(), endAddrStr.c_str());
m_sndcpStartAddr = __IP_FROM_STR("10.10.1.10");
m_sndcpEndAddr = __IP_FROM_STR("10.10.1.254");
}
}
}
m_logDenials = conf["logDenials"].as<bool>(false);
m_logUpstreamCallStartEnd = conf["logUpstreamCallStartEnd"].as<bool>(true);

@ -399,6 +399,9 @@ namespace network
bool m_dumpPacketData;
bool m_verbosePacketData;
uint32_t m_sndcpStartAddr;
uint32_t m_sndcpEndAddr;
bool m_logDenials;
bool m_logUpstreamCallStartEnd;
bool m_reportPeerPing;

@ -32,6 +32,7 @@ using namespace p25::sndcp;
#include <cassert>
#include <chrono>
#include <unordered_set>
#if !defined(_WIN32)
#include <netinet/ip.h>
@ -900,7 +901,198 @@ bool P25PacketData::processSNDCPControl(RxStatus* status)
LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP context activation request, llId = %u, nsapi = %u, ipAddr = %s, nat = $%02X, dsut = $%02X, mdpco = $%02X", llId,
isp->getNSAPI(), __IP_FROM_UINT(isp->getIPAddress()).c_str(), isp->getNAT(), isp->getDSUT(), isp->getMDPCO());
m_arpTable[llId] = isp->getIPAddress();
// check if subscriber is provisioned (from RID table)
lookups::RadioId rid = m_network->m_ridLookup->find(llId);
if (rid.radioDefault() || !rid.radioEnabled()) {
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(isp->getNSAPI());
osp->setRejectCode(SNDCPRejectReason::SU_NOT_PROVISIONED);
osp->encode(txPduUserData);
// Build response header
data::DataHeader rspHeader;
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
rspHeader.calculateLength(2U);
dispatchUserFrameToFNE(rspHeader, false, false, txPduUserData);
LogWarning(LOG_P25, P25_PDU_STR ", SNDCP context activation reject, llId = %u, reason = SU_NOT_PROVISIONED", llId);
return true;
}
// handle different network address types
switch (isp->getNAT()) {
case SNDCPNAT::IPV4_STATIC_ADDR:
{
// get static IP from RID table
uint32_t staticIP = 0U;
if (!rid.radioDefault()) {
std::string addr = rid.radioIPAddress();
staticIP = __IP_FROM_STR(addr);
}
if (staticIP == 0U) {
// no static IP configured - reject
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(isp->getNSAPI());
osp->setRejectCode(SNDCPRejectReason::STATIC_IP_ALLOCATION_UNSUPPORTED);
osp->encode(txPduUserData);
data::DataHeader rspHeader;
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
rspHeader.calculateLength(2U);
dispatchUserFrameToFNE(rspHeader, false, false, txPduUserData);
LogWarning(LOG_P25, P25_PDU_STR ", SNDCP context activation reject, llId = %u, reason = STATIC_IP_ALLOCATION_UNSUPPORTED", llId);
return true;
}
// Accept with static IP
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<SNDCPCtxActAccept> osp = std::make_unique<SNDCPCtxActAccept>();
osp->setNSAPI(isp->getNSAPI());
osp->setPriority(4U);
osp->setReadyTimer(SNDCPReadyTimer::TEN_SECONDS);
osp->setStandbyTimer(SNDCPStandbyTimer::ONE_MINUTE);
osp->setNAT(SNDCPNAT::IPV4_STATIC_ADDR);
osp->setIPAddress(staticIP);
osp->setMTU(SNDCP_MTU_510);
osp->setMDPCO(isp->getMDPCO());
osp->encode(txPduUserData);
data::DataHeader rspHeader;
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
rspHeader.calculateLength(13U);
m_arpTable[llId] = staticIP;
m_readyForNextPkt[llId] = true;
dispatchUserFrameToFNE(rspHeader, false, false, txPduUserData);
LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP context activation accept, llId = %u, ipAddr = %s (static)",
llId, __IP_FROM_UINT(staticIP).c_str());
}
break;
case SNDCPNAT::IPV4_DYN_ADDR:
{
// allocate dynamic IP
uint32_t dynamicIP = allocateIPAddress(llId);
if (dynamicIP == 0U) {
// IP pool exhausted - reject
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(isp->getNSAPI());
osp->setRejectCode(SNDCPRejectReason::DYN_IP_POOL_EMPTY);
osp->encode(txPduUserData);
data::DataHeader rspHeader;
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
rspHeader.calculateLength(2U);
dispatchUserFrameToFNE(rspHeader, false, false, txPduUserData);
LogWarning(LOG_P25, P25_PDU_STR ", SNDCP context activation reject, llId = %u, reason = DYN_IP_POOL_EMPTY", llId);
return true;
}
// accept with dynamic IP
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<SNDCPCtxActAccept> osp = std::make_unique<SNDCPCtxActAccept>();
osp->setNSAPI(isp->getNSAPI());
osp->setPriority(4U);
osp->setReadyTimer(SNDCPReadyTimer::TEN_SECONDS);
osp->setStandbyTimer(SNDCPStandbyTimer::ONE_MINUTE);
osp->setNAT(SNDCPNAT::IPV4_DYN_ADDR);
osp->setIPAddress(dynamicIP);
osp->setMTU(SNDCP_MTU_510);
osp->setMDPCO(isp->getMDPCO());
osp->encode(txPduUserData);
data::DataHeader rspHeader;
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
rspHeader.calculateLength(13U);
m_arpTable[llId] = dynamicIP;
m_readyForNextPkt[llId] = true;
dispatchUserFrameToFNE(rspHeader, false, false, txPduUserData);
LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP context activation accept, llId = %u, ipAddr = %s (dynamic)",
llId, __IP_FROM_UINT(dynamicIP).c_str());
}
break;
default:
{
// unsupported NAT type - reject
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(isp->getNSAPI());
osp->setRejectCode(SNDCPRejectReason::ANY_REASON);
osp->encode(txPduUserData);
data::DataHeader rspHeader;
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
rspHeader.calculateLength(2U);
dispatchUserFrameToFNE(rspHeader, false, false, txPduUserData);
LogWarning(LOG_P25, P25_PDU_STR ", SNDCP context activation reject, llId = %u, reason = UNSUPPORTED_NAT", llId);
}
break;
}
}
break;
@ -911,6 +1103,11 @@ bool P25PacketData::processSNDCPControl(RxStatus* status)
isp->getDeactType());
m_arpTable.erase(llId);
m_readyForNextPkt.erase(llId);
// send ACK response
write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK,
status->assembler.dataHeader.getNs(), llId, false);
}
break;
@ -1149,3 +1346,64 @@ uint32_t P25PacketData::getLLIdAddress(uint32_t addr)
return 0U;
}
/* Helper to allocate a dynamic IP address for SNDCP. */
uint32_t P25PacketData::allocateIPAddress(uint32_t llId)
{
uint32_t existingIP = getIPAddress(llId);
if (existingIP != 0U) {
return existingIP;
}
// sequential allocation from configurable pool with uniqueness check
static uint32_t nextIP = 0U;
// initialize nextIP on first call
if (nextIP == 0U) {
nextIP = m_network->m_sndcpStartAddr;
}
// build set of already-allocated IPs to ensure uniqueness
std::unordered_set<uint32_t> allocatedIPs;
for (const auto& entry : m_arpTable) {
allocatedIPs.insert(entry.second);
}
// find next available IP not already in use
uint32_t candidateIP = nextIP;
const uint32_t poolSize = m_network->m_sndcpEndAddr - m_network->m_sndcpStartAddr + 1U;
uint32_t attempts = 0U;
while (allocatedIPs.find(candidateIP) != allocatedIPs.end() && attempts < poolSize) {
candidateIP++;
// wrap around if we exceed the end address
if (candidateIP > m_network->m_sndcpEndAddr) {
candidateIP = m_network->m_sndcpStartAddr;
}
attempts++;
}
if (attempts >= poolSize) {
LogError(LOG_P25, P25_PDU_STR ", SNDCP dynamic IP pool exhausted for llId = %u (pool: %s - %s)",
llId, __IP_FROM_UINT(m_network->m_sndcpStartAddr).c_str(), __IP_FROM_UINT(m_network->m_sndcpEndAddr).c_str());
return 0U; // Pool exhausted
}
// allocate the unique IP
uint32_t allocatedIP = candidateIP;
nextIP = candidateIP + 1U;
// wrap around for next allocation if needed
if (nextIP > m_network->m_sndcpEndAddr) {
nextIP = m_network->m_sndcpStartAddr;
}
m_arpTable[llId] = allocatedIP;
LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP allocated dynamic IP %s to llId = %u (pool: %s - %s)",
__IP_FROM_UINT(allocatedIP).c_str(), llId, __IP_FROM_UINT(m_network->m_sndcpStartAddr).c_str(), __IP_FROM_UINT(m_network->m_sndcpEndAddr).c_str());
return allocatedIP;
}

@ -295,6 +295,12 @@ namespace network
* @returns uint32_t Logical Link Address.
*/
uint32_t getLLIdAddress(uint32_t addr);
/**
* @brief Helper to allocate a dynamic IP address for SNDCP.
* @param llId Logical Link Address.
* @returns uint32_t Allocated IP address.
*/
uint32_t allocateIPAddress(uint32_t llId);
};
} // namespace packetdata
} // namespace callhandler

@ -52,7 +52,8 @@ using namespace nxdn::packet;
}
// Validate the target RID.
#define VALID_DSTID(_PCKT_STR, _PCKT, _DSTID, _RSN) \
// NOTE: Pass the source ID explicitly to ensure deny frames reference the correct originator.
#define VALID_DSTID(_PCKT_STR, _PCKT, _DSTID, _SRCID, _RSN) \
if (!acl::AccessControl::validateSrcId(_DSTID)) { \
LogWarning(LOG_RF, "NXDN, %s denial, RID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \
writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \

@ -624,21 +624,13 @@ void Data::clock(uint32_t ms)
{
// has the LLID reached ready state expiration?
if (std::find(sndcpReadyExpired.begin(), sndcpReadyExpired.end(), llId) != sndcpReadyExpired.end()) {
m_sndcpStateTable[llId] = SNDCPState::IDLE;
// transition to STANDBY per TIA-102 (preserves context)
m_sndcpStateTable[llId] = SNDCPState::STANDBY;
m_sndcpReadyTimers[llId].stop();
m_sndcpStandbyTimers[llId].start();
if (m_verbose) {
LogInfoEx(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), llId = %u", llId);
}
std::unique_ptr<lc::TDULC> lc = std::make_unique<lc::tdulc::LC_CALL_TERM>();
lc->setDstId(llId);
m_p25->m_control->writeRF_TDULC(lc.get(), true);
for (uint8_t i = 0U; i < 8U; i++) {
m_p25->writeRF_TDU(true);
}
if (m_p25->m_notifyCC) {
m_p25->notifyCC_ReleaseGrant(llId);
LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP state transition, llId = %u, READY_S -> STANDBY", llId);
}
}
}
@ -861,9 +853,6 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData)
return false;
}
uint8_t txPduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(txPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
std::unique_ptr<sndcp::SNDCPPacket> packet = SNDCPFactory::create(pduUserData);
if (packet == nullptr) {
LogWarning(LOG_RF, P25_PDU_STR ", undecodable SNDCP packet");
@ -880,98 +869,10 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData)
LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP context activation request, llId = %u, nsapi = %u, ipAddr = %s, nat = $%02X, dsut = $%02X, mdpco = $%02X", llId,
isp->getNSAPI(), __IP_FROM_UINT(isp->getIPAddress()).c_str(), isp->getNAT(), isp->getDSUT(), isp->getMDPCO());
}
m_p25->writeRF_Preamble();
DataHeader rspHeader = DataHeader();
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(MFG_STANDARD);
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA);
rspHeader.setNs(m_rfAssembler->dataHeader.getNs());
rspHeader.setLLId(llId);
rspHeader.setBlocksToFollow(1U);
// initialize SNDCP state if not already done
if (!isSNDCPInitialized(llId)) {
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(DEFAULT_NSAPI);
osp->setRejectCode(SNDCPRejectReason::SU_NOT_PROVISIONED);
osp->encode(txPduUserData);
rspHeader.calculateLength(2U);
writeRF_PDU_User(rspHeader, false, false, txPduUserData);
return true;
}
// which network address type is this?
switch (isp->getNAT()) {
case SNDCPNAT::IPV4_STATIC_ADDR:
{
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(DEFAULT_NSAPI);
osp->setRejectCode(SNDCPRejectReason::STATIC_IP_ALLOCATION_UNSUPPORTED);
osp->encode(txPduUserData);
rspHeader.calculateLength(2U);
writeRF_PDU_User(rspHeader, false, false, txPduUserData);
sndcpReset(llId, true);
}
break;
case SNDCPNAT::IPV4_DYN_ADDR:
{
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(DEFAULT_NSAPI);
osp->setRejectCode(SNDCPRejectReason::DYN_IP_ALLOCATION_UNSUPPORTED);
osp->encode(txPduUserData);
rspHeader.calculateLength(2U);
writeRF_PDU_User(rspHeader, false, false, txPduUserData);
sndcpReset(llId, true);
// TODO TODO TODO
/*
std::unique_ptr<SNDCPCtxActAccept> osp = std::make_unique<SNDCPCtxActAccept>();
osp->setNSAPI(DEFAULT_NSAPI);
osp->setReadyTimer(SNDCPReadyTimer::TEN_SECONDS);
osp->setStandbyTimer(SNDCPStandbyTimer::ONE_MINUTE);
osp->setNAT(SNDCPNAT::IPV4_DYN_ADDR);
osp->setIPAddress(__IP_FROM_STR(std::string("10.10.1.10")));
osp->setMTU(SNDCP_MTU_510);
osp->setMDPCO(isp->getMDPCO());
osp->encode(txPduUserData);
rspHeader.calculateLength(13U);
writeRF_PDU_User(rspHeader, rspHeader, false, txPduUserData);
m_sndcpStateTable[llId] = SNDCPState::STANDBY;
m_sndcpReadyTimers[llId].stop();
m_sndcpStandbyTimers[llId].start();
*/
}
break;
default:
{
std::unique_ptr<SNDCPCtxActReject> osp = std::make_unique<SNDCPCtxActReject>();
osp->setNSAPI(DEFAULT_NSAPI);
osp->setRejectCode(SNDCPRejectReason::ANY_REASON);
osp->encode(txPduUserData);
rspHeader.calculateLength(2U);
writeRF_PDU_User(rspHeader, false, false, txPduUserData);
sndcpReset(llId, true);
}
break;
sndcpInitialize(llId);
}
}
break;
@ -983,20 +884,21 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData)
LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP context deactivation request, llId = %u, deactType = %02X", llId,
isp->getDeactType());
}
writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfAssembler->dataHeader.getNs(), llId, false);
sndcpReset(llId, true);
// reset local SNDCP state (FNE will handle response)
sndcpReset(llId, false); // don't send CALL_TERM here
}
break;
default:
{
LogError(LOG_RF, P25_PDU_STR ", unhandled SNDCP PDU Type, pduType = $%02X", packet->getPDUType());
sndcpReset(llId, true);
}
break;
} // switch (packet->getPDUType())
// always forward SNDCP control to FNE for processing
// FNE will generate the accept/reject response
return true;
}

@ -1166,7 +1166,7 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply,
return;
}
if (slot == 0U && slot >= 3U) {
if (slot == 0U || slot >= 3U) {
errorPayload(reply, "invalid DMR slot number (slot == 0 or slot > 3)");
return;
}
@ -1212,7 +1212,7 @@ void RESTAPI::restAPI_GetDMRCCEnable(const HTTPPayload& request, HTTPPayload& re
}
m_host->m_dmrCtrlChannel = !m_host->m_dmrCtrlChannel;
errorPayload(reply, string_format("DMR CC is %s", m_host->m_p25CtrlChannel ? "enabled" : "disabled"), HTTPPayload::OK);
errorPayload(reply, string_format("DMR CC is %s", m_host->m_dmrCtrlChannel ? "enabled" : "disabled"), HTTPPayload::OK);
}
else {
errorPayload(reply, "DMR control data is not enabled!");
@ -1315,7 +1315,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPPayload& request, HTTPPayload& reply
setResponseDefaultStatus(response);
errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) {
if (m_p25 != nullptr) {
if (match.size() <= 1) {
bool debug = m_p25->getDebug();
bool verbose = m_p25->getVerbose();
@ -1689,7 +1689,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPPayload& request, HTTPPayload& repl
setResponseDefaultStatus(response);
errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) {
if (m_nxdn != nullptr) {
if (match.size() <= 1) {
bool debug = m_nxdn->getDebug();
bool verbose = m_nxdn->getVerbose();
@ -1726,7 +1726,7 @@ void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPPayload& request, HTTPPayload& r
setResponseDefaultStatus(response);
errorPayload(reply, "OK", HTTPPayload::OK);
if (m_p25 != nullptr) {
if (m_nxdn != nullptr) {
if (match.size() <= 1) {
bool rcchDump = m_nxdn->getRCCHVerbose();

@ -4,7 +4,7 @@
# * GPLv2 Open Source. Use is subject to license terms.
# * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# *
# * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
# * Copyright (C) 2022,2024,2025 Bryan Biedenkapp, N2PLL
# * Copyright (C) 2022 Natalie Moore
# *
# */
@ -12,6 +12,7 @@ file(GLOB dvmtests_SRC
"tests/*.h"
"tests/*.cpp"
"tests/crypto/*.cpp"
"tests/dmr/*.cpp"
"tests/edac/*.cpp"
"tests/p25/*.cpp"
"tests/nxdn/*.cpp"

@ -18,50 +18,48 @@ using namespace crypto;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES", "[Crypto Test]") {
SECTION("AES_Crypto_Test") {
bool failed = false;
TEST_CASE("AES Crypto Test", "[aes][crypto_test]") {
bool failed = false;
INFO("AES Crypto Test");
INFO("AES Crypto Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
// key (K)
uint8_t K[32] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// key (K)
uint8_t K[32] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// message
uint8_t message[48] =
{
0x90, 0x56, 0x00, 0x00, 0x2D, 0x75, 0xE6, 0x8D, 0x00, 0x89, 0x69, 0xCF, 0x00, 0xFE, 0x00, 0x04,
0x4F, 0xC7, 0x60, 0xFF, 0x30, 0x3E, 0x2B, 0xAD, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x08,
0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// message
uint8_t message[48] =
{
0x90, 0x56, 0x00, 0x00, 0x2D, 0x75, 0xE6, 0x8D, 0x00, 0x89, 0x69, 0xCF, 0x00, 0xFE, 0x00, 0x04,
0x4F, 0xC7, 0x60, 0xFF, 0x30, 0x3E, 0x2B, 0xAD, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x08,
0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// perform crypto
AES* aes = new AES(AESKeyLength::AES_256);
// perform crypto
AES* aes = new AES(AESKeyLength::AES_256);
Utils::dump(2U, "AES_Crypto_Test, Message", message, 48);
Utils::dump(2U, "AES_Crypto_Test, Message", message, 48);
uint8_t* crypted = aes->encryptECB(message, 48 * sizeof(uint8_t), K);
Utils::dump(2U, "AES_Crypto_Test, Encrypted", crypted, 48);
uint8_t* crypted = aes->encryptECB(message, 48 * sizeof(uint8_t), K);
Utils::dump(2U, "AES_Crypto_Test, Encrypted", crypted, 48);
uint8_t* decrypted = aes->decryptECB(crypted, 48 * sizeof(uint8_t), K);
Utils::dump(2U, "AES_Crypto_Test, Decrypted", decrypted, 48);
uint8_t* decrypted = aes->decryptECB(crypted, 48 * sizeof(uint8_t), K);
Utils::dump(2U, "AES_Crypto_Test, Decrypted", decrypted, 48);
for (uint32_t i = 0; i < 48U; i++) {
if (decrypted[i] != message[i]) {
::LogError("T", "AES_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 48U; i++) {
if (decrypted[i] != message[i]) {
::LogError("T", "AES_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
delete aes;
REQUIRE(failed==false);
}
delete aes;
REQUIRE(failed==false);
}

@ -18,64 +18,62 @@ using namespace crypto;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_LLA", "[LLA AM1 Test]") {
SECTION("LLA_AM1_Test") {
bool failed = false;
INFO("AES P25 LLA AM1 Test");
/*
** TIA-102.AACE-A 6.6 AM1 Sample
*/
srand((unsigned int)time(NULL));
// key (K)
uint8_t K[16] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// result KS
uint8_t resultKS[16] =
{
0x05, 0x24, 0x30, 0xBD, 0xAF, 0x39, 0xE8, 0x2F,
0xD0, 0xDD, 0xD6, 0x98, 0xC0, 0x2F, 0xB0, 0x36
};
// RS
uint8_t RS[10] =
{
0x38, 0xAE, 0xC8, 0x29, 0x33, 0xB1, 0x7F, 0x80,
0x24, 0x9D
};
// expand RS to 16 bytes
uint8_t expandedRS[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRS[i] = 0x00U;
for (uint32_t i = 0; i < 10U; i++)
expandedRS[i] = RS[i];
Utils::dump(2U, "LLA_AM1_Test, Expanded RS", expandedRS, 16);
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* KS = aes->encryptECB(expandedRS, 16 * sizeof(uint8_t), K);
Utils::dump(2U, "LLA_AM1_Test, Const Result", resultKS, 16);
Utils::dump(2U, "LLA_AM1_Test, Result", KS, 16);
for (uint32_t i = 0; i < 16U; i++) {
if (KS[i] != resultKS[i]) {
::LogError("T", "LLA_AM1_Test, INVALID AT IDX %d", i);
failed = true;
}
TEST_CASE("AES LLA AM1 Test", "[aes][lla_am1]") {
bool failed = false;
INFO("AES P25 LLA AM1 Test");
/*
** TIA-102.AACE-A 6.6 AM1 Sample
*/
srand((unsigned int)time(NULL));
// key (K)
uint8_t K[16] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// result KS
uint8_t resultKS[16] =
{
0x05, 0x24, 0x30, 0xBD, 0xAF, 0x39, 0xE8, 0x2F,
0xD0, 0xDD, 0xD6, 0x98, 0xC0, 0x2F, 0xB0, 0x36
};
// RS
uint8_t RS[10] =
{
0x38, 0xAE, 0xC8, 0x29, 0x33, 0xB1, 0x7F, 0x80,
0x24, 0x9D
};
// expand RS to 16 bytes
uint8_t expandedRS[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRS[i] = 0x00U;
for (uint32_t i = 0; i < 10U; i++)
expandedRS[i] = RS[i];
Utils::dump(2U, "LLA_AM1_Test, Expanded RS", expandedRS, 16);
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* KS = aes->encryptECB(expandedRS, 16 * sizeof(uint8_t), K);
Utils::dump(2U, "LLA_AM1_Test, Const Result", resultKS, 16);
Utils::dump(2U, "LLA_AM1_Test, Result", KS, 16);
for (uint32_t i = 0; i < 16U; i++) {
if (KS[i] != resultKS[i]) {
::LogError("T", "LLA_AM1_Test, INVALID AT IDX %d", i);
failed = true;
}
delete aes;
REQUIRE(failed==false);
}
delete aes;
REQUIRE(failed==false);
}

@ -18,66 +18,64 @@ using namespace crypto;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_LLA", "[LLA AM2 Test]") {
SECTION("LLA_AM2_Test") {
bool failed = false;
INFO("AES P25 LLA AM2 Test");
/*
** TIA-102.AACE-A 6.6 AM2 Sample
*/
srand((unsigned int)time(NULL));
// key (KS)
uint8_t KS[16] =
{
0x05, 0x24, 0x30, 0xBD, 0xAF, 0x39, 0xE8, 0x2F,
0xD0, 0xDD, 0xD6, 0x98, 0xC0, 0x2F, 0xB0, 0x36
};
// RES1
uint8_t resultRES1[4] =
{
0x3E, 0x00, 0xFA, 0xA8
};
// RAND1
uint8_t RAND1[5] =
{
0x4D, 0x92, 0x5A, 0xF6, 0x08
};
// expand RAND1 to 16 bytes
uint8_t expandedRAND1[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRAND1[i] = 0x00U;
for (uint32_t i = 0; i < 5U; i++)
expandedRAND1[i] = RAND1[i];
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* aesOut = aes->encryptECB(expandedRAND1, 16 * sizeof(uint8_t), KS);
// reduce AES output
uint8_t RES1[4];
for (uint32_t i = 0; i < 4U; i++)
RES1[i] = aesOut[i];
Utils::dump(2U, "LLA_AM2_Test, Const Result", resultRES1, 4);
Utils::dump(2U, "LLA_AM2_Test, AES Out", aesOut, 16);
Utils::dump(2U, "LLA_AM2_Test, Result", RES1, 4);
for (uint32_t i = 0; i < 4U; i++) {
if (RES1[i] != resultRES1[i]) {
::LogError("T", "LLA_AM2_Test, INVALID AT IDX %d", i);
failed = true;
}
TEST_CASE("AES LLA AM2 Test", "[aes][lla_am2]") {
bool failed = false;
INFO("AES P25 LLA AM2 Test");
/*
** TIA-102.AACE-A 6.6 AM2 Sample
*/
srand((unsigned int)time(NULL));
// key (KS)
uint8_t KS[16] =
{
0x05, 0x24, 0x30, 0xBD, 0xAF, 0x39, 0xE8, 0x2F,
0xD0, 0xDD, 0xD6, 0x98, 0xC0, 0x2F, 0xB0, 0x36
};
// RES1
uint8_t resultRES1[4] =
{
0x3E, 0x00, 0xFA, 0xA8
};
// RAND1
uint8_t RAND1[5] =
{
0x4D, 0x92, 0x5A, 0xF6, 0x08
};
// expand RAND1 to 16 bytes
uint8_t expandedRAND1[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRAND1[i] = 0x00U;
for (uint32_t i = 0; i < 5U; i++)
expandedRAND1[i] = RAND1[i];
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* aesOut = aes->encryptECB(expandedRAND1, 16 * sizeof(uint8_t), KS);
// reduce AES output
uint8_t RES1[4];
for (uint32_t i = 0; i < 4U; i++)
RES1[i] = aesOut[i];
Utils::dump(2U, "LLA_AM2_Test, Const Result", resultRES1, 4);
Utils::dump(2U, "LLA_AM2_Test, AES Out", aesOut, 16);
Utils::dump(2U, "LLA_AM2_Test, Result", RES1, 4);
for (uint32_t i = 0; i < 4U; i++) {
if (RES1[i] != resultRES1[i]) {
::LogError("T", "LLA_AM2_Test, INVALID AT IDX %d", i);
failed = true;
}
delete aes;
REQUIRE(failed==false);
}
delete aes;
REQUIRE(failed==false);
}

@ -18,71 +18,69 @@ using namespace crypto;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_LLA", "[LLA AM3 Test]") {
SECTION("LLA_AM3_Test") {
bool failed = false;
INFO("AES P25 LLA AM3 Test");
/*
** TIA-102.AACE-A 6.6 AM3 Sample
*/
srand((unsigned int)time(NULL));
// key (K)
uint8_t K[16] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// result KS
uint8_t resultKS[16] =
{
0x69, 0xD5, 0xDC, 0x08, 0x02, 0x3C, 0x46, 0x52,
0xCC, 0x71, 0xD5, 0xCD, 0x1E, 0x74, 0xE1, 0x04
};
// RS
uint8_t RS[10] =
{
0x38, 0xAE, 0xC8, 0x29, 0x33, 0xB1, 0x7F, 0x80,
0x24, 0x9D
};
// expand RS to 16 bytes
uint8_t expandedRS[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRS[i] = 0x00U;
for (uint32_t i = 0; i < 10U; i++)
expandedRS[i] = RS[i];
Utils::dump(2U, "LLA_AM3_Test, Expanded RS", expandedRS, 16);
// complement RS
uint8_t complementRS[16];
for (uint32_t i = 0; i < 16U; i++)
complementRS[i] = ~expandedRS[i];
Utils::dump(2U, "LLA_AM3_Test, Complement RS", complementRS, 16);
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* KS = aes->encryptECB(complementRS, 16 * sizeof(uint8_t), K);
Utils::dump(2U, "LLA_AM3_Test, Const Result", resultKS, 16);
Utils::dump(2U, "LLA_AM3_Test, Result", KS, 16);
for (uint32_t i = 0; i < 16U; i++) {
if (KS[i] != resultKS[i]) {
::LogError("T", "LLA_AM3_Test, INVALID AT IDX %d", i);
failed = true;
}
TEST_CASE("AES LLA AM3 Test", "[aes][lla_am3]") {
bool failed = false;
INFO("AES P25 LLA AM3 Test");
/*
** TIA-102.AACE-A 6.6 AM3 Sample
*/
srand((unsigned int)time(NULL));
// key (K)
uint8_t K[16] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// result KS
uint8_t resultKS[16] =
{
0x69, 0xD5, 0xDC, 0x08, 0x02, 0x3C, 0x46, 0x52,
0xCC, 0x71, 0xD5, 0xCD, 0x1E, 0x74, 0xE1, 0x04
};
// RS
uint8_t RS[10] =
{
0x38, 0xAE, 0xC8, 0x29, 0x33, 0xB1, 0x7F, 0x80,
0x24, 0x9D
};
// expand RS to 16 bytes
uint8_t expandedRS[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRS[i] = 0x00U;
for (uint32_t i = 0; i < 10U; i++)
expandedRS[i] = RS[i];
Utils::dump(2U, "LLA_AM3_Test, Expanded RS", expandedRS, 16);
// complement RS
uint8_t complementRS[16];
for (uint32_t i = 0; i < 16U; i++)
complementRS[i] = ~expandedRS[i];
Utils::dump(2U, "LLA_AM3_Test, Complement RS", complementRS, 16);
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* KS = aes->encryptECB(complementRS, 16 * sizeof(uint8_t), K);
Utils::dump(2U, "LLA_AM3_Test, Const Result", resultKS, 16);
Utils::dump(2U, "LLA_AM3_Test, Result", KS, 16);
for (uint32_t i = 0; i < 16U; i++) {
if (KS[i] != resultKS[i]) {
::LogError("T", "LLA_AM3_Test, INVALID AT IDX %d", i);
failed = true;
}
delete aes;
REQUIRE(failed==false);
}
delete aes;
REQUIRE(failed==false);
}

@ -18,66 +18,64 @@ using namespace crypto;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_LLA", "[LLA AM4 Test]") {
SECTION("LLA_AM4_Test") {
bool failed = false;
INFO("AES P25 LLA AM4 Test");
/*
** TIA-102.AACE-A 6.6 AM4 Sample
*/
srand((unsigned int)time(NULL));
// key (KS)
uint8_t KS[16] =
{
0x69, 0xD5, 0xDC, 0x08, 0x02, 0x3C, 0x46, 0x52,
0xCC, 0x71, 0xD5, 0xCD, 0x1E, 0x74, 0xE1, 0x04
};
// RES2
uint8_t resultRES2[4] =
{
0xB3, 0xAD, 0x16, 0xE1
};
// RAND2
uint8_t RAND2[5] =
{
0x6E, 0x78, 0x4F, 0x75, 0xBD
};
// expand RAND2 to 16 bytes
uint8_t expandedRAND2[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRAND2[i] = 0x00U;
for (uint32_t i = 0; i < 5U; i++)
expandedRAND2[i] = RAND2[i];
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* aesOut = aes->encryptECB(expandedRAND2, 16 * sizeof(uint8_t), KS);
// reduce AES output
uint8_t RES2[4];
for (uint32_t i = 0; i < 4U; i++)
RES2[i] = aesOut[i];
Utils::dump(2U, "LLA_AM4_Test, Const Result", resultRES2, 4);
Utils::dump(2U, "LLA_AM4_Test, AES Out", aesOut, 16);
Utils::dump(2U, "LLA_AM4_Test, Result", RES2, 4);
for (uint32_t i = 0; i < 4U; i++) {
if (RES2[i] != resultRES2[i]) {
::LogError("T", "LLA_AM4_Test, INVALID AT IDX %d", i);
failed = true;
}
TEST_CASE("AES LLA AM4 Test", "[aes][lla_am4]") {
bool failed = false;
INFO("AES P25 LLA AM4 Test");
/*
** TIA-102.AACE-A 6.6 AM4 Sample
*/
srand((unsigned int)time(NULL));
// key (KS)
uint8_t KS[16] =
{
0x69, 0xD5, 0xDC, 0x08, 0x02, 0x3C, 0x46, 0x52,
0xCC, 0x71, 0xD5, 0xCD, 0x1E, 0x74, 0xE1, 0x04
};
// RES2
uint8_t resultRES2[4] =
{
0xB3, 0xAD, 0x16, 0xE1
};
// RAND2
uint8_t RAND2[5] =
{
0x6E, 0x78, 0x4F, 0x75, 0xBD
};
// expand RAND2 to 16 bytes
uint8_t expandedRAND2[16];
for (uint32_t i = 0; i < 16U; i++)
expandedRAND2[i] = 0x00U;
for (uint32_t i = 0; i < 5U; i++)
expandedRAND2[i] = RAND2[i];
// perform crypto
AES* aes = new AES(AESKeyLength::AES_128);
uint8_t* aesOut = aes->encryptECB(expandedRAND2, 16 * sizeof(uint8_t), KS);
// reduce AES output
uint8_t RES2[4];
for (uint32_t i = 0; i < 4U; i++)
RES2[i] = aesOut[i];
Utils::dump(2U, "LLA_AM4_Test, Const Result", resultRES2, 4);
Utils::dump(2U, "LLA_AM4_Test, AES Out", aesOut, 16);
Utils::dump(2U, "LLA_AM4_Test, Result", RES2, 4);
for (uint32_t i = 0; i < 4U; i++) {
if (RES2[i] != resultRES2[i]) {
::LogError("T", "LLA_AM4_Test, INVALID AT IDX %d", i);
failed = true;
}
delete aes;
REQUIRE(failed==false);
}
delete aes;
REQUIRE(failed==false);
}

@ -16,65 +16,63 @@
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_KEK", "[KEK Crypto Test]") {
SECTION("P25_AES_KEK_Crypto_Test") {
bool failed = false;
TEST_CASE("AES KEK Crypto Test", "[aes][p25_kek_crypto]") {
bool failed = false;
INFO("P25 AES256 KEK Crypto Test");
INFO("P25 AES256 KEK Crypto Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
// example data taken from TIA-102.AACA-C-2023 Section 14.3.3
// example data taken from TIA-102.AACA-C-2023 Section 14.3.3
// Encrypted Key Frame
uint8_t testWrappedKeyFrame[40U] =
{
0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4,
0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0,
0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80
};
// Encrypted Key Frame
uint8_t testWrappedKeyFrame[40U] =
{
0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4,
0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0,
0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80
};
// key (K)
uint8_t K[32U] =
{
0x49, 0x40, 0x02, 0xBF, 0x16, 0x31, 0x32, 0xA4, 0x21, 0xFB, 0xEF, 0x11, 0x7F, 0x98, 0x5A, 0x0C,
0xAA, 0xDD, 0xC2, 0x50, 0xA4, 0xC2, 0x19, 0x47, 0xD5, 0x93, 0xE6, 0xC0, 0x67, 0xDE, 0x40, 0x2C
};
// key (K)
uint8_t K[32U] =
{
0x49, 0x40, 0x02, 0xBF, 0x16, 0x31, 0x32, 0xA4, 0x21, 0xFB, 0xEF, 0x11, 0x7F, 0x98, 0x5A, 0x0C,
0xAA, 0xDD, 0xC2, 0x50, 0xA4, 0xC2, 0x19, 0x47, 0xD5, 0x93, 0xE6, 0xC0, 0x67, 0xDE, 0x40, 0x2C
};
// message
uint8_t message[32U] =
{
0x2A, 0x19, 0x38, 0xCD, 0x0B, 0x6B, 0x6B, 0xD0, 0xB7, 0x74, 0x56, 0x92, 0xFE, 0x19, 0x14, 0xF0,
0x38, 0x76, 0x61, 0x2F, 0xC2, 0x9D, 0x57, 0x77, 0x89, 0xA6, 0x2F, 0x65, 0xFA, 0x05, 0xEF, 0x83
};
// message
uint8_t message[32U] =
{
0x2A, 0x19, 0x38, 0xCD, 0x0B, 0x6B, 0x6B, 0xD0, 0xB7, 0x74, 0x56, 0x92, 0xFE, 0x19, 0x14, 0xF0,
0x38, 0x76, 0x61, 0x2F, 0xC2, 0x9D, 0x57, 0x77, 0x89, 0xA6, 0x2F, 0x65, 0xFA, 0x05, 0xEF, 0x83
};
Utils::dump(2U, "KEK_Crypto_Test, Key", K, 32);
Utils::dump(2U, "KEK_Crypto_Test, Message", message, 32);
Utils::dump(2U, "KEK_Crypto_Test, Key", K, 32);
Utils::dump(2U, "KEK_Crypto_Test, Message", message, 32);
p25::crypto::P25Crypto crypto;
p25::crypto::P25Crypto crypto;
UInt8Array wrappedKey = crypto.cryptAES_TEK(K, message, 32U);
UInt8Array wrappedKey = crypto.cryptAES_TEK(K, message, 32U);
Utils::dump(2U, "KEK_Crypto_Test, Wrapped", wrappedKey.get(), 40);
Utils::dump(2U, "KEK_Crypto_Test, Wrapped", wrappedKey.get(), 40);
for (uint32_t i = 0; i < 40U; i++) {
if (wrappedKey[i] != testWrappedKeyFrame[i]) {
::LogDebug("T", "P25_AES_KEK_Crypto_Test, WRAPPED INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 40U; i++) {
if (wrappedKey[i] != testWrappedKeyFrame[i]) {
::LogDebug("T", "P25_AES_KEK_Crypto_Test, WRAPPED INVALID AT IDX %d", i);
failed = true;
}
}
UInt8Array unwrappedKey = crypto.decryptAES_TEK(K, wrappedKey.get(), 40U);
UInt8Array unwrappedKey = crypto.decryptAES_TEK(K, wrappedKey.get(), 40U);
Utils::dump(2U, "KEK_Crypto_Test, Unwrapped", unwrappedKey.get(), 40);
Utils::dump(2U, "KEK_Crypto_Test, Unwrapped", unwrappedKey.get(), 40);
for (uint32_t i = 0; i < 32U; i++) {
if (unwrappedKey[i] != message[i]) {
::LogError("T", "P25_AES_KEK_Crypto_Test, UNWRAPPED INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 32U; i++) {
if (unwrappedKey[i] != message[i]) {
::LogError("T", "P25_AES_KEK_Crypto_Test, UNWRAPPED INVALID AT IDX %d", i);
failed = true;
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -19,117 +19,115 @@ using namespace p25::defines;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_MAC_CBC", "[AES256 MAC CBC-MAC Test]") {
SECTION("P25_MAC_CBC_Crypto_Test") {
bool failed = false;
TEST_CASE("AES MAC CBC-MAC Test", "[aes][p25_mac_cbc]") {
bool failed = false;
INFO("P25 AES256 MAC CBC-MAC Test");
srand((unsigned int)time(NULL));
// example data taken from TIA-102.AACA-C-2023 Section 14.3.4
// MAC TEK
uint8_t macTek[] =
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
// expected CBC key
uint8_t expectedCBC[] =
{
0x09, 0xE7, 0x11, 0x7B, 0x4E, 0x42, 0x06, 0xDE, 0xD3, 0x66, 0xEA, 0x5D, 0x69, 0x33, 0x01, 0xCA,
0x83, 0x21, 0xBC, 0xC2, 0x0F, 0xA5, 0x05, 0xDF, 0x12, 0x67, 0xDC, 0x2A, 0xE4, 0x58, 0xA0, 0x57
};
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x42, 0xA0, 0x91, 0x56, 0xF0, 0xD4, 0x72, 0x1C, 0x08, 0x84, 0x2F, 0x62, 0x40
};
uint8_t expectedMAC[8U];
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, TEK", macTek, 32U);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, DataBlock", dataBlock, 80U);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected CBC-MAC Key", expectedCBC, 32U);
uint16_t fullLength = 0U;
uint16_t messageLength = GET_UINT16(dataBlock, 1U);
fullLength = messageLength + 3U;
bool hasMN = ((dataBlock[3U] >> 4U) & 0x03U) == 0x02U;
uint8_t macType = (dataBlock[3U] >> 2U) & 0x03U;
::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, messageLength = %u, hasMN = %u, macType = $%02X", messageLength, hasMN, macType);
switch (macType) {
case KMM_MAC::DES_MAC:
{
uint8_t macLength = 4U;
::memset(expectedMAC, 0x00U, macLength);
INFO("P25 AES256 MAC CBC-MAC Test");
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
srand((unsigned int)time(NULL));
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
// example data taken from TIA-102.AACA-C-2023 Section 14.3.4
::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
// MAC TEK
uint8_t macTek[] =
case KMM_MAC::ENH_MAC:
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
uint8_t macLength = 8U;
::memset(expectedMAC, 0x00U, macLength);
// expected CBC key
uint8_t expectedCBC[] =
{
0x09, 0xE7, 0x11, 0x7B, 0x4E, 0x42, 0x06, 0xDE, 0xD3, 0x66, 0xEA, 0x5D, 0x69, 0x33, 0x01, 0xCA,
0x83, 0x21, 0xBC, 0xC2, 0x0F, 0xA5, 0x05, 0xDF, 0x12, 0x67, 0xDC, 0x2A, 0xE4, 0x58, 0xA0, 0x57
};
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x42, 0xA0, 0x91, 0x56, 0xF0, 0xD4, 0x72, 0x1C, 0x08, 0x84, 0x2F, 0x62, 0x40
};
uint8_t expectedMAC[8U];
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, TEK", macTek, 32U);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, DataBlock", dataBlock, 80U);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected CBC-MAC Key", expectedCBC, 32U);
uint16_t fullLength = 0U;
uint16_t messageLength = GET_UINT16(dataBlock, 1U);
fullLength = messageLength + 3U;
bool hasMN = ((dataBlock[3U] >> 4U) & 0x03U) == 0x02U;
uint8_t macType = (dataBlock[3U] >> 2U) & 0x03U;
::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, messageLength = %u, hasMN = %u, macType = $%02X", messageLength, hasMN, macType);
switch (macType) {
case KMM_MAC::DES_MAC:
{
uint8_t macLength = 4U;
::memset(expectedMAC, 0x00U, macLength);
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
case KMM_MAC::ENH_MAC:
{
uint8_t macLength = 8U;
::memset(expectedMAC, 0x00U, macLength);
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
case KMM_MAC::NO_MAC:
break;
default:
::LogError(LOG_P25, "P25_MAC_CBC_Crypto_Test, unknown KMM MAC inventory type value, macType = $%02X", macType);
break;
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
p25::crypto::P25Crypto crypto;
case KMM_MAC::NO_MAC:
break;
UInt8Array macKey = crypto.cryptAES_KMM_CBC_KDF(macTek, dataBlock, fullLength);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, CBC MAC Key", macKey.get(), 32U);
default:
::LogError(LOG_P25, "P25_MAC_CBC_Crypto_Test, unknown KMM MAC inventory type value, macType = $%02X", macType);
break;
}
for (uint32_t i = 0; i < 32U; i++) {
if (macKey[i] != expectedCBC[i]) {
::LogError("T", "P25_MAC_CBC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
}
p25::crypto::P25Crypto crypto;
UInt8Array mac = crypto.cryptAES_KMM_CBC(macKey.get(), dataBlock, fullLength);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, MAC", mac.get(), 8U);
UInt8Array macKey = crypto.cryptAES_KMM_CBC_KDF(macTek, dataBlock, fullLength);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, CBC MAC Key", macKey.get(), 32U);
for (uint32_t i = 0; i < 8U; i++) {
if (mac[i] != expectedMAC[i]) {
::LogError("T", "P25_MAC_CBC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 32U; i++) {
if (macKey[i] != expectedCBC[i]) {
::LogError("T", "P25_MAC_CBC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
}
UInt8Array mac = crypto.cryptAES_KMM_CBC(macKey.get(), dataBlock, fullLength);
Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, MAC", mac.get(), 8U);
REQUIRE(failed==false);
for (uint32_t i = 0; i < 8U; i++) {
if (mac[i] != expectedMAC[i]) {
::LogError("T", "P25_MAC_CBC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
}
REQUIRE(failed==false);
}

@ -19,117 +19,115 @@ using namespace p25::defines;
#include <stdlib.h>
#include <time.h>
TEST_CASE("AES_MAC_CMAC", "[AES256 MAC CMAC Test]") {
SECTION("P25_MAC_CMAC_Crypto_Test") {
bool failed = false;
TEST_CASE("AES MAC CMAC Test", "[aes][mac_cmac]") {
bool failed = false;
INFO("P25 AES256 MAC CMAC Test");
srand((unsigned int)time(NULL));
// example data taken from TIA-102.AACA-C-2023 Section 14.3.5.1
// MAC TEK
uint8_t macTek[] =
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
// expected CMAC key
uint8_t expectedCMAC[] =
{
0x5F, 0xB2, 0x91, 0xD0, 0x9E, 0xE3, 0x99, 0x1E, 0x13, 0x1A, 0x04, 0xB0, 0xE3, 0xA0, 0xBF, 0x58,
0xB4, 0xA1, 0xCE, 0x46, 0x10, 0x48, 0xEB, 0x14, 0xB4, 0x97, 0xAE, 0x95, 0x22, 0xD0, 0x0D, 0x31
};
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x21, 0x85, 0x22, 0x33, 0x41, 0xD9, 0x8A, 0x97, 0x08, 0x84, 0x2F, 0x62, 0x41
};
uint8_t expectedMAC[8U];
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, TEK", macTek, 32U);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, DataBlock", dataBlock, 80U);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected CMAC Key", expectedCMAC, 32U);
uint16_t fullLength = 0U;
uint16_t messageLength = GET_UINT16(dataBlock, 1U);
fullLength = messageLength + 3U;
bool hasMN = ((dataBlock[3U] >> 4U) & 0x03U) == 0x02U;
uint8_t macType = (dataBlock[3U] >> 2U) & 0x03U;
::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, messageLength = %u, hasMN = %u, macType = $%02X", messageLength, hasMN, macType);
switch (macType) {
case KMM_MAC::DES_MAC:
{
uint8_t macLength = 4U;
::memset(expectedMAC, 0x00U, macLength);
INFO("P25 AES256 MAC CMAC Test");
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
srand((unsigned int)time(NULL));
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
// example data taken from TIA-102.AACA-C-2023 Section 14.3.5.1
::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
// MAC TEK
uint8_t macTek[] =
case KMM_MAC::ENH_MAC:
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
uint8_t macLength = 8U;
::memset(expectedMAC, 0x00U, macLength);
// expected CMAC key
uint8_t expectedCMAC[] =
{
0x5F, 0xB2, 0x91, 0xD0, 0x9E, 0xE3, 0x99, 0x1E, 0x13, 0x1A, 0x04, 0xB0, 0xE3, 0xA0, 0xBF, 0x58,
0xB4, 0xA1, 0xCE, 0x46, 0x10, 0x48, 0xEB, 0x14, 0xB4, 0x97, 0xAE, 0x95, 0x22, 0xD0, 0x0D, 0x31
};
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x21, 0x85, 0x22, 0x33, 0x41, 0xD9, 0x8A, 0x97, 0x08, 0x84, 0x2F, 0x62, 0x41
};
uint8_t expectedMAC[8U];
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, TEK", macTek, 32U);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, DataBlock", dataBlock, 80U);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected CMAC Key", expectedCMAC, 32U);
uint16_t fullLength = 0U;
uint16_t messageLength = GET_UINT16(dataBlock, 1U);
fullLength = messageLength + 3U;
bool hasMN = ((dataBlock[3U] >> 4U) & 0x03U) == 0x02U;
uint8_t macType = (dataBlock[3U] >> 2U) & 0x03U;
::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, messageLength = %u, hasMN = %u, macType = $%02X", messageLength, hasMN, macType);
switch (macType) {
case KMM_MAC::DES_MAC:
{
uint8_t macLength = 4U;
::memset(expectedMAC, 0x00U, macLength);
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
case KMM_MAC::ENH_MAC:
{
uint8_t macLength = 8U;
::memset(expectedMAC, 0x00U, macLength);
uint8_t macAlgId = dataBlock[fullLength - 4U];
uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U);
uint8_t macFormat = dataBlock[fullLength - 1U];
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
case KMM_MAC::NO_MAC:
break;
default:
::LogError(LOG_P25, "P25_MAC_CMAC_Crypto_Test, unknown KMM MAC inventory type value, macType = $%02X", macType);
break;
::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength);
::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected MAC", expectedMAC, macLength);
}
break;
p25::crypto::P25Crypto crypto;
case KMM_MAC::NO_MAC:
break;
UInt8Array macKey = crypto.cryptAES_KMM_CMAC_KDF(macTek, dataBlock, fullLength, true);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, CMAC MAC Key", macKey.get(), 32U);
default:
::LogError(LOG_P25, "P25_MAC_CMAC_Crypto_Test, unknown KMM MAC inventory type value, macType = $%02X", macType);
break;
}
for (uint32_t i = 0; i < 32U; i++) {
if (macKey[i] != expectedCMAC[i]) {
::LogError("T", "P25_MAC_CMAC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
}
p25::crypto::P25Crypto crypto;
UInt8Array mac = crypto.cryptAES_KMM_CMAC(expectedCMAC/* macKey.get()*/, dataBlock, fullLength);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, MAC", mac.get(), 8U);
UInt8Array macKey = crypto.cryptAES_KMM_CMAC_KDF(macTek, dataBlock, fullLength, true);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, CMAC MAC Key", macKey.get(), 32U);
for (uint32_t i = 0; i < 8U; i++) {
if (mac[i] != expectedMAC[i]) {
::LogError("T", "P25_MAC_CMAC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 32U; i++) {
if (macKey[i] != expectedCMAC[i]) {
::LogError("T", "P25_MAC_CMAC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
}
UInt8Array mac = crypto.cryptAES_KMM_CMAC(expectedCMAC/* macKey.get()*/, dataBlock, fullLength);
Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, MAC", mac.get(), 8U);
REQUIRE(failed==false);
for (uint32_t i = 0; i < 8U; i++) {
if (mac[i] != expectedMAC[i]) {
::LogError("T", "P25_MAC_CMAC_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
}
REQUIRE(failed==false);
}

@ -18,48 +18,46 @@ using namespace crypto;
#include <stdlib.h>
#include <time.h>
TEST_CASE("RC4", "[Crypto Test]") {
SECTION("RC4_Crypto_Test") {
bool failed = false;
TEST_CASE("RC4 Crypto Test", "[rc4][crypto_test]") {
bool failed = false;
INFO("RC4 Crypto Test");
INFO("RC4 Crypto Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
// key (K)
uint8_t K[13] =
{
0x00, 0x01, 0x02, 0x03, 0x04, // Selectable Key
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 // MI
};
// key (K)
uint8_t K[13] =
{
0x00, 0x01, 0x02, 0x03, 0x04, // Selectable Key
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 // MI
};
// message
uint8_t message[48] =
{
0x90, 0x56, 0x00, 0x00, 0x2D, 0x75, 0xE6, 0x8D, 0x00, 0x89, 0x69, 0xCF, 0x00, 0xFE, 0x00, 0x04,
0x4F, 0xC7, 0x60, 0xFF, 0x30, 0x3E, 0x2B, 0xAD, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x08,
0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// message
uint8_t message[48] =
{
0x90, 0x56, 0x00, 0x00, 0x2D, 0x75, 0xE6, 0x8D, 0x00, 0x89, 0x69, 0xCF, 0x00, 0xFE, 0x00, 0x04,
0x4F, 0xC7, 0x60, 0xFF, 0x30, 0x3E, 0x2B, 0xAD, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x08,
0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// perform crypto
RC4* rc4 = new RC4();
// perform crypto
RC4* rc4 = new RC4();
Utils::dump(2U, "RC4_Crypto_Test, Message", message, 48);
Utils::dump(2U, "RC4_Crypto_Test, Message", message, 48);
uint8_t* crypted = rc4->crypt(message, 48 * sizeof(uint8_t), K, 13);
Utils::dump(2U, "RC4_Crypto_Test, Encrypted", crypted, 48);
uint8_t* crypted = rc4->crypt(message, 48 * sizeof(uint8_t), K, 13);
Utils::dump(2U, "RC4_Crypto_Test, Encrypted", crypted, 48);
uint8_t* decrypted = rc4->crypt(crypted, 48 * sizeof(uint8_t), K, 13);
Utils::dump(2U, "RC4_Crypto_Test, Decrypted", decrypted, 48);
uint8_t* decrypted = rc4->crypt(crypted, 48 * sizeof(uint8_t), K, 13);
Utils::dump(2U, "RC4_Crypto_Test, Decrypted", decrypted, 48);
for (uint32_t i = 0; i < 48U; i++) {
if (decrypted[i] != message[i]) {
::LogError("T", "RC4_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 48U; i++) {
if (decrypted[i] != message[i]) {
::LogError("T", "RC4_Crypto_Test, INVALID AT IDX %d", i);
failed = true;
}
delete rc4;
REQUIRE(failed==false);
}
delete rc4;
REQUIRE(failed==false);
}

@ -0,0 +1,103 @@
// 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 <cstring>
#include "common/edac/BPTC19696.h"
using namespace edac;
TEST_CASE("BPTC19696 preserves all-zero payload", "[dmr][bptc19696]") {
uint8_t input[12U];
::memset(input, 0x00U, sizeof(input));
uint8_t encoded[196U];
BPTC19696 bptc;
bptc.encode(input, encoded);
uint8_t decoded[12U];
bptc.decode(encoded, decoded);
REQUIRE(::memcmp(input, decoded, 12U) == 0);
}
TEST_CASE("BPTC19696 preserves all-ones payload", "[dmr][bptc19696]") {
uint8_t input[12U];
::memset(input, 0xFFU, sizeof(input));
uint8_t encoded[196U];
BPTC19696 bptc;
bptc.encode(input, encoded);
uint8_t decoded[12U];
bptc.decode(encoded, decoded);
REQUIRE(::memcmp(input, decoded, 12U) == 0);
}
TEST_CASE("BPTC19696 preserves alternating bit pattern", "[dmr][bptc19696]") {
uint8_t input[12U];
for (size_t i = 0; i < 12U; i++) {
input[i] = (i % 2 == 0) ? 0xAAU : 0x55U;
}
uint8_t encoded[196U];
BPTC19696 bptc;
bptc.encode(input, encoded);
uint8_t decoded[12U];
bptc.decode(encoded, decoded);
REQUIRE(::memcmp(input, decoded, 12U) == 0);
}
TEST_CASE("BPTC19696 preserves incrementing pattern", "[dmr][bptc19696]") {
uint8_t input[12U];
for (size_t i = 0; i < 12U; i++) {
input[i] = (uint8_t)(i * 17); // Spread values across byte range
}
uint8_t encoded[196U];
BPTC19696 bptc;
bptc.encode(input, encoded);
uint8_t decoded[12U];
bptc.decode(encoded, decoded);
REQUIRE(::memcmp(input, decoded, 12U) == 0);
}
TEST_CASE("BPTC19696 corrects single-bit errors", "[dmr][bptc19696]") {
uint8_t input[12U];
for (size_t i = 0; i < 12U; i++) {
input[i] = 0x42U; // Specific pattern
}
uint8_t encoded[196U];
BPTC19696 bptc;
bptc.encode(input, encoded);
// Introduce single-bit error in various positions
const size_t errorPositions[] = {10, 50, 100, 150, 190};
for (auto pos : errorPositions) {
uint8_t corrupted[196U];
::memcpy(corrupted, encoded, 196U);
corrupted[pos] ^= 1U; // Flip one bit
uint8_t decoded[12U];
BPTC19696 bptcDec;
bptcDec.decode(corrupted, decoded);
// Should still match original (or be close - FEC corrects single errors)
// Note: This assumes BPTC can correct single-bit errors
REQUIRE(::memcmp(input, decoded, 12U) == 0);
}
}

@ -0,0 +1,384 @@
// 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);
}
}

@ -0,0 +1,138 @@
// 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 <cstring>
#include "common/dmr/DMRDefines.h"
#include "common/dmr/data/DataHeader.h"
using namespace dmr;
using namespace dmr::defines;
using namespace dmr::data;
TEST_CASE("DataHeader encodes and decodes UDT data", "[dmr][dataheader]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
DataHeader hdr;
hdr.setDPF(DPF::UDT);
hdr.setSAP(0x01U); // UDT SAP is 4 bits (0x0-0xF)
hdr.setGI(false);
hdr.setSrcId(1001U);
hdr.setDstId(2002U);
hdr.setBlocksToFollow(3U); // UDT blocks to follow is 2 bits + 1 (1-4 blocks)
hdr.encode(frame + 2U);
// Decode and verify
DataHeader decoded;
REQUIRE(decoded.decode(frame + 2U));
REQUIRE(decoded.getDPF() == DPF::UDT);
REQUIRE(decoded.getSAP() == 0x01U);
REQUIRE(decoded.getGI() == false);
REQUIRE(decoded.getSrcId() == 1001U);
REQUIRE(decoded.getDstId() == 2002U);
REQUIRE(decoded.getBlocksToFollow() == 3U);
}
TEST_CASE("DataHeader encodes and decodes unconfirmed data", "[dmr][dataheader]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
DataHeader hdr;
hdr.setDPF(DPF::UNCONFIRMED_DATA);
hdr.setSAP(0x00U); // SAP is 4 bits (0x0-0xF)
hdr.setGI(true);
hdr.setSrcId(5000U);
hdr.setDstId(9999U);
hdr.setBlocksToFollow(1U);
hdr.encode(frame + 2U);
// Decode and verify
DataHeader decoded;
REQUIRE(decoded.decode(frame + 2U));
REQUIRE(decoded.getDPF() == DPF::UNCONFIRMED_DATA);
REQUIRE(decoded.getSAP() == 0x00U);
REQUIRE(decoded.getGI() == true);
REQUIRE(decoded.getSrcId() == 5000U);
REQUIRE(decoded.getDstId() == 9999U);
REQUIRE(decoded.getBlocksToFollow() == 1U);
}
TEST_CASE("DataHeader handles response headers", "[dmr][dataheader]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
DataHeader hdr;
hdr.setDPF(DPF::RESPONSE);
hdr.setSAP(0x05U);
hdr.setGI(false);
hdr.setSrcId(3333U);
hdr.setDstId(4444U);
hdr.setResponseClass(PDUResponseClass::ACK);
hdr.setResponseType(PDUResponseType::ACK);
hdr.setResponseStatus(0x00U);
hdr.setBlocksToFollow(1U);
hdr.encode(frame + 2U);
// Decode and verify
DataHeader decoded;
REQUIRE(decoded.decode(frame + 2U));
REQUIRE(decoded.getDPF() == DPF::RESPONSE);
REQUIRE(decoded.getResponseClass() == PDUResponseClass::ACK);
REQUIRE(decoded.getResponseType() == PDUResponseType::ACK);
}
TEST_CASE("DataHeader preserves all SAP values", "[dmr][dataheader]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
// SAP is 4 bits, valid values are 0x0-0xF
const uint8_t sapValues[] = {0x00U, 0x02U, 0x0AU, 0x0DU, 0x0FU};
for (auto sap : sapValues) {
::memset(frame, 0x00U, sizeof(frame));
DataHeader hdr;
hdr.setDPF(DPF::UDT);
hdr.setSAP(sap);
hdr.setGI(true);
hdr.setSrcId(100U);
hdr.setDstId(200U);
hdr.setBlocksToFollow(2U);
hdr.encode(frame + 2U);
DataHeader decoded;
REQUIRE(decoded.decode(frame + 2U));
REQUIRE(decoded.getSAP() == sap);
}
}
TEST_CASE("DataHeader handles maximum blocks to follow", "[dmr][dataheader]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
DataHeader hdr;
hdr.setDPF(DPF::UNCONFIRMED_DATA); // Use UNCONFIRMED_DATA which has 7-bit blocks to follow
hdr.setSAP(0x00U);
hdr.setGI(true);
hdr.setSrcId(1U);
hdr.setDstId(1U);
hdr.setBlocksToFollow(127U); // Max value for 7-bit field
hdr.encode(frame + 2U);
DataHeader decoded;
REQUIRE(decoded.decode(frame + 2U));
REQUIRE(decoded.getBlocksToFollow() == 127U);
}

@ -0,0 +1,110 @@
// 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 <cstring>
#include <memory>
#include "common/dmr/lc/FullLC.h"
#include "common/dmr/lc/LC.h"
#include "common/dmr/DMRDefines.h"
using namespace dmr;
using namespace dmr::defines;
using namespace dmr::lc;
TEST_CASE("FullLC encodes and decodes VOICE_LC_HEADER for private call", "[dmr][fulllc]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
uint32_t srcId = 12345U;
uint32_t dstId = 54321U;
LC lc(FLCO::PRIVATE, srcId, dstId);
lc.setFID(0x10U);
FullLC fullLC;
fullLC.encode(lc, frame + 2U, DataType::VOICE_LC_HEADER);
// Decode and verify
std::unique_ptr<LC> decoded = fullLC.decode(frame + 2U, DataType::VOICE_LC_HEADER);
REQUIRE(decoded != nullptr);
REQUIRE(decoded->getFLCO() == FLCO::PRIVATE);
REQUIRE(decoded->getSrcId() == srcId);
REQUIRE(decoded->getDstId() == dstId);
REQUIRE(decoded->getFID() == 0x10U);
}
TEST_CASE("FullLC encodes and decodes VOICE_LC_HEADER for group call", "[dmr][fulllc]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
uint32_t srcId = 1001U;
uint32_t dstId = 9999U;
LC lc(FLCO::GROUP, srcId, dstId);
lc.setFID(0x00U);
FullLC fullLC;
fullLC.encode(lc, frame + 2U, DataType::VOICE_LC_HEADER);
// Decode and verify
std::unique_ptr<LC> decoded = fullLC.decode(frame + 2U, DataType::VOICE_LC_HEADER);
REQUIRE(decoded != nullptr);
REQUIRE(decoded->getFLCO() == FLCO::GROUP);
REQUIRE(decoded->getSrcId() == srcId);
REQUIRE(decoded->getDstId() == dstId);
}
TEST_CASE("FullLC encodes and decodes TERMINATOR_WITH_LC", "[dmr][fulllc]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
uint32_t srcId = 7777U;
uint32_t dstId = 8888U;
LC lc(FLCO::GROUP, srcId, dstId);
lc.setFID(0x02U);
FullLC fullLC;
fullLC.encode(lc, frame + 2U, DataType::TERMINATOR_WITH_LC);
// Decode and verify
std::unique_ptr<LC> decoded = fullLC.decode(frame + 2U, DataType::TERMINATOR_WITH_LC);
REQUIRE(decoded != nullptr);
REQUIRE(decoded->getFLCO() == FLCO::GROUP);
REQUIRE(decoded->getSrcId() == srcId);
REQUIRE(decoded->getDstId() == dstId);
REQUIRE(decoded->getFID() == 0x02U);
}
TEST_CASE("FullLC preserves service options", "[dmr][fulllc]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
uint32_t srcId = 100U;
uint32_t dstId = 200U;
LC lc(FLCO::PRIVATE, srcId, dstId);
lc.setFID(0x01U);
lc.setEmergency(true);
lc.setEncrypted(true);
lc.setPriority(3U);
FullLC fullLC;
fullLC.encode(lc, frame + 2U, DataType::VOICE_LC_HEADER);
// Decode and verify options
std::unique_ptr<LC> decoded = fullLC.decode(frame + 2U, DataType::VOICE_LC_HEADER);
REQUIRE(decoded != nullptr);
REQUIRE(decoded->getEmergency() == true);
REQUIRE(decoded->getEncrypted() == true);
REQUIRE(decoded->getPriority() == 3U);
}

@ -0,0 +1,81 @@
// 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 <cstring>
#include "common/dmr/SlotType.h"
#include "common/dmr/DMRDefines.h"
using namespace dmr;
using namespace dmr::defines;
TEST_CASE("SlotType encodes and decodes DataType correctly", "[dmr][slottype]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
::memset(frame, 0x00U, sizeof(frame));
SlotType slotType;
slotType.setColorCode(5U);
slotType.setDataType(DataType::VOICE_LC_HEADER);
slotType.encode(frame + 2U);
// Decode and verify
SlotType decoded;
decoded.decode(frame + 2U);
REQUIRE(decoded.getColorCode() == 5U);
REQUIRE(decoded.getDataType() == DataType::VOICE_LC_HEADER);
}
TEST_CASE("SlotType handles all DataType values", "[dmr][slottype]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
const DataType::E types[] = {
DataType::VOICE_PI_HEADER,
DataType::VOICE_LC_HEADER,
DataType::TERMINATOR_WITH_LC,
DataType::CSBK,
DataType::DATA_HEADER,
DataType::RATE_12_DATA,
DataType::RATE_34_DATA,
DataType::IDLE,
DataType::RATE_1_DATA
};
for (auto type : types) {
::memset(frame, 0x00U, sizeof(frame));
SlotType slotType;
slotType.setColorCode(3U);
slotType.setDataType(type);
slotType.encode(frame + 2U);
SlotType decoded;
decoded.decode(frame + 2U);
REQUIRE(decoded.getColorCode() == 3U);
REQUIRE(decoded.getDataType() == type);
}
}
TEST_CASE("SlotType handles all valid ColorCode values", "[dmr][slottype]") {
uint8_t frame[DMR_FRAME_LENGTH_BYTES + 2U];
for (uint32_t cc = 0U; cc <= 15U; cc++) {
::memset(frame, 0x00U, sizeof(frame));
SlotType slotType;
slotType.setColorCode(cc);
slotType.setDataType(DataType::CSBK);
slotType.encode(frame + 2U);
SlotType decoded;
decoded.decode(frame + 2U);
REQUIRE(decoded.getColorCode() == cc);
}
}

@ -18,48 +18,46 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[12-bit Test]") {
SECTION("12_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 12-bit Test", "[crc][12bit]") {
bool failed = false;
INFO("CRC 12-bit CRC Test");
INFO("CRC 12-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
CRC::addCRC12(random, lenBits);
CRC::addCRC12(random, lenBits);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC12(), crc = $%04X", inCrc);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC12(), crc = $%04X", inCrc);
Utils::dump(2U, "12_Sanity_Test CRC", random, len);
Utils::dump(2U, "12_Sanity_Test CRC", random, len);
bool ret = CRC::checkCRC12(random, lenBits);
if (!ret) {
::LogError("T", "12_Sanity_Test, failed CRC12 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCRC12(random, lenBits);
if (!ret) {
::LogError("T", "12_Sanity_Test, failed CRC12 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCRC12(random, lenBits);
if (ret) {
::LogError("T", "12_Sanity_Test, failed CRC12 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCRC12(random, lenBits);
if (ret) {
::LogError("T", "12_Sanity_Test, failed CRC12 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,48 +18,46 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[15-bit Test]") {
SECTION("15_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 15-bit Test", "[crc][15bit]") {
bool failed = false;
INFO("CRC 15-bit CRC Test");
INFO("CRC 15-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
CRC::addCRC15(random, lenBits);
CRC::addCRC15(random, lenBits);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC15(), crc = $%04X", inCrc);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC15(), crc = $%04X", inCrc);
Utils::dump(2U, "15_Sanity_Test CRC", random, len);
Utils::dump(2U, "15_Sanity_Test CRC", random, len);
bool ret = CRC::checkCRC15(random, lenBits);
if (!ret) {
::LogError("T", "15_Sanity_Test, failed CRC15 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCRC15(random, lenBits);
if (!ret) {
::LogError("T", "15_Sanity_Test, failed CRC15 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCRC15(random, lenBits);
if (ret) {
::LogError("T", "15_Sanity_Test, failed CRC15 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCRC15(random, lenBits);
if (ret) {
::LogError("T", "15_Sanity_Test, failed CRC15 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,48 +18,46 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[16-bit Test]") {
SECTION("16_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 16-bit Test", "[crc][16bit]") {
bool failed = false;
INFO("CRC 16-bit CRC Test");
INFO("CRC 16-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
CRC::addCRC16(random, lenBits);
CRC::addCRC16(random, lenBits);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC16(), crc = $%04X", inCrc);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC16(), crc = $%04X", inCrc);
Utils::dump(2U, "16_Sanity_Test CRC", random, len);
Utils::dump(2U, "16_Sanity_Test CRC", random, len);
bool ret = CRC::checkCRC16(random, lenBits);
if (!ret) {
::LogError("T", "16_Sanity_Test, failed CRC16 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCRC16(random, lenBits);
if (!ret) {
::LogError("T", "16_Sanity_Test, failed CRC16 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCRC16(random, lenBits);
if (ret) {
::LogError("T", "16_Sanity_Test, failed CRC16 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCRC16(random, lenBits);
if (ret) {
::LogError("T", "16_Sanity_Test, failed CRC16 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,47 +18,45 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[32-bit Test]") {
SECTION("32_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 32-bit Test", "[crc][32bit]") {
bool failed = false;
INFO("CRC 32-bit CRC Test");
INFO("CRC 32-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 4U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 4U; i++) {
random[i] = rand();
}
CRC::addCRC32(random, len);
CRC::addCRC32(random, len);
uint32_t inCrc = (random[len - 4U] << 24) | (random[len - 3U] << 16) | (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC32(), crc = $%08X", inCrc);
uint32_t inCrc = (random[len - 4U] << 24) | (random[len - 3U] << 16) | (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC32(), crc = $%08X", inCrc);
Utils::dump(2U, "32_Sanity_Test CRC", random, len);
Utils::dump(2U, "32_Sanity_Test CRC", random, len);
bool ret = CRC::checkCRC32(random, len);
if (!ret) {
::LogError("T", "32_Sanity_Test, failed CRC32 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCRC32(random, len);
if (!ret) {
::LogError("T", "32_Sanity_Test, failed CRC32 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCRC32(random, len);
if (ret) {
::LogError("T", "32_Sanity_Test, failed CRC32 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCRC32(random, len);
if (ret) {
::LogError("T", "32_Sanity_Test, failed CRC32 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,48 +18,46 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[6-bit Test]") {
SECTION("6_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 6-bit Test", "[crc][6bit]") {
bool failed = false;
INFO("CRC 6-bit CRC Test");
INFO("CRC 6-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
const uint32_t lenBits = len * 8U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 1U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 1U; i++) {
random[i] = rand();
}
CRC::addCRC6(random, lenBits);
CRC::addCRC6(random, lenBits);
uint32_t inCrc = (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC6(), crc = $%02X", inCrc);
uint32_t inCrc = (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCRC6(), crc = $%02X", inCrc);
Utils::dump(2U, "6_Sanity_Test CRC", random, len);
Utils::dump(2U, "6_Sanity_Test CRC", random, len);
bool ret = CRC::checkCRC6(random, lenBits);
if (!ret) {
::LogError("T", "6_Sanity_Test, failed CRC6 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCRC6(random, lenBits);
if (!ret) {
::LogError("T", "6_Sanity_Test, failed CRC6 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCRC6(random, lenBits);
if (ret) {
::LogError("T", "6_Sanity_Test, failed CRC6 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCRC6(random, lenBits);
if (ret) {
::LogError("T", "6_Sanity_Test, failed CRC6 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,39 +18,37 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[8-bit Test]") {
SECTION("8_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 8-bit Test", "[crc][8bit]") {
bool failed = false;
INFO("CRC 8-bit CRC Test");
INFO("CRC 8-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len; i++) {
random[i] = rand();
}
uint8_t crc = CRC::crc8(random, len);
::LogInfoEx("T", "crc = %02X", crc);
uint8_t crc = CRC::crc8(random, len);
::LogInfoEx("T", "crc = %02X", crc);
Utils::dump(2U, "8_Sanity_Test CRC", random, len);
Utils::dump(2U, "8_Sanity_Test CRC", random, len);
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
uint8_t calc = CRC::crc8(random, len);
::LogInfoEx("T", "calc = %02X", calc);
if (crc == calc) {
::LogError("T", "8_Sanity_Test, failed CRC8 error check");
failed = true;
goto cleanup;
}
uint8_t calc = CRC::crc8(random, len);
::LogInfoEx("T", "calc = %02X", calc);
if (crc == calc) {
::LogError("T", "8_Sanity_Test, failed CRC8 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,44 +18,42 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[9-bit Test]") {
SECTION("9_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 9-bit Test", "[crc][9bit]") {
bool failed = false;
INFO("CRC 9-bit CRC Test");
INFO("CRC 9-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 18U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 18U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len; i++) {
random[i] = rand();
}
random[0U] = 0;
random[1U] = 0;
random[0U] = 0;
random[1U] = 0;
uint16_t crc = edac::CRC::createCRC9(random, 144U);
::LogInfoEx("T", "crc = %04X", crc);
uint16_t crc = edac::CRC::createCRC9(random, 144U);
::LogInfoEx("T", "crc = %04X", crc);
random[0U] = random[0U] + ((crc >> 8) & 0x01U);
random[1U] = (crc & 0xFFU);
random[0U] = random[0U] + ((crc >> 8) & 0x01U);
random[1U] = (crc & 0xFFU);
Utils::dump(2U, "9_Sanity_Test CRC", random, len);
Utils::dump(2U, "9_Sanity_Test CRC", random, len);
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
uint16_t calculated = edac::CRC::createCRC9(random, 144U);
if (((crc ^ calculated) == 0)/*|| ((crc ^ calculated) == 0x1FFU)*/) {
::LogError("T", "9_Sanity_Test, failed CRC9 error check");
failed = true;
goto cleanup;
}
uint16_t calculated = edac::CRC::createCRC9(random, 144U);
if (((crc ^ calculated) == 0)/*|| ((crc ^ calculated) == 0x1FFU)*/) {
::LogError("T", "9_Sanity_Test, failed CRC9 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,47 +18,45 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[16-bit CCITT-161 Test]") {
SECTION("CCITT-161_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 16-bit CCITT-161 Test", "[crc][ccitt_161]") {
bool failed = false;
INFO("CRC CCITT-161 16-bit CRC Test");
INFO("CRC CCITT-161 16-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
CRC::addCCITT161(random, len);
CRC::addCCITT161(random, len);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCCITT161(), crc = $%04X", inCrc);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCCITT161(), crc = $%04X", inCrc);
Utils::dump(2U, "CCITT-161_Sanity_Test CRC", random, len);
Utils::dump(2U, "CCITT-161_Sanity_Test CRC", random, len);
bool ret = CRC::checkCCITT161(random, len);
if (!ret) {
::LogError("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCCITT161(random, len);
if (!ret) {
::LogError("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCCITT161(random, len);
if (ret) {
::LogError("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCCITT161(random, len);
if (ret) {
::LogError("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -18,47 +18,45 @@ using namespace edac;
#include <stdlib.h>
#include <time.h>
TEST_CASE("CRC", "[16-bit CCITT-162 Test]") {
SECTION("CCITT-162_Sanity_Test") {
bool failed = false;
TEST_CASE("CRC 16-bit CCITT-162 Test", "[crc][ccitt_162]") {
bool failed = false;
INFO("CRC CCITT-162 16-bit CRC Test");
INFO("CRC CCITT-162 16-bit CRC Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
const uint32_t len = 32U;
uint8_t* random = (uint8_t*)malloc(len);
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < len - 2U; i++) {
random[i] = rand();
}
CRC::addCCITT162(random, len);
CRC::addCCITT162(random, len);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCCITT162(), crc = $%04X", inCrc);
uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0);
::LogInfoEx("T", "CRC::checkCCITT162(), crc = $%04X", inCrc);
Utils::dump(2U, "CCITT-162_Sanity_Test CRC", random, len);
Utils::dump(2U, "CCITT-162_Sanity_Test CRC", random, len);
bool ret = CRC::checkCCITT162(random, len);
if (!ret) {
::LogError("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 check");
failed = true;
goto cleanup;
}
bool ret = CRC::checkCCITT162(random, len);
if (!ret) {
::LogError("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 check");
failed = true;
goto cleanup;
}
random[10U] >>= 8;
random[11U] >>= 8;
random[10U] >>= 8;
random[11U] >>= 8;
ret = CRC::checkCCITT162(random, len);
if (ret) {
::LogError("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 error check");
failed = true;
goto cleanup;
}
ret = CRC::checkCCITT162(random, len);
if (ret) {
::LogError("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 error check");
failed = true;
goto cleanup;
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -0,0 +1,142 @@
// 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 "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/edac/RS634717.h"
using namespace edac;
TEST_CASE("RS241213 preserves all-zero payload", "[edac][rs241213]") {
uint8_t data[24U];
::memset(data, 0x00U, sizeof(data));
RS634717 rs;
rs.encode241213(data);
REQUIRE(rs.decode241213(data));
for (size_t i = 0; i < 9U; i++) {
REQUIRE(data[i] == 0x00U);
}
}
TEST_CASE("RS241213 preserves all-ones payload", "[edac][rs241213]") {
uint8_t data[24U];
::memset(data, 0xFFU, sizeof(data));
RS634717 rs;
rs.encode241213(data);
Utils::dump(2U, "encode241213()", data, 24U);
REQUIRE(rs.decode241213(data));
for (size_t i = 0; i < 9U; i++) {
REQUIRE(data[i] == 0xFFU);
}
}
TEST_CASE("RS241213 preserves alternating pattern", "[edac][rs241213]") {
uint8_t original[12U];
for (size_t i = 0; i < 12U; i++) {
original[i] = (i % 2 == 0) ? 0xAAU : 0x55U;
}
uint8_t data[24U];
::memcpy(data, original, 12U);
::memset(data + 12U, 0x00U, 12U);
RS634717 rs;
rs.encode241213(data);
Utils::dump(2U, "encode241213()", data, 24U);
REQUIRE(rs.decode241213(data));
// Verify data portion matches original
REQUIRE(::memcmp(data, original, 9U) == 0);
}
TEST_CASE("RS241213 preserves incrementing pattern", "[edac][rs241213]") {
uint8_t original[12U];
for (size_t i = 0; i < 12U; i++) {
original[i] = (uint8_t)(i * 21); // Spread across byte range
}
uint8_t data[24U];
::memcpy(data, original, 12U);
::memset(data + 12U, 0x00U, 12U);
RS634717 rs;
rs.encode241213(data);
Utils::dump(2U, "encode241213()", data, 24U);
REQUIRE(rs.decode241213(data));
REQUIRE(::memcmp(data, original, 9U) == 0);
}
TEST_CASE("RS241213 corrects single-byte errors", "[edac][rs241213]") {
uint8_t original[12U];
for (size_t i = 0; i < 12U; i++) {
original[i] = (uint8_t)(i + 100);
}
uint8_t data[24U];
::memcpy(data, original, 12U);
::memset(data + 12U, 0x00U, 12U);
RS634717 rs;
rs.encode241213(data);
Utils::dump(2U, "encode241213()", data, 24U);
// Introduce single-byte errors at various positions
const size_t errorPositions[] = {0, 5, 11, 15, 20};
for (auto pos : errorPositions) {
uint8_t corrupted[24U];
::memcpy(corrupted, data, 24U);
corrupted[pos] ^= 0xFFU; // Flip all bits in one byte
RS634717 rsDec;
bool decoded = rsDec.decode241213(corrupted);
// RS(24,12,13) can correct up to 6 symbol errors
if (decoded) {
REQUIRE(::memcmp(corrupted, original, 9U) == 0);
}
}
}
TEST_CASE("RS241213 detects uncorrectable errors", "[edac][rs241213]") {
uint8_t original[12U];
for (size_t i = 0; i < 12U; i++) {
original[i] = (uint8_t)(i * 17);
}
uint8_t data[24U];
::memcpy(data, original, 12U);
::memset(data + 12U, 0x00U, 12U);
RS634717 rs;
rs.encode241213(data);
Utils::dump(2U, "encode241213()", data, 9U);
// Introduce too many errors (beyond correction capability)
for (size_t i = 0; i < 10U; i++) {
data[i] ^= 0xFFU;
}
// Should fail to decode
bool result = rs.decode241213(data);
REQUIRE(!result);
}

@ -0,0 +1,143 @@
// 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 "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/edac/RS634717.h"
using namespace edac;
TEST_CASE("RS24169 preserves all-zero payload", "[edac][rs24169]") {
uint8_t data[24U];
::memset(data, 0x00U, sizeof(data));
RS634717 rs;
rs.encode24169(data);
Utils::dump(2U, "encode24169()", data, 24U);
REQUIRE(rs.decode24169(data));
// First 16 bytes should be zero (data portion)
for (size_t i = 0; i < 12U; i++) {
REQUIRE(data[i] == 0x00U);
}
}
TEST_CASE("RS24169 preserves all-ones payload", "[edac][rs24169]") {
uint8_t data[24U];
::memset(data, 0xFFU, sizeof(data));
RS634717 rs;
rs.encode24169(data);
Utils::dump(2U, "encode24169()", data, 24U);
REQUIRE(rs.decode24169(data));
// First 16 bytes should be 0xFF
for (size_t i = 0; i < 12U; i++) {
REQUIRE(data[i] == 0xFFU);
}
}
TEST_CASE("RS24169 preserves alternating pattern", "[edac][rs24169]") {
uint8_t original[16U];
for (size_t i = 0; i < 16U; i++) {
original[i] = (i % 2 == 0) ? 0xAAU : 0x55U;
}
uint8_t data[24U];
::memcpy(data, original, 16U);
::memset(data + 16U, 0x00U, 8U);
RS634717 rs;
rs.encode24169(data);
Utils::dump(2U, "encode24169()", data, 24U);
REQUIRE(rs.decode24169(data));
REQUIRE(::memcmp(data, original, 12U) == 0);
}
TEST_CASE("RS24169 preserves incrementing pattern", "[edac][rs24169]") {
uint8_t original[16U];
for (size_t i = 0; i < 16U; i++) {
original[i] = (uint8_t)(i * 16);
}
uint8_t data[24U];
::memcpy(data, original, 16U);
::memset(data + 16U, 0x00U, 8U);
RS634717 rs;
rs.encode24169(data);
Utils::dump(2U, "encode24169()", data, 24U);
REQUIRE(rs.decode24169(data));
REQUIRE(::memcmp(data, original, 12U) == 0);
}
TEST_CASE("RS24169 corrects single-byte errors", "[edac][rs24169]") {
uint8_t original[16U];
for (size_t i = 0; i < 16U; i++) {
original[i] = (uint8_t)(i + 50);
}
uint8_t data[24U];
::memcpy(data, original, 16U);
::memset(data + 16U, 0x00U, 8U);
RS634717 rs;
rs.encode24169(data);
Utils::dump(2U, "encode24169()", data, 24U);
// Introduce single-byte errors
const size_t errorPositions[] = {0, 8, 15, 18, 22};
for (auto pos : errorPositions) {
uint8_t corrupted[24U];
::memcpy(corrupted, data, 24U);
corrupted[pos] ^= 0xFFU;
RS634717 rsDec;
bool decoded = rsDec.decode24169(corrupted);
// RS(24,16,9) can correct up to 4 symbol errors
if (decoded) {
REQUIRE(::memcmp(corrupted, original, 12U) == 0);
}
}
}
TEST_CASE("RS24169 detects uncorrectable errors", "[edac][rs24169]") {
uint8_t original[16U];
for (size_t i = 0; i < 16U; i++) {
original[i] = (uint8_t)(i * 13);
}
uint8_t data[24U];
::memcpy(data, original, 16U);
::memset(data + 16U, 0x00U, 8U);
RS634717 rs;
rs.encode24169(data);
Utils::dump(2U, "encode24169()", data, 24U);
// Introduce too many errors
for (size_t i = 0; i < 8U; i++) {
data[i] ^= 0xFFU;
}
bool result = rs.decode24169(data);
REQUIRE(!result);
}

@ -0,0 +1,144 @@
// 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 "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/edac/RS634717.h"
using namespace edac;
TEST_CASE("RS362017 preserves all-zero payload", "[edac][rs362017]") {
uint8_t data[27U]; // 36 symbols * 6 bits = 216 bits = 27 bytes
::memset(data, 0x00U, sizeof(data));
RS634717 rs;
rs.encode362017(data);
Utils::dump(2U, "encode362017()", data, 27U);
REQUIRE(rs.decode362017(data));
// First 15 bytes (20 symbols * 6 bits = 120 bits) should be zero
for (size_t i = 0; i < 15U; i++) {
REQUIRE(data[i] == 0x00U);
}
}
TEST_CASE("RS362017 preserves all-ones payload", "[edac][rs362017]") {
uint8_t data[27U];
::memset(data, 0xFFU, sizeof(data));
RS634717 rs;
rs.encode362017(data);
Utils::dump(2U, "encode362017()", data, 27U);
REQUIRE(rs.decode362017(data));
// First 15 bytes should be 0xFF
for (size_t i = 0; i < 15U; i++) {
REQUIRE(data[i] == 0xFFU);
}
}
TEST_CASE("RS362017 preserves alternating pattern", "[edac][rs362017]") {
uint8_t original[27U];
for (size_t i = 0; i < 27U; i++) {
original[i] = (i % 2 == 0) ? 0xAAU : 0x55U;
}
uint8_t data[27U];
::memcpy(data, original, 27U);
RS634717 rs;
rs.encode362017(data);
Utils::dump(2U, "encode362017()", data, 27U);
REQUIRE(rs.decode362017(data));
// Verify first 15 bytes (data portion) match
REQUIRE(::memcmp(data, original, 15U) == 0);
}
TEST_CASE("RS362017 preserves incrementing pattern", "[edac][rs362017]") {
uint8_t original[27U];
for (size_t i = 0; i < 27U; i++) {
original[i] = (uint8_t)(i * 9);
}
uint8_t data[27U];
::memcpy(data, original, 27U);
RS634717 rs;
rs.encode362017(data);
Utils::dump(2U, "encode362017()", data, 27U);
REQUIRE(rs.decode362017(data));
REQUIRE(::memcmp(data, original, 15U) == 0);
}
TEST_CASE("RS362017 corrects symbol errors", "[edac][rs362017]") {
uint8_t original[27U];
for (size_t i = 0; i < 27U; i++) {
original[i] = (uint8_t)(i + 30);
}
uint8_t data[27U];
::memcpy(data, original, 27U);
RS634717 rs;
rs.encode362017(data);
Utils::dump(2U, "encode362017()", data, 27U);
// Save encoded data
uint8_t encoded[27U];
::memcpy(encoded, data, 27U);
// Introduce errors at various positions
const size_t errorPositions[] = {0, 5, 10, 15, 20};
for (auto pos : errorPositions) {
uint8_t corrupted[27U];
::memcpy(corrupted, encoded, 27U);
corrupted[pos] ^= 0x3FU; // Flip 6 bits (1 symbol)
RS634717 rsDec;
bool decoded = rsDec.decode362017(corrupted);
// RS(36,20,17) can correct up to 8 symbol errors
if (decoded) {
REQUIRE(::memcmp(corrupted, original, 15U) == 0);
}
}
}
TEST_CASE("RS362017 detects uncorrectable errors", "[edac][rs362017]") {
uint8_t original[27U];
for (size_t i = 0; i < 27U; i++) {
original[i] = (uint8_t)(i * 11);
}
uint8_t data[27U];
::memcpy(data, original, 27U);
RS634717 rs;
rs.encode362017(data);
Utils::dump(2U, "encode362017()", data, 27U);
// Introduce too many errors (beyond 8 symbol correction)
for (size_t i = 0; i < 12U; i++) {
data[i] ^= 0xFFU;
}
bool result = rs.decode362017(data);
REQUIRE(!result);
}

@ -20,36 +20,34 @@ using namespace nxdn::defines;
#include <catch2/catch_test_macros.hpp>
TEST_CASE("NXDN", "[AMBE FEC Test]") {
SECTION("NXDN_AMBEFEC_Test") {
bool failed = false;
TEST_CASE("NXDN AMBE FEC Test", "[nxdn][ambe_fec]") {
bool failed = false;
INFO("NXDN AMBE FEC FEC Test");
INFO("NXDN AMBE FEC FEC Test");
uint8_t testData[] = {
0xCDU, 0xF5U, 0x9DU, 0x5DU, 0xFCU, 0xFAU, 0x0AU, 0x6EU, 0x8AU, 0x23U, 0x56U, 0xE8U,
0x17U, 0x49U, 0xC6U, 0x58U, 0x89U, 0x30U, 0x1AU, 0xA5U, 0xF5U, 0xACU, 0x5AU, 0x6EU, 0xF8U, 0x09U, 0x3CU, 0x48U,
0x0FU, 0x4FU, 0xFDU, 0xCFU, 0x80U, 0xD5U, 0x77U, 0x0CU, 0xFEU, 0xE9U, 0x05U, 0xCEU, 0xE6U, 0x20U, 0xDFU, 0xFFU,
0x18U, 0x9CU, 0x2DU, 0xA9U
};
uint8_t testData[] = {
0xCDU, 0xF5U, 0x9DU, 0x5DU, 0xFCU, 0xFAU, 0x0AU, 0x6EU, 0x8AU, 0x23U, 0x56U, 0xE8U,
0x17U, 0x49U, 0xC6U, 0x58U, 0x89U, 0x30U, 0x1AU, 0xA5U, 0xF5U, 0xACU, 0x5AU, 0x6EU, 0xF8U, 0x09U, 0x3CU, 0x48U,
0x0FU, 0x4FU, 0xFDU, 0xCFU, 0x80U, 0xD5U, 0x77U, 0x0CU, 0xFEU, 0xE9U, 0x05U, 0xCEU, 0xE6U, 0x20U, 0xDFU, 0xFFU,
0x18U, 0x9CU, 0x2DU, 0xA9U
};
NXDNUtils::scrambler(testData);
NXDNUtils::scrambler(testData);
Utils::dump(2U, "NXDN AMBE FEC Test, descrambled test data", testData, NXDN_FRAME_LENGTH_BYTES);
Utils::dump(2U, "NXDN AMBE FEC Test, descrambled test data", testData, NXDN_FRAME_LENGTH_BYTES);
AMBEFEC fec = AMBEFEC();
AMBEFEC fec = AMBEFEC();
uint32_t errors = 0U;
uint32_t errors = 0U;
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U);
errors += fec.measureNXDNBER(testData + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U);
if (errors > 0)
failed = true;
if (errors > 0)
failed = true;
cleanup:
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -0,0 +1,191 @@
// 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 <cstring>
#include "common/nxdn/channel/FACCH1.h"
#include "common/nxdn/NXDNDefines.h"
using namespace nxdn;
using namespace nxdn::defines;
using namespace nxdn::channel;
TEST_CASE("FACCH1 encodes and decodes zeros", "[nxdn][facch1]") {
uint8_t dataIn[10U];
::memset(dataIn, 0x00U, sizeof(dataIn));
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0x00U, sizeof(frameData));
FACCH1 facch;
facch.setData(dataIn);
facch.encode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
// Decode and verify
FACCH1 decoded;
REQUIRE(decoded.decode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS));
uint8_t dataOut[10U];
decoded.getData(dataOut);
REQUIRE(::memcmp(dataIn, dataOut, 10U) == 0);
}
TEST_CASE("FACCH1 encodes and decodes ones", "[nxdn][facch1]") {
uint8_t dataIn[10U];
::memset(dataIn, 0xFFU, sizeof(dataIn));
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0x00U, sizeof(frameData));
FACCH1 facch;
facch.setData(dataIn);
facch.encode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
FACCH1 decoded;
REQUIRE(decoded.decode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS));
uint8_t dataOut[10U];
decoded.getData(dataOut);
REQUIRE(::memcmp(dataIn, dataOut, 10U) == 0);
}
TEST_CASE("FACCH1 encodes and decodes alternating pattern", "[nxdn][facch1]") {
uint8_t dataIn[10U] = {0xAAU, 0x55U, 0xAAU, 0x55U, 0xAAU, 0x55U, 0xAAU, 0x55U, 0xAAU, 0x55U};
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0x00U, sizeof(frameData));
FACCH1 facch;
facch.setData(dataIn);
facch.encode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
FACCH1 decoded;
REQUIRE(decoded.decode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS));
uint8_t dataOut[10U];
decoded.getData(dataOut);
REQUIRE(::memcmp(dataIn, dataOut, 10U) == 0);
}
TEST_CASE("FACCH1 handles sequential data patterns", "[nxdn][facch1]") {
const uint8_t patterns[][10] = {
{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99},
{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22},
{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66}
};
for (const auto& pattern : patterns) {
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0x00U, sizeof(frameData));
FACCH1 facch;
facch.setData(pattern);
facch.encode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
FACCH1 decoded;
REQUIRE(decoded.decode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS));
uint8_t dataOut[10U];
decoded.getData(dataOut);
REQUIRE(::memcmp(pattern, dataOut, 10U) == 0);
}
}
TEST_CASE("FACCH1 decodes at alternate bit offset", "[nxdn][facch1]") {
uint8_t dataIn[10U] = {0xA5, 0x5A, 0xF0, 0x0F, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC};
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0x00U, sizeof(frameData));
// Encode at second FACCH1 position
FACCH1 facch;
facch.setData(dataIn);
const uint32_t secondOffset = NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS;
facch.encode(frameData, secondOffset);
// Decode from second position
FACCH1 decoded;
REQUIRE(decoded.decode(frameData, secondOffset));
uint8_t dataOut[10U];
decoded.getData(dataOut);
REQUIRE(::memcmp(dataIn, dataOut, 10U) == 0);
}
TEST_CASE("FACCH1 copy constructor preserves data", "[nxdn][facch1]") {
uint8_t testData[10U] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA};
FACCH1 original;
original.setData(testData);
FACCH1 copy(original);
uint8_t originalData[10U], copyData[10U];
original.getData(originalData);
copy.getData(copyData);
REQUIRE(::memcmp(originalData, copyData, 10U) == 0);
}
TEST_CASE("FACCH1 assignment operator preserves data", "[nxdn][facch1]") {
uint8_t testData[10U] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33};
FACCH1 original;
original.setData(testData);
FACCH1 assigned;
assigned = original;
uint8_t originalData[10U], assignedData[10U];
original.getData(originalData);
assigned.getData(assignedData);
REQUIRE(::memcmp(originalData, assignedData, 10U) == 0);
}
TEST_CASE("FACCH1 rejects invalid CRC", "[nxdn][facch1]") {
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0xFFU, sizeof(frameData));
// Create random corrupted data that should fail CRC
for (uint32_t i = 0; i < NXDN_FACCH1_FEC_LENGTH_BYTES; i++) {
frameData[i] = static_cast<uint8_t>(i * 17 + 23);
}
FACCH1 decoded;
// Decode may succeed or fail depending on corruption, but this tests the CRC validation path
decoded.decode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
}
TEST_CASE("FACCH1 golden test for voice call header", "[nxdn][facch1][golden]") {
uint8_t dataIn[10U];
::memset(dataIn, 0x00U, sizeof(dataIn));
// Simulate RTCH header structure
dataIn[0] = MessageType::RTCH_VCALL; // Message Type
dataIn[1] = 0x00; // Options
dataIn[2] = 0x12; // Source ID (high)
dataIn[3] = 0x34; // Source ID (low)
dataIn[4] = 0x56; // Dest ID (high)
dataIn[5] = 0x78; // Dest ID (low)
uint8_t frameData[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(frameData, 0x00U, sizeof(frameData));
FACCH1 facch;
facch.setData(dataIn);
facch.encode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
// Decode and verify round-trip
FACCH1 decoded;
REQUIRE(decoded.decode(frameData, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS));
uint8_t dataOut[10U];
decoded.getData(dataOut);
REQUIRE(::memcmp(dataIn, dataOut, 10U) == 0);
}

@ -0,0 +1,210 @@
// 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 <cstring>
#include "common/nxdn/channel/LICH.h"
#include "common/nxdn/NXDNDefines.h"
using namespace nxdn;
using namespace nxdn::defines;
using namespace nxdn::channel;
TEST_CASE("LICH encodes and decodes RCCH channel", "[nxdn][lich]") {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(RFChannelType::RCCH);
lich.setFCT(FuncChannelType::CAC_OUTBOUND);
lich.setOption(ChOption::DATA_COMMON);
lich.setOutbound(true);
lich.encode(data);
// Decode and verify
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getRFCT() == RFChannelType::RCCH);
REQUIRE(decoded.getFCT() == FuncChannelType::CAC_OUTBOUND);
REQUIRE(decoded.getOption() == ChOption::DATA_COMMON);
REQUIRE(decoded.getOutbound() == true);
}
TEST_CASE("LICH encodes and decodes RDCH voice channel", "[nxdn][lich]") {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(RFChannelType::RDCH);
lich.setFCT(FuncChannelType::USC_SACCH_NS);
lich.setOption(ChOption::STEAL_FACCH);
lich.setOutbound(false);
lich.encode(data);
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getRFCT() == RFChannelType::RDCH);
REQUIRE(decoded.getFCT() == FuncChannelType::USC_SACCH_NS);
REQUIRE(decoded.getOption() == ChOption::STEAL_FACCH);
REQUIRE(decoded.getOutbound() == false);
}
TEST_CASE("LICH preserves all RFChannelType values", "[nxdn][lich]") {
const RFChannelType::E rfctValues[] = {
RFChannelType::RCCH,
RFChannelType::RTCH,
RFChannelType::RDCH
};
for (auto rfct : rfctValues) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(rfct);
lich.setFCT(FuncChannelType::USC_SACCH_NS);
lich.setOption(ChOption::DATA_NORMAL);
lich.setOutbound(true);
lich.encode(data);
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getRFCT() == rfct);
}
}
TEST_CASE("LICH preserves all FuncChannelType values", "[nxdn][lich]") {
const FuncChannelType::E fctValues[] = {
FuncChannelType::CAC_OUTBOUND,
FuncChannelType::CAC_INBOUND_LONG,
FuncChannelType::CAC_INBOUND_SHORT,
FuncChannelType::USC_SACCH_NS,
FuncChannelType::USC_UDCH,
FuncChannelType::USC_SACCH_SS,
FuncChannelType::USC_SACCH_SS_IDLE
};
for (auto fct : fctValues) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(RFChannelType::RDCH);
lich.setFCT(fct);
lich.setOption(ChOption::DATA_NORMAL);
lich.setOutbound(true);
lich.encode(data);
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getFCT() == fct);
}
}
TEST_CASE("LICH preserves all ChOption values", "[nxdn][lich]") {
const ChOption::E optionValues[] = {
ChOption::DATA_NORMAL,
ChOption::DATA_COMMON,
ChOption::STEAL_FACCH,
ChOption::STEAL_FACCH1_1,
ChOption::STEAL_FACCH1_2
};
for (auto option : optionValues) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(RFChannelType::RDCH);
lich.setFCT(FuncChannelType::USC_SACCH_NS);
lich.setOption(option);
lich.setOutbound(true);
lich.encode(data);
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getOption() == option);
}
}
TEST_CASE("LICH preserves outbound flag", "[nxdn][lich]") {
for (bool outbound : {true, false}) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(RFChannelType::RDCH);
lich.setFCT(FuncChannelType::USC_SACCH_NS);
lich.setOption(ChOption::DATA_NORMAL);
lich.setOutbound(outbound);
lich.encode(data);
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getOutbound() == outbound);
}
}
TEST_CASE("LICH copy constructor preserves all fields", "[nxdn][lich]") {
LICH original;
original.setRFCT(RFChannelType::RDCH);
original.setFCT(FuncChannelType::USC_SACCH_NS);
original.setOption(ChOption::STEAL_FACCH);
original.setOutbound(false);
LICH copy(original);
REQUIRE(copy.getRFCT() == original.getRFCT());
REQUIRE(copy.getFCT() == original.getFCT());
REQUIRE(copy.getOption() == original.getOption());
REQUIRE(copy.getOutbound() == original.getOutbound());
}
TEST_CASE("LICH assignment operator preserves all fields", "[nxdn][lich]") {
LICH original;
original.setRFCT(RFChannelType::RCCH);
original.setFCT(FuncChannelType::CAC_OUTBOUND);
original.setOption(ChOption::DATA_COMMON);
original.setOutbound(true);
LICH assigned;
assigned = original;
REQUIRE(assigned.getRFCT() == original.getRFCT());
REQUIRE(assigned.getFCT() == original.getFCT());
REQUIRE(assigned.getOption() == original.getOption());
REQUIRE(assigned.getOutbound() == original.getOutbound());
}
TEST_CASE("LICH golden test for voice call", "[nxdn][lich][golden]") {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
LICH lich;
lich.setRFCT(RFChannelType::RDCH);
lich.setFCT(FuncChannelType::USC_SACCH_NS);
lich.setOption(ChOption::STEAL_FACCH);
lich.setOutbound(false);
lich.encode(data);
// Decode and verify round-trip
LICH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getRFCT() == RFChannelType::RDCH);
REQUIRE(decoded.getFCT() == FuncChannelType::USC_SACCH_NS);
REQUIRE(decoded.getOption() == ChOption::STEAL_FACCH);
REQUIRE(decoded.getOutbound() == false);
}

@ -0,0 +1,196 @@
// 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 "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/nxdn/lc/RTCH.h"
#include "common/nxdn/NXDNDefines.h"
using namespace nxdn;
using namespace nxdn::defines;
using namespace nxdn::lc;
TEST_CASE("RTCH encodes and decodes voice call", "[nxdn][rtch]") {
uint8_t data[NXDN_RTCH_LC_LENGTH_BYTES];
::memset(data, 0x00U, sizeof(data));
RTCH rtch;
rtch.setMessageType(MessageType::RTCH_VCALL);
rtch.setSrcId(12345U);
rtch.setDstId(54321U);
rtch.setEmergency(false);
rtch.setPriority(false);
rtch.setDuplex(true);
rtch.setTransmissionMode(TransmissionMode::MODE_4800);
rtch.encode(data, NXDN_RTCH_LC_LENGTH_BITS);
// Decode and verify
RTCH decoded;
decoded.decode(data, NXDN_RTCH_LC_LENGTH_BITS);
REQUIRE(decoded.getMessageType() == MessageType::RTCH_VCALL);
REQUIRE(decoded.getSrcId() == 12345U);
REQUIRE(decoded.getDstId() == 54321U);
REQUIRE(decoded.getEmergency() == false);
}
TEST_CASE("RTCH preserves all MessageType values", "[nxdn][rtch]") {
const uint8_t messageTypes[] = {
MessageType::RTCH_VCALL,
MessageType::RTCH_VCALL_IV,
MessageType::RTCH_TX_REL,
MessageType::RTCH_TX_REL_EX,
MessageType::RTCH_DCALL_HDR,
MessageType::RTCH_DCALL_DATA
};
for (auto messageType : messageTypes) {
uint8_t data[NXDN_RTCH_LC_LENGTH_BYTES];
::memset(data, 0x00U, sizeof(data));
RTCH rtch;
rtch.setMessageType(messageType);
rtch.setSrcId(1234U);
rtch.setDstId(5678U);
rtch.encode(data, NXDN_RTCH_LC_LENGTH_BITS);
RTCH decoded;
decoded.decode(data, NXDN_RTCH_LC_LENGTH_BITS);
REQUIRE(decoded.getMessageType() == messageType);
}
}
TEST_CASE("RTCH preserves source and destination IDs", "[nxdn][rtch]") {
const uint32_t testIds[] = {0U, 1U, 255U, 1000U, 32767U, 65535U};
for (auto srcId : testIds) {
for (auto dstId : testIds) {
uint8_t data[NXDN_RTCH_LC_LENGTH_BYTES];
::memset(data, 0x00U, sizeof(data));
RTCH rtch;
rtch.setMessageType(MessageType::RTCH_VCALL);
rtch.setSrcId(srcId);
rtch.setDstId(dstId);
rtch.encode(data, NXDN_RTCH_LC_LENGTH_BITS);
RTCH decoded;
decoded.decode(data, NXDN_RTCH_LC_LENGTH_BITS);
REQUIRE(decoded.getSrcId() == srcId);
REQUIRE(decoded.getDstId() == dstId);
}
}
}
TEST_CASE("RTCH preserves emergency flag", "[nxdn][rtch]") {
for (bool isEmergency : {true, false}) {
uint8_t data[NXDN_RTCH_LC_LENGTH_BYTES];
::memset(data, 0x00U, sizeof(data));
RTCH rtch;
rtch.setMessageType(MessageType::RTCH_VCALL);
rtch.setSrcId(100U);
rtch.setDstId(200U);
rtch.setEmergency(isEmergency);
rtch.encode(data, NXDN_RTCH_LC_LENGTH_BITS);
RTCH decoded;
decoded.decode(data, NXDN_RTCH_LC_LENGTH_BITS);
REQUIRE(decoded.getEmergency() == isEmergency);
}
}
TEST_CASE("RTCH preserves duplex flag", "[nxdn][rtch]") {
for (bool isDuplex : {true, false}) {
uint8_t data[NXDN_RTCH_LC_LENGTH_BYTES];
::memset(data, 0x00U, sizeof(data));
RTCH rtch;
rtch.setMessageType(MessageType::RTCH_VCALL);
rtch.setSrcId(100U);
rtch.setDstId(200U);
rtch.setDuplex(isDuplex);
rtch.encode(data, NXDN_RTCH_LC_LENGTH_BITS);
RTCH decoded;
decoded.decode(data, NXDN_RTCH_LC_LENGTH_BITS);
REQUIRE(decoded.getDuplex() == isDuplex);
}
}
TEST_CASE("RTCH preserves transmission mode", "[nxdn][rtch]") {
const uint8_t transmissionModes[] = {
TransmissionMode::MODE_4800,
TransmissionMode::MODE_9600
};
for (auto mode : transmissionModes) {
uint8_t data[NXDN_RTCH_LC_LENGTH_BYTES];
::memset(data, 0x00U, sizeof(data));
RTCH rtch;
rtch.setMessageType(MessageType::RTCH_VCALL);
rtch.setSrcId(100U);
rtch.setDstId(200U);
rtch.setTransmissionMode(mode);
rtch.encode(data, NXDN_RTCH_LC_LENGTH_BITS);
RTCH decoded;
decoded.decode(data, NXDN_RTCH_LC_LENGTH_BITS);
REQUIRE(decoded.getTransmissionMode() == mode);
}
}
TEST_CASE("RTCH copy constructor preserves all fields", "[nxdn][rtch]") {
RTCH original;
original.setMessageType(MessageType::RTCH_VCALL);
original.setSrcId(11111U);
original.setDstId(22222U);
original.setGroup(true);
original.setEmergency(true);
original.setEncrypted(false);
original.setPriority(true);
RTCH copy(original);
REQUIRE(copy.getMessageType() == original.getMessageType());
REQUIRE(copy.getSrcId() == original.getSrcId());
REQUIRE(copy.getDstId() == original.getDstId());
REQUIRE(copy.getGroup() == original.getGroup());
REQUIRE(copy.getEmergency() == original.getEmergency());
REQUIRE(copy.getEncrypted() == original.getEncrypted());
}
TEST_CASE("RTCH assignment operator preserves all fields", "[nxdn][rtch]") {
RTCH original;
original.setMessageType(MessageType::RTCH_TX_REL);
original.setSrcId(9999U);
original.setDstId(8888U);
original.setGroup(false);
original.setEmergency(false);
original.setEncrypted(true);
RTCH assigned;
assigned = original;
REQUIRE(assigned.getMessageType() == original.getMessageType());
REQUIRE(assigned.getSrcId() == original.getSrcId());
REQUIRE(assigned.getDstId() == original.getDstId());
REQUIRE(assigned.getGroup() == original.getGroup());
REQUIRE(assigned.getEmergency() == original.getEmergency());
REQUIRE(assigned.getEncrypted() == original.getEncrypted());
}

@ -0,0 +1,165 @@
// 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 "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/nxdn/channel/SACCH.h"
#include "common/nxdn/NXDNDefines.h"
using namespace nxdn;
using namespace nxdn::defines;
using namespace nxdn::channel;
TEST_CASE("SACCH encodes and decodes idle pattern", "[nxdn][sacch]") {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
SACCH sacch;
sacch.setData(SACCH_IDLE);
sacch.setRAN(1U);
sacch.setStructure(ChStructure::SR_SINGLE);
sacch.encode(data);
// Decode and verify
SACCH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getRAN() == 1U);
REQUIRE(decoded.getStructure() == ChStructure::SR_SINGLE);
// Verify data matches
uint8_t decodedData[3U];
decoded.getData(decodedData);
REQUIRE(::memcmp(decodedData, SACCH_IDLE, 3U) == 0);
}
TEST_CASE("SACCH preserves all RAN values", "[nxdn][sacch]") {
for (uint8_t ran = 0U; ran < 64U; ran++) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
SACCH sacch;
sacch.setData(SACCH_IDLE);
sacch.setRAN(ran);
sacch.setStructure(ChStructure::SR_SINGLE);
sacch.encode(data);
SACCH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getRAN() == ran);
}
}
TEST_CASE("SACCH preserves all ChStructure values", "[nxdn][sacch]") {
const ChStructure::E structures[] = {
ChStructure::SR_SINGLE,
ChStructure::SR_1_4,
ChStructure::SR_2_4,
ChStructure::SR_3_4,
ChStructure::SR_RCCH_SINGLE
};
for (auto structure : structures) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
SACCH sacch;
sacch.setData(SACCH_IDLE);
sacch.setRAN(1U);
sacch.setStructure(structure);
sacch.encode(data);
SACCH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getStructure() == structure);
}
}
TEST_CASE("SACCH copy constructor preserves all fields", "[nxdn][sacch]") {
SACCH original;
original.setData(SACCH_IDLE);
original.setRAN(5U);
original.setStructure(ChStructure::SR_1_4);
SACCH copy(original);
REQUIRE(copy.getRAN() == original.getRAN());
REQUIRE(copy.getStructure() == original.getStructure());
// initialize buffers to zero since getData() only writes 18 bits (NXDN_SACCH_LENGTH_BITS - 8)
uint8_t originalData[3U], copyData[3U];
::memset(originalData, 0x00U, 3U);
::memset(copyData, 0x00U, 3U);
original.getData(originalData);
Utils::dump(2U, "originalData", originalData, 3U);
copy.getData(copyData);
Utils::dump(2U, "copyData", copyData, 3U);
REQUIRE(::memcmp(originalData, copyData, 3U) == 0);
}
TEST_CASE("SACCH assignment operator preserves all fields", "[nxdn][sacch]") {
SACCH original;
const uint8_t testData[] = {0x12, 0x34, 0x56};
original.setData(testData);
original.setRAN(10U);
original.setStructure(ChStructure::SR_2_4);
SACCH assigned;
assigned = original;
REQUIRE(assigned.getRAN() == original.getRAN());
REQUIRE(assigned.getStructure() == original.getStructure());
// initialize buffers to zero since getData() only writes 18 bits (NXDN_SACCH_LENGTH_BITS - 8)
uint8_t originalData[3U], assignedData[3U];
::memset(originalData, 0x00U, 3U);
::memset(assignedData, 0x00U, 3U);
original.getData(originalData);
Utils::dump(2U, "originalData", originalData, 3U);
assigned.getData(assignedData);
Utils::dump(2U, "assignedData", assignedData, 3U);
REQUIRE(::memcmp(originalData, assignedData, 3U) == 0);
}
TEST_CASE("SACCH handles multi-part structures", "[nxdn][sacch]") {
// Test multi-part SACCH structures (SR_1_4, SR_2_4, etc.)
const ChStructure::E multiPart[] = {
ChStructure::SR_1_4,
ChStructure::SR_2_4,
ChStructure::SR_3_4
};
for (auto structure : multiPart) {
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
::memset(data, 0x00U, sizeof(data));
SACCH sacch;
const uint8_t testData[] = {0xA5, 0x5A, 0xC0};
sacch.setData(testData);
sacch.setRAN(7U);
sacch.setStructure(structure);
sacch.encode(data);
SACCH decoded;
REQUIRE(decoded.decode(data));
REQUIRE(decoded.getStructure() == structure);
REQUIRE(decoded.getRAN() == 7U);
uint8_t decodedData[3U];
decoded.getData(decodedData);
Utils::dump(2U, "decodedData", decodedData, 3U);
REQUIRE(::memcmp(decodedData, testData, 3U) == 0);
}
}

@ -21,79 +21,77 @@ using namespace p25::defines;
#include <stdlib.h>
#include <time.h>
TEST_CASE("HDU", "[Reed-Soloman 36,20,17 Test]") {
SECTION("RS_362017_Test") {
bool failed = false;
TEST_CASE("P25 HDU Reed-Soloman 36,20,17 Test", "[p25][hdu_rs362017]") {
bool failed = false;
INFO("P25 HDU RS (36,20,17) FEC Test");
INFO("P25 HDU RS (36,20,17) FEC Test");
srand((unsigned int)time(NULL));
RS634717 m_rs = RS634717();
srand((unsigned int)time(NULL));
RS634717 m_rs = RS634717();
uint8_t* random = (uint8_t*)malloc(15U);
uint8_t* random = (uint8_t*)malloc(15U);
for (size_t i = 0; i < 15U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < 15U; i++) {
random[i] = rand();
}
// HDU Encode
uint8_t rs[P25_HDU_LENGTH_BYTES];
::memset(rs, 0x00U, P25_HDU_LENGTH_BYTES);
// HDU Encode
uint8_t rs[P25_HDU_LENGTH_BYTES];
::memset(rs, 0x00U, P25_HDU_LENGTH_BYTES);
for (uint32_t i = 0; i < 15U; i++)
rs[i] = random[i];
rs[14U] = 0xF0U;
for (uint32_t i = 0; i < 15U; i++)
rs[i] = random[i];
rs[14U] = 0xF0U;
Utils::dump(2U, "LC::encodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES);
// encode RS (36,20,17) FEC
m_rs.encode362017(rs);
// encode RS (36,20,17) FEC
m_rs.encode362017(rs);
Utils::dump(2U, "LC::encodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeHDU(), HDU RS", rs, P25_HDU_LENGTH_BYTES);
// HDU Decode
rs[9U] >>= 8;
rs[10U] >>= 8;
rs[11U] >>= 8;
rs[12U] >>= 8;
rs[13U] >>= 8;
// HDU Decode
rs[9U] >>= 8;
rs[10U] >>= 8;
rs[11U] >>= 8;
rs[12U] >>= 8;
rs[13U] >>= 8;
Utils::dump(2U, "LC::decodeHDU(), HDU RS (errors injected)", rs, P25_HDU_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeHDU(), HDU RS (errors injected)", rs, P25_HDU_LENGTH_BYTES);
// decode RS (36,20,17) FEC
try {
bool ret = m_rs.decode362017(rs);
if (!ret) {
::LogError("T", "LC::decodeHDU(), failed to decode RS (36,20,17) FEC");
failed = true;
goto cleanup;
}
}
catch (...) {
Utils::dump(2U, "P25, RS excepted with input data", rs, P25_HDU_LENGTH_BYTES);
// decode RS (36,20,17) FEC
try {
bool ret = m_rs.decode362017(rs);
if (!ret) {
::LogError("T", "LC::decodeHDU(), failed to decode RS (36,20,17) FEC");
failed = true;
goto cleanup;
}
}
catch (...) {
Utils::dump(2U, "P25, RS excepted with input data", rs, P25_HDU_LENGTH_BYTES);
failed = true;
goto cleanup;
}
Utils::dump(2U, "LC::decodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeHDU(), HDU", rs, P25_HDU_LENGTH_BYTES);
for (uint32_t i = 0; i < 15U; i++) {
if (i == 14U) {
if (rs[i] != 0xF0U) {
::LogError("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 15U; i++) {
if (i == 14U) {
if (rs[i] != 0xF0U) {
::LogError("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
else {
if (rs[i] != random[i]) {
::LogError("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
}
else {
if (rs[i] != random[i]) {
::LogError("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
}
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -23,123 +23,121 @@ using namespace p25::kmm;
#include <stdlib.h>
#include <time.h>
TEST_CASE("KMM_ReKey_CBC", "[P25 KMM Rekey Command CBC Test]") {
SECTION("P25_KMM_ReKey_CBC_Test") {
bool failed = false;
INFO("P25 KMM ReKey Test");
srand((unsigned int)time(NULL));
// MAC TEK
uint8_t macTek[] =
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x42, 0xA0, 0x91, 0x56, 0xF0, 0xD4, 0x72, 0x1C, 0x08, 0x84, 0x2F, 0x62, 0x40
};
// Encrypted Key Frame
uint8_t testWrappedKeyFrame[40U] =
{
0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4,
0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0,
0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80
};
uint8_t encryptMI[] =
{
0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67
};
// final encrypted block
uint8_t encryptedBlock[] =
{
0x67, 0x75, 0xB1, 0xD1, 0x8A, 0xBD, 0xCF, 0x86, 0x08, 0x54, 0xDF, 0x09, 0x8E, 0xA3, 0x41, 0x29,
0x13, 0x2A, 0x0E, 0x48, 0x4C, 0xCC, 0x5C, 0xAE, 0x80, 0x08, 0x0B, 0x19, 0xF7, 0x08, 0xAE, 0x8F,
0xB8, 0x40, 0xAA, 0x2E, 0x3E, 0x5E, 0xCD, 0x03, 0x73, 0x52, 0x75, 0xFE, 0xE2, 0x88, 0x0E, 0x6D,
0xDD, 0x00, 0xC1, 0x11, 0x42, 0x8F, 0xEE, 0x39, 0xC6, 0x2B, 0xF3, 0xC1, 0xD2, 0xEE, 0x3B, 0xEB,
0xBB, 0x7C, 0x44, 0xA5, 0xE3, 0xC9, 0x30, 0x8C, 0x5D, 0xE9, 0x17, 0x84, 0x7C, 0x17, 0xAF, 0x23
};
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, MAC TEK", macTek, 32U);
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, OFB MI", encryptMI, 8U);
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, DataBlock", dataBlock, 80U);
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, EncryptedBlock", encryptedBlock, 80U);
KMMRekeyCommand outKmm = KMMRekeyCommand();
outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE);
outKmm.setSrcLLId(0x712B1DU);
outKmm.setDstLLId(0x643BA8U);
outKmm.setMACType(KMM_MAC::ENH_MAC);
outKmm.setMACAlgId(ALGO_AES_256);
outKmm.setMACKId(0x2F62U);
outKmm.setMACFormat(KMM_MAC_FORMAT_CBC);
outKmm.setMessageNumber(0x1772U);
outKmm.setAlgId(ALGO_AES_256);
outKmm.setKId(0x50BCU);
KeysetItem ks;
ks.keysetId(1U);
ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys
ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES);
p25::kmm::KeyItem ki = p25::kmm::KeyItem();
ki.keyFormat(0U);
ki.sln(0U);
ki.kId(0x4983U);
ki.setKey(testWrappedKeyFrame, 40U);
ks.push_back(ki);
std::vector<KeysetItem> keysets;
keysets.push_back(ks);
outKmm.setKeysets(keysets);
UInt8Array kmmFrame = std::make_unique<uint8_t[]>(outKmm.fullLength());
outKmm.encode(kmmFrame.get());
outKmm.generateMAC(macTek, kmmFrame.get());
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, GeneratedDataBlock", kmmFrame.get(), outKmm.fullLength());
for (uint32_t i = 0; i < outKmm.fullLength(); i++) {
if (kmmFrame.get()[i] != dataBlock[i]) {
::LogError("T", "P25_KMM_ReKey_CBC_Test, INVALID AT IDX %d", i);
failed = true;
}
TEST_CASE("KMM ReKey Command CBC Test", "[p25][kmm_cbc]") {
bool failed = false;
INFO("P25 KMM ReKey Test");
srand((unsigned int)time(NULL));
// MAC TEK
uint8_t macTek[] =
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x42, 0xA0, 0x91, 0x56, 0xF0, 0xD4, 0x72, 0x1C, 0x08, 0x84, 0x2F, 0x62, 0x40
};
// Encrypted Key Frame
uint8_t testWrappedKeyFrame[40U] =
{
0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4,
0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0,
0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80
};
uint8_t encryptMI[] =
{
0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67
};
// final encrypted block
uint8_t encryptedBlock[] =
{
0x67, 0x75, 0xB1, 0xD1, 0x8A, 0xBD, 0xCF, 0x86, 0x08, 0x54, 0xDF, 0x09, 0x8E, 0xA3, 0x41, 0x29,
0x13, 0x2A, 0x0E, 0x48, 0x4C, 0xCC, 0x5C, 0xAE, 0x80, 0x08, 0x0B, 0x19, 0xF7, 0x08, 0xAE, 0x8F,
0xB8, 0x40, 0xAA, 0x2E, 0x3E, 0x5E, 0xCD, 0x03, 0x73, 0x52, 0x75, 0xFE, 0xE2, 0x88, 0x0E, 0x6D,
0xDD, 0x00, 0xC1, 0x11, 0x42, 0x8F, 0xEE, 0x39, 0xC6, 0x2B, 0xF3, 0xC1, 0xD2, 0xEE, 0x3B, 0xEB,
0xBB, 0x7C, 0x44, 0xA5, 0xE3, 0xC9, 0x30, 0x8C, 0x5D, 0xE9, 0x17, 0x84, 0x7C, 0x17, 0xAF, 0x23
};
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, MAC TEK", macTek, 32U);
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, OFB MI", encryptMI, 8U);
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, DataBlock", dataBlock, 80U);
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, EncryptedBlock", encryptedBlock, 80U);
KMMRekeyCommand outKmm = KMMRekeyCommand();
outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE);
outKmm.setSrcLLId(0x712B1DU);
outKmm.setDstLLId(0x643BA8U);
outKmm.setMACType(KMM_MAC::ENH_MAC);
outKmm.setMACAlgId(ALGO_AES_256);
outKmm.setMACKId(0x2F62U);
outKmm.setMACFormat(KMM_MAC_FORMAT_CBC);
outKmm.setMessageNumber(0x1772U);
outKmm.setAlgId(ALGO_AES_256);
outKmm.setKId(0x50BCU);
KeysetItem ks;
ks.keysetId(1U);
ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys
ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES);
p25::kmm::KeyItem ki = p25::kmm::KeyItem();
ki.keyFormat(0U);
ki.sln(0U);
ki.kId(0x4983U);
ki.setKey(testWrappedKeyFrame, 40U);
ks.push_back(ki);
std::vector<KeysetItem> keysets;
keysets.push_back(ks);
outKmm.setKeysets(keysets);
UInt8Array kmmFrame = std::make_unique<uint8_t[]>(outKmm.fullLength());
outKmm.encode(kmmFrame.get());
outKmm.generateMAC(macTek, kmmFrame.get());
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, GeneratedDataBlock", kmmFrame.get(), outKmm.fullLength());
for (uint32_t i = 0; i < outKmm.fullLength(); i++) {
if (kmmFrame.get()[i] != dataBlock[i]) {
::LogError("T", "P25_KMM_ReKey_CBC_Test, INVALID AT IDX %d", i);
failed = true;
}
}
P25Crypto crypto;
crypto.setMI(encryptMI);
crypto.setTEKAlgoId(ALGO_AES_256);
crypto.setKey(macTek, 32U);
crypto.generateKeystream();
crypto.cryptAES_PDU(kmmFrame.get(), outKmm.fullLength());
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, EncryptedDataBlock", kmmFrame.get(), outKmm.fullLength());
for (uint32_t i = 0; i < outKmm.fullLength(); i++) {
if (kmmFrame.get()[i] != encryptedBlock[i]) {
::LogError("T", "P25_KMM_ReKey_CBC_Test, INVALID AT IDX %d", i);
failed = true;
}
}
P25Crypto crypto;
crypto.setMI(encryptMI);
crypto.setTEKAlgoId(ALGO_AES_256);
crypto.setKey(macTek, 32U);
crypto.generateKeystream();
crypto.cryptAES_PDU(kmmFrame.get(), outKmm.fullLength());
Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, EncryptedDataBlock", kmmFrame.get(), outKmm.fullLength());
REQUIRE(failed==false);
for (uint32_t i = 0; i < outKmm.fullLength(); i++) {
if (kmmFrame.get()[i] != encryptedBlock[i]) {
::LogError("T", "P25_KMM_ReKey_CBC_Test, INVALID AT IDX %d", i);
failed = true;
}
}
REQUIRE(failed==false);
}

@ -21,88 +21,86 @@ using namespace p25::kmm;
#include <stdlib.h>
#include <time.h>
TEST_CASE("KMM_ReKey_CMAC", "[P25 KMM Rekey Command CMAC Test]") {
SECTION("P25_KMM_ReKey_CMAC_Test") {
bool failed = false;
INFO("P25 KMM ReKey Test");
srand((unsigned int)time(NULL));
// MAC TEK
uint8_t macTek[] =
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x21, 0x85, 0x22, 0x33, 0x41, 0xD9, 0x8A, 0x97, 0x08, 0x84, 0x2F, 0x62, 0x41
};
// Encrypted Key Frame
uint8_t testWrappedKeyFrame[40U] =
{
0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4,
0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0,
0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80
};
Utils::dump(2U, "P25_KMM_ReKey_CMAC_Test, DataBlock", dataBlock, 80U);
KMMRekeyCommand outKmm = KMMRekeyCommand();
outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE);
outKmm.setSrcLLId(0x712B1DU);
outKmm.setDstLLId(0x643BA8U);
outKmm.setMACType(KMM_MAC::ENH_MAC);
outKmm.setMACAlgId(ALGO_AES_256);
outKmm.setMACKId(0x2F62U);
outKmm.setMACFormat(KMM_MAC_FORMAT_CMAC);
outKmm.setMessageNumber(0x1772U);
outKmm.setAlgId(ALGO_AES_256);
outKmm.setKId(0x50BCU);
KeysetItem ks;
ks.keysetId(1U);
ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys
ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES);
p25::kmm::KeyItem ki = p25::kmm::KeyItem();
ki.keyFormat(0U);
ki.sln(0U);
ki.kId(0x4983U);
ki.setKey(testWrappedKeyFrame, 40U);
ks.push_back(ki);
std::vector<KeysetItem> keysets;
keysets.push_back(ks);
outKmm.setKeysets(keysets);
UInt8Array kmmFrame = std::make_unique<uint8_t[]>(outKmm.fullLength());
outKmm.encode(kmmFrame.get());
outKmm.generateMAC(macTek, kmmFrame.get());
Utils::dump(2U, "P25_KMM_ReKey_CMAC_Test, GeneratedDataBlock", kmmFrame.get(), outKmm.fullLength());
for (uint32_t i = 0; i < outKmm.fullLength(); i++) {
if (kmmFrame.get()[i] != dataBlock[i]) {
::LogError("T", "P25_KMM_ReKey_CMAC_Test, INVALID AT IDX %d", i);
failed = true;
}
}
TEST_CASE("KMM ReKey Command CMAC Test", "[p25][kmm_cmac]") {
bool failed = false;
INFO("P25 KMM ReKey Test");
srand((unsigned int)time(NULL));
// MAC TEK
uint8_t macTek[] =
{
0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1,
0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F
};
// data block
uint8_t dataBlock[] =
{
0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC,
0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35,
0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56,
0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23,
0x72, 0xFB, 0x80, 0x21, 0x85, 0x22, 0x33, 0x41, 0xD9, 0x8A, 0x97, 0x08, 0x84, 0x2F, 0x62, 0x41
};
// Encrypted Key Frame
uint8_t testWrappedKeyFrame[40U] =
{
0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4,
0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0,
0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80
};
Utils::dump(2U, "P25_KMM_ReKey_CMAC_Test, DataBlock", dataBlock, 80U);
KMMRekeyCommand outKmm = KMMRekeyCommand();
outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE);
outKmm.setSrcLLId(0x712B1DU);
outKmm.setDstLLId(0x643BA8U);
outKmm.setMACType(KMM_MAC::ENH_MAC);
outKmm.setMACAlgId(ALGO_AES_256);
outKmm.setMACKId(0x2F62U);
outKmm.setMACFormat(KMM_MAC_FORMAT_CMAC);
outKmm.setMessageNumber(0x1772U);
REQUIRE(failed==false);
outKmm.setAlgId(ALGO_AES_256);
outKmm.setKId(0x50BCU);
KeysetItem ks;
ks.keysetId(1U);
ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys
ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES);
p25::kmm::KeyItem ki = p25::kmm::KeyItem();
ki.keyFormat(0U);
ki.sln(0U);
ki.kId(0x4983U);
ki.setKey(testWrappedKeyFrame, 40U);
ks.push_back(ki);
std::vector<KeysetItem> keysets;
keysets.push_back(ks);
outKmm.setKeysets(keysets);
UInt8Array kmmFrame = std::make_unique<uint8_t[]>(outKmm.fullLength());
outKmm.encode(kmmFrame.get());
outKmm.generateMAC(macTek, kmmFrame.get());
Utils::dump(2U, "P25_KMM_ReKey_CMAC_Test, GeneratedDataBlock", kmmFrame.get(), outKmm.fullLength());
for (uint32_t i = 0; i < outKmm.fullLength(); i++) {
if (kmmFrame.get()[i] != dataBlock[i]) {
::LogError("T", "P25_KMM_ReKey_CMAC_Test, INVALID AT IDX %d", i);
failed = true;
}
}
REQUIRE(failed==false);
}

@ -21,77 +21,75 @@ using namespace p25::defines;
#include <stdlib.h>
#include <time.h>
TEST_CASE("LDU1", "[Reed-Soloman 24,12,13 Test]") {
SECTION("RS_241213_Test") {
bool failed = false;
TEST_CASE("P25 LDU1 Reed-Soloman 24,12,13 Test", "[p25][ldu1_rs241213]") {
bool failed = false;
INFO("P25 LDU1 RS (24,12,13) FEC Test");
INFO("P25 LDU1 RS (24,12,13) FEC Test");
srand((unsigned int)time(NULL));
RS634717 m_rs = RS634717();
srand((unsigned int)time(NULL));
RS634717 m_rs = RS634717();
uint8_t* random = (uint8_t*)malloc(15U);
uint8_t* random = (uint8_t*)malloc(15U);
for (size_t i = 0; i < 15U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < 15U; i++) {
random[i] = rand();
}
// LDU1 Encode
uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES];
::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES);
// LDU1 Encode
uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES];
::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES);
for (uint32_t i = 0; i < 9U; i++)
rs[i] = random[i];
rs[8U] = 0xF0U;
for (uint32_t i = 0; i < 9U; i++)
rs[i] = random[i];
rs[8U] = 0xF0U;
Utils::dump(2U, "LC::encodeLDU1(), LDU1", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU1(), LDU1", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// encode RS (24,12,13) FEC
m_rs.encode241213(rs);
// encode RS (24,12,13) FEC
m_rs.encode241213(rs);
Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU1(), LDU1 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// LDU1 Decode
rs[6U] >>= 8;
rs[7U] >>= 8;
rs[8U] >>= 8;
// LDU1 Decode
rs[6U] >>= 8;
rs[7U] >>= 8;
rs[8U] >>= 8;
Utils::dump(2U, "LC::encodeLDU1(), LDU RS (errors injected)", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU1(), LDU RS (errors injected)", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// decode RS (24,12,13) FEC
try {
bool ret = m_rs.decode241213(rs);
if (!ret) {
::LogError("T", "LC::decodeLDU1(), failed to decode RS (24,12,13) FEC");
failed = true;
goto cleanup;
}
}
catch (...) {
Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// decode RS (24,12,13) FEC
try {
bool ret = m_rs.decode241213(rs);
if (!ret) {
::LogError("T", "LC::decodeLDU1(), failed to decode RS (24,12,13) FEC");
failed = true;
goto cleanup;
}
}
catch (...) {
Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
failed = true;
goto cleanup;
}
Utils::dump(2U, "LC::decodeLDU1(), LDU1", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU1(), LDU1", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
for (uint32_t i = 0; i < 9U; i++) {
if (i == 8U) {
if (rs[i] != 0xF0U) {
::LogError("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 9U; i++) {
if (i == 8U) {
if (rs[i] != 0xF0U) {
::LogError("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
else {
if (rs[i] != random[i]) {
::LogError("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
}
else {
if (rs[i] != random[i]) {
::LogError("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
}
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -21,76 +21,74 @@ using namespace p25::defines;
#include <stdlib.h>
#include <time.h>
TEST_CASE("LDU2", "[Reed-Soloman 24,16,9 Test]") {
SECTION("RS_24169_Test") {
bool failed = false;
TEST_CASE("P25 LDU2 Reed-Soloman 24,16,9 Test", "[p25][ldu2_rs24169]") {
bool failed = false;
INFO("P25 LDU2 RS (24,16,9) FEC Test");
INFO("P25 LDU2 RS (24,16,9) FEC Test");
srand((unsigned int)time(NULL));
RS634717 m_rs = RS634717();
srand((unsigned int)time(NULL));
RS634717 m_rs = RS634717();
uint8_t* random = (uint8_t*)malloc(15U);
uint8_t* random = (uint8_t*)malloc(15U);
for (size_t i = 0; i < 15U; i++) {
random[i] = rand();
}
for (size_t i = 0; i < 15U; i++) {
random[i] = rand();
}
// LDU2 Encode
uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES];
::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES);
// LDU2 Encode
uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES];
::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES);
for (uint32_t i = 0; i < 12U; i++)
rs[i] = random[i];
rs[11U] = 0xF0U;
for (uint32_t i = 0; i < 12U; i++)
rs[i] = random[i];
rs[11U] = 0xF0U;
Utils::dump(2U, "LC::encodeLDU2(), LDU2", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU2(), LDU2", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// encode RS (24,16,9) FEC
m_rs.encode24169(rs);
// encode RS (24,16,9) FEC
m_rs.encode24169(rs);
Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::encodeLDU2(), LDU2 RS", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// LDU2 Decode
rs[9U] >>= 4;
rs[10U] >>= 4;
// LDU2 Decode
rs[9U] >>= 4;
rs[10U] >>= 4;
Utils::dump(2U, "LC::decodeLDU2(), LDU RS (errors injected)", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU2(), LDU RS (errors injected)", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// decode RS (24,16,9) FEC
try {
bool ret = m_rs.decode24169(rs);
if (!ret) {
::LogError("T", "LC::decodeLDU2(), failed to decode RS (24,16,9) FEC");
failed = true;
goto cleanup;
}
}
catch (...) {
Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
// decode RS (24,16,9) FEC
try {
bool ret = m_rs.decode24169(rs);
if (!ret) {
::LogError("T", "LC::decodeLDU2(), failed to decode RS (24,16,9) FEC");
failed = true;
goto cleanup;
}
}
catch (...) {
Utils::dump(2U, "P25, RS excepted with input data", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
failed = true;
goto cleanup;
}
Utils::dump(2U, "LC::decodeLDU2(), LDU2", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
Utils::dump(2U, "LC::decodeLDU2(), LDU2", rs, P25_LDU_LC_FEC_LENGTH_BYTES);
for (uint32_t i = 0; i < 12U; i++) {
if (i == 11U) {
if (rs[i] != 0xF0U) {
::LogError("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < 12U; i++) {
if (i == 11U) {
if (rs[i] != 0xF0U) {
::LogError("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
else {
if (rs[i] != random[i]) {
::LogError("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
}
else {
if (rs[i] != random[i]) {
::LogError("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d", i);
failed = true;
}
}
}
cleanup:
free(random);
REQUIRE(failed==false);
}
free(random);
REQUIRE(failed==false);
}

@ -21,109 +21,107 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Confirmed_AuxES_Test", "[P25 PDU Confirmed Aux ES Test]") {
SECTION("P25_PDU_Confirmed_AuxES_Test") {
bool failed = false;
TEST_CASE("P25 PDU Confirmed AuxES Test", "[p25][pdu_confirmed_auxes]") {
bool failed = false;
INFO("P25 PDU Confirmed Aux ES Test");
INFO("P25 PDU Confirmed Aux ES Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
uint8_t encryptMI[] =
{
0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67
};
uint8_t encryptMI[] =
{
0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Confirmed_AuxES_Test, Test Source", testPDUSource, 30U);
Utils::dump(2U, "P25_PDU_Confirmed_AuxES_Test, Test Source", testPDUSource, 30U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::ENC_USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::ENC_USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setMI(encryptMI);
dataHeader.setAlgId(ALGO_AES_256);
dataHeader.setKId(0x2F62U);
dataHeader.setMI(encryptMI);
dataHeader.setAlgId(ALGO_AES_256);
dataHeader.setKId(0x2F62U);
dataHeader.calculateLength(testLength);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, true, testPDUSource, &bitLength);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, true, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Confirmed_AuxES_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_AuxES_Test, Assembled PDU", ret.get(), bitLength / 8);
LogInfoEx("T", "P25_PDU_Confirmed_AuxES_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_AuxES_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "P25_PDU_Confirmed_AuxES_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
LogInfoEx("T", "P25_PDU_Confirmed_AuxES_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_AuxES_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_AuxES_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Confirmed_AuxES_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Confirmed_AuxES_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,164 +21,162 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Confirmed_ConvReg_Test", "[P25 PDU Confirmed Conv Reg Test]") {
SECTION("P25_PDU_Confirmed_ConvReg_Test") {
bool failed = false;
INFO("P25 PDU Confirmed Conv Reg Test");
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
// data block
uint8_t dataBlock[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4, 0x1C,
0x2A, 0x6E, 0x12, 0x2A, 0x20, 0x67, 0x0F, 0x79, 0x29, 0x2C, 0x70, 0x9E, 0x0B, 0x32, 0x21, 0x23,
0x3D, 0x22, 0xED, 0x8C, 0x29, 0x26, 0x50,
0x26, 0xE0, 0xB2, 0x22, 0x22, 0xB0, 0x72, 0x20, 0xE2, 0x22, 0x22, 0x59, 0x11, 0xE3, 0x92, 0x22,
0x22, 0x92, 0x73, 0x21, 0x52, 0x22, 0x22, 0x1F, 0x30
};
// expected PDU user data
uint8_t expectedUserData[] =
{
0x00, 0x54, 0x36, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC9, 0x9D, 0x42, 0x56
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
uint8_t pduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
/*
** self-sanity check the assembler chain
*/
DataHeader rspHeader = DataHeader();
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(assembler.dataHeader.getMFId());
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::CONV_DATA_REG);
rspHeader.setSynchronize(true);
rspHeader.setLLId(0x12345U);
rspHeader.setBlocksToFollow(1U);
uint32_t regType = PDURegType::ACCEPT;
uint32_t llId = 0x12345U;
uint32_t ipAddr = 0x7F000001;
pduUserData[0U] = ((regType & 0x0FU) << 4); // Registration Type & Options
pduUserData[1U] = (llId >> 16) & 0xFFU; // Logical Link ID
pduUserData[2U] = (llId >> 8) & 0xFFU;
pduUserData[3U] = (llId >> 0) & 0xFFU;
if (regType == PDURegType::ACCEPT) {
pduUserData[8U] = (ipAddr >> 24) & 0xFFU; // IP Address
pduUserData[9U] = (ipAddr >> 16) & 0xFFU;
pduUserData[10U] = (ipAddr >> 8) & 0xFFU;
pduUserData[11U] = (ipAddr >> 0) & 0xFFU;
}
Utils::dump(2U, "P25, PDU Registration Response", pduUserData, 12U);
TEST_CASE("P25 PDU Confirmed ConvReg Test", "[p25][pdu_confirmed_convreg]") {
bool failed = false;
INFO("P25 PDU Confirmed Conv Reg Test");
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
// data block
uint8_t dataBlock[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4, 0x1C,
0x2A, 0x6E, 0x12, 0x2A, 0x20, 0x67, 0x0F, 0x79, 0x29, 0x2C, 0x70, 0x9E, 0x0B, 0x32, 0x21, 0x23,
0x3D, 0x22, 0xED, 0x8C, 0x29, 0x26, 0x50,
0x26, 0xE0, 0xB2, 0x22, 0x22, 0xB0, 0x72, 0x20, 0xE2, 0x22, 0x22, 0x59, 0x11, 0xE3, 0x92, 0x22,
0x22, 0x92, 0x73, 0x21, 0x52, 0x22, 0x22, 0x1F, 0x30
};
// expected PDU user data
uint8_t expectedUserData[] =
{
0x00, 0x54, 0x36, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC9, 0x9D, 0x42, 0x56
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
uint8_t pduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES];
::memset(pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES);
/*
** self-sanity check the assembler chain
*/
DataHeader rspHeader = DataHeader();
rspHeader.setFormat(PDUFormatType::CONFIRMED);
rspHeader.setMFId(assembler.dataHeader.getMFId());
rspHeader.setAckNeeded(true);
rspHeader.setOutbound(true);
rspHeader.setSAP(PDUSAP::CONV_DATA_REG);
rspHeader.setSynchronize(true);
rspHeader.setLLId(0x12345U);
rspHeader.setBlocksToFollow(1U);
uint32_t regType = PDURegType::ACCEPT;
uint32_t llId = 0x12345U;
uint32_t ipAddr = 0x7F000001;
pduUserData[0U] = ((regType & 0x0FU) << 4); // Registration Type & Options
pduUserData[1U] = (llId >> 16) & 0xFFU; // Logical Link ID
pduUserData[2U] = (llId >> 8) & 0xFFU;
pduUserData[3U] = (llId >> 0) & 0xFFU;
if (regType == PDURegType::ACCEPT) {
pduUserData[8U] = (ipAddr >> 24) & 0xFFU; // IP Address
pduUserData[9U] = (ipAddr >> 16) & 0xFFU;
pduUserData[10U] = (ipAddr >> 8) & 0xFFU;
pduUserData[11U] = (ipAddr >> 0) & 0xFFU;
}
rspHeader.calculateLength(12U);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(rspHeader, false, false, pduUserData, &bitLength);
Utils::dump(2U, "P25, PDU Registration Response", pduUserData, 12U);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_Test, Assembled PDU", ret.get(), bitLength / 8);
rspHeader.calculateLength(12U);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(rspHeader, false, false, pduUserData, &bitLength);
if (ret == nullptr)
failed = true;
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_Test, Assembled PDU", ret.get(), bitLength / 8);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
if (ret == nullptr)
failed = true;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
Utils::dump(2U, "P25_PDU_Confirmed_Test, Block", buffer, P25_PDU_FEC_LENGTH_BYTES);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_Test, PDU Disassemble, block %u", blockCnt);
}
Utils::dump(2U, "P25_PDU_Confirmed_Test, Block", buffer, P25_PDU_FEC_LENGTH_BYTES);
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength() - 4U;
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength() - 4U;
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength; i++) {
if (pduUserData2[i] != pduUserData[i]) {
::LogError("T", "P25_PDU_Confirmed_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength; i++) {
if (pduUserData2[i] != pduUserData[i]) {
::LogError("T", "P25_PDU_Confirmed_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
}
/*
** test disassembly against the static test data block
*/
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
/*
** test disassembly against the static test data block
*/
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BYTES; i < 64U; i += P25_PDU_FEC_LENGTH_BYTES) {
LogInfoEx("T", "i = %u", i);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
::memcpy(buffer, dataBlock + i, P25_PDU_FEC_LENGTH_BYTES);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BYTES; i < 64U; i += P25_PDU_FEC_LENGTH_BYTES) {
LogInfoEx("T", "i = %u", i);
Utils::dump(2U, "P25_PDU_Confirmed_Test, Block", buffer, P25_PDU_FEC_LENGTH_BYTES);
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
::memcpy(buffer, dataBlock + i, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_Test, PDU Disassemble, block %u", blockCnt);
}
Utils::dump(2U, "P25_PDU_Confirmed_Test, Block", buffer, P25_PDU_FEC_LENGTH_BYTES);
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength() - 4U;
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength() - 4U;
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength; i++) {
if (pduUserData2[i] != expectedUserData[i]) {
::LogError("T", "P25_PDU_Confirmed_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength; i++) {
if (pduUserData2[i] != expectedUserData[i]) {
::LogError("T", "P25_PDU_Confirmed_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,101 +21,99 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Confirmed_ExtAddr_Test", "[P25 PDU Confirmed Ext Addr Test]") {
SECTION("P25_PDU_Confirmed_ExtAddr_Test") {
bool failed = false;
TEST_CASE("P25 PDU Confirmed ExtAddr Test", "[p25][pdu_confirmed_extaddr]") {
bool failed = false;
INFO("P25 PDU Confirmed Ext Addr Test");
INFO("P25 PDU Confirmed Ext Addr Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Confirmed_ExtAddr_Test, Test Source", testPDUSource, 30U);
Utils::dump(2U, "P25_PDU_Confirmed_ExtAddr_Test, Test Source", testPDUSource, 30U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::EXT_ADDR);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::EXT_ADDR);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setSrcLLId(0x54321U);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setSrcLLId(0x54321U);
dataHeader.calculateLength(testLength);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, true, false, testPDUSource, &bitLength);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, true, false, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Confirmed_ExtAddr_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_ExtAddr_Test, Assembled PDU", ret.get(), bitLength / 8);
LogInfoEx("T", "P25_PDU_Confirmed_ExtAddr_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_ExtAddr_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "P25_PDU_Confirmed_ExtAddr_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
LogInfoEx("T", "P25_PDU_Confirmed_ExtAddr_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_ExtAddr_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_ExtAddr_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Confirmed_ExtAddr_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Confirmed_ExtAddr_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,104 +21,102 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Confirmed_Large_Test", "[P25 PDU Confirmed Large Test]") {
SECTION("P25_PDU_Confirmed_Large_Test") {
bool failed = false;
INFO("P25 PDU Confirmed Large Test");
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 120U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Confirmed_Large_Test, Test Source", testPDUSource, 120U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(false);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_Large_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_Large_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
TEST_CASE("P25 PDU Confirmed Large Test", "[p25][pdu_confirmed_large]") {
bool failed = false;
INFO("P25 PDU Confirmed Large Test");
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 120U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Confirmed_Large_Test, Test Source", testPDUSource, 120U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(false);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Confirmed_Large_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Confirmed_Large_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Confirmed_Large_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Confirmed_Large_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,98 +21,96 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Confirmed_Small_Test", "[P25 PDU Confirmed Small Test]") {
SECTION("P25_PDU_Confirmed_Small_Test") {
bool failed = false;
TEST_CASE("P25 PDU Confirmed Small Test", "[p25][pdu_confirmed_small]") {
bool failed = false;
INFO("P25 PDU Confirmed Small Test");
INFO("P25 PDU Confirmed Small Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Assembler assembler = Assembler();
Utils::dump(2U, "PDU_Confirmed_Small_Test, Test Source", testPDUSource, 30U);
Utils::dump(2U, "PDU_Confirmed_Small_Test, Test Source", testPDUSource, 30U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::CONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.calculateLength(testLength);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength);
LogInfoEx("T", "PDU_Confirmed_Small_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "PDU_Confirmed_Small_Test, Assembled PDU", ret.get(), bitLength / 8);
LogInfoEx("T", "PDU_Confirmed_Small_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "PDU_Confirmed_Small_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "PDU_Confirmed_Small_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
LogInfoEx("T", "PDU_Confirmed_Small_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "PDU_Confirmed_Small_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "PDU_Confirmed_Small_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "PDU_Confirmed_Small_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "PDU_Confirmed_Small_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,109 +21,107 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Unconfirmed_AuxES_Test", "[P25 PDU Unconfirmed Aux ES Test]") {
SECTION("P25_PDU_Unconfirmed_AuxES_Test") {
bool failed = false;
TEST_CASE("P25 PDU Unconfirmed AuxES Test", "[p25][pdu_unconfirmed_auxes]") {
bool failed = false;
INFO("P25 PDU Unconfirmed Aux ES Test");
INFO("P25 PDU Unconfirmed Aux ES Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
uint8_t encryptMI[] =
{
0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67
};
uint8_t encryptMI[] =
{
0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Unconfirmed_AuxES_Test, Test Source", testPDUSource, 30U);
Utils::dump(2U, "P25_PDU_Unconfirmed_AuxES_Test, Test Source", testPDUSource, 30U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::UNCONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::ENC_USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::UNCONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::ENC_USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setMI(encryptMI);
dataHeader.setAlgId(ALGO_AES_256);
dataHeader.setKId(0x2F62U);
dataHeader.setMI(encryptMI);
dataHeader.setAlgId(ALGO_AES_256);
dataHeader.setKId(0x2F62U);
dataHeader.calculateLength(testLength);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, true, testPDUSource, &bitLength);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, true, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Unconfirmed_AuxES_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Unconfirmed_AuxES_Test, Assembled PDU", ret.get(), bitLength / 8);
LogInfoEx("T", "P25_PDU_Unconfirmed_AuxES_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Unconfirmed_AuxES_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "P25_PDU_Unconfirmed_AuxES_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
LogInfoEx("T", "P25_PDU_Unconfirmed_AuxES_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Unconfirmed_AuxES_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Unconfirmed_AuxES_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Unconfirmed_AuxES_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Unconfirmed_AuxES_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,101 +21,99 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Unconfirmed_ExtAddr_Test", "[P25 PDU Unconfirmed Ext Addr Test]") {
SECTION("P25_PDU_Unconfirmed_ExtAddr_Test") {
bool failed = false;
TEST_CASE("P25 PDU Unconfirmed ExtAddr Test", "[p25][pdu_unconfirmed_extaddr]") {
bool failed = false;
INFO("P25 PDU Unconfirmed Ext Addr Test");
INFO("P25 PDU Unconfirmed Ext Addr Test");
srand((unsigned int)time(NULL));
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
// test PDU data
uint32_t testLength = 30U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Unconfirmed_ExtAddr_Test, Test Source", testPDUSource, 30U);
Utils::dump(2U, "P25_PDU_Unconfirmed_ExtAddr_Test, Test Source", testPDUSource, 30U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::UNCONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::EXT_ADDR);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::UNCONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(true);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::EXT_ADDR);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setSrcLLId(0x54321U);
dataHeader.setEXSAP(PDUSAP::USER_DATA);
dataHeader.setSrcLLId(0x54321U);
dataHeader.calculateLength(testLength);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, true, false, testPDUSource, &bitLength);
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, true, false, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Unconfirmed_ExtAddr_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Unconfirmed_ExtAddr_Test, Assembled PDU", ret.get(), bitLength / 8);
LogInfoEx("T", "P25_PDU_Unconfirmed_ExtAddr_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Unconfirmed_ExtAddr_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
LogInfoEx("T", "P25_PDU_Unconfirmed_ExtAddr_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
LogInfoEx("T", "P25_PDU_Unconfirmed_ExtAddr_Test, i = %u", i);
Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Unconfirmed_ExtAddr_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Unconfirmed_ExtAddr_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Unconfirmed_ExtAddr_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Unconfirmed_ExtAddr_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -21,101 +21,99 @@ using namespace p25::data;
#include <stdlib.h>
#include <time.h>
TEST_CASE("PDU_Unconfirmed_Test", "[P25 PDU Unconfirmed Test]") {
SECTION("P25_PDU_Unconfirmed_Test") {
bool failed = false;
INFO("P25 PDU Unconfirmed Test");
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 120U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Unconfirmed_Test, Test Source", testPDUSource, 120U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::UNCONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(false);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Unconfirmed_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Unconfirmed_Test, PDU Disassemble, block %u", blockCnt);
}
blockCnt++;
TEST_CASE("P25 PDU Unconfirmed Test", "[p25][pdu_unconfirmed]") {
bool failed = false;
INFO("P25 PDU Unconfirmed Test");
srand((unsigned int)time(NULL));
g_logDisplayLevel = 1U;
// test PDU data
uint32_t testLength = 120U;
uint8_t testPDUSource[] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11,
0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21
};
data::Assembler::setVerbose(true);
data::Assembler::setDumpPDUData(true);
Assembler assembler = Assembler();
Utils::dump(2U, "P25_PDU_Unconfirmed_Test, Test Source", testPDUSource, 120U);
DataHeader dataHeader = DataHeader();
dataHeader.setFormat(PDUFormatType::UNCONFIRMED);
dataHeader.setMFId(MFG_STANDARD);
dataHeader.setAckNeeded(false);
dataHeader.setOutbound(true);
dataHeader.setSAP(PDUSAP::USER_DATA);
dataHeader.setLLId(0x12345U);
dataHeader.setFullMessage(true);
dataHeader.setBlocksToFollow(1U);
dataHeader.calculateLength(testLength);
/*
** self-sanity check the assembler chain
*/
uint32_t bitLength = 0U;
UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength);
LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8);
Utils::dump(2U, "P25_PDU_Unconfirmed_Test, Assembled PDU", ret.get(), bitLength / 8);
if (ret == nullptr)
failed = true;
if (!failed) {
uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES];
::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES);
// for the purposes of our test we strip the pad bit length from the bit length
bitLength -= dataHeader.getPadLength() * 8U;
uint32_t blockCnt = 0U;
for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) {
::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES);
Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS);
bool ret = false;
if (blockCnt == 0U)
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true);
else
ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES);
if (!ret) {
failed = true;
::LogError("T", "P25_PDU_Unconfirmed_Test, PDU Disassemble, block %u", blockCnt);
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
blockCnt++;
}
if (assembler.getComplete()) {
uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U];
uint32_t pduUserDataLength = assembler.getUserDataLength();
assembler.getUserData(pduUserData2);
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Unconfirmed_Test, INVALID AT IDX %d", i);
failed = true;
}
for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) {
if (pduUserData2[i] != testPDUSource[i]) {
::LogError("T", "P25_PDU_Unconfirmed_Test, INVALID AT IDX %d", i);
failed = true;
}
}
}
REQUIRE(failed==false);
}
REQUIRE(failed==false);
}

@ -0,0 +1,324 @@
// 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/p25/lc/tdulc/LC_GROUP.h"
#include "common/p25/lc/tdulc/LC_PRIVATE.h"
#include "common/p25/P25Defines.h"
#include "common/edac/Golay24128.h"
#include "common/edac/RS634717.h"
using namespace p25;
using namespace p25::defines;
using namespace p25::lc;
using namespace p25::lc::tdulc;
#include <catch2/catch_test_macros.hpp>
TEST_CASE("TDULC", "[p25][tdulc]") {
SECTION("Constants_Valid") {
// Verify TDULC length constants
REQUIRE(P25_TDULC_LENGTH_BYTES == 18); // Total length with RS FEC
REQUIRE(P25_TDULC_PAYLOAD_LENGTH_BYTES == 8); // Payload only
REQUIRE(P25_TDULC_FEC_LENGTH_BYTES == 36); // After Golay encoding
REQUIRE(P25_TDULC_FRAME_LENGTH_BYTES == 54); // Full frame with preamble
}
SECTION("Golay_Encode_Decode") {
// Test Golay (24,12,8) FEC encoding/decoding
uint8_t input[P25_TDULC_LENGTH_BYTES];
::memset(input, 0x00, P25_TDULC_LENGTH_BYTES);
// Set test pattern
input[0] = 0x12;
input[1] = 0x34;
input[2] = 0x56;
input[3] = 0x78;
// Encode with Golay
uint8_t encoded[P25_TDULC_FEC_LENGTH_BYTES + 1];
::memset(encoded, 0x00, P25_TDULC_FEC_LENGTH_BYTES + 1);
edac::Golay24128::encode24128(encoded, input, P25_TDULC_LENGTH_BYTES);
// Decode with Golay
uint8_t decoded[P25_TDULC_LENGTH_BYTES];
::memset(decoded, 0x00, P25_TDULC_LENGTH_BYTES);
edac::Golay24128::decode24128(decoded, encoded, P25_TDULC_LENGTH_BYTES);
// Verify round-trip
for (uint32_t i = 0; i < P25_TDULC_LENGTH_BYTES; i++) {
REQUIRE(decoded[i] == input[i]);
}
}
SECTION("RS_241213_Encode_Decode") {
// Test RS (24,12,13) FEC encoding/decoding
edac::RS634717 rs;
uint8_t input[P25_TDULC_LENGTH_BYTES];
::memset(input, 0x00, P25_TDULC_LENGTH_BYTES);
// Set test pattern in first 12 bytes (data portion)
for (uint32_t i = 0; i < 12; i++) {
input[i] = (uint8_t)(i * 0x11);
}
// Encode RS (adds 6 parity bytes)
rs.encode241213(input);
// Decode RS
bool result = rs.decode241213(input);
REQUIRE(result == true);
}
SECTION("LCO_Values") {
// Test various LCO values (6 bits)
uint8_t lcoValues[] = { 0x00, 0x01, 0x02, 0x03, 0x20, 0x3F };
for (auto lco : lcoValues) {
LC_GROUP tdulc;
tdulc.setLCO(lco & 0x3F); // Mask to 6 bits
REQUIRE(tdulc.getLCO() == (lco & 0x3F));
}
}
SECTION("Emergency_Flag") {
// Test emergency flag
LC_GROUP tdulc;
tdulc.setEmergency(false);
REQUIRE(tdulc.getEmergency() == false);
tdulc.setEmergency(true);
REQUIRE(tdulc.getEmergency() == true);
}
SECTION("Encrypted_Flag") {
// Test encrypted flag
LC_GROUP tdulc;
tdulc.setEncrypted(false);
REQUIRE(tdulc.getEncrypted() == false);
tdulc.setEncrypted(true);
REQUIRE(tdulc.getEncrypted() == true);
}
SECTION("Priority_Values") {
// Test priority values (3 bits: 0-7)
for (uint8_t priority = 0; priority <= 7; priority++) {
LC_GROUP tdulc;
tdulc.setPriority(priority);
REQUIRE(tdulc.getPriority() == priority);
}
}
SECTION("Group_Flag") {
// Test group flag
LC_GROUP groupTdulc;
groupTdulc.setGroup(true);
REQUIRE(groupTdulc.getGroup() == true);
LC_PRIVATE privateTdulc;
privateTdulc.setGroup(false);
REQUIRE(privateTdulc.getGroup() == false);
}
SECTION("SrcId_Values") {
// Test source ID values (24 bits)
uint32_t srcIds[] = { 0x000000, 0x000001, 0x123456, 0xFFFFFE, 0xFFFFFF };
for (auto srcId : srcIds) {
LC_GROUP tdulc;
tdulc.setSrcId(srcId & 0xFFFFFF); // Mask to 24 bits
REQUIRE(tdulc.getSrcId() == (srcId & 0xFFFFFF));
}
}
SECTION("DstId_Values") {
// Test destination ID values (16 bits for group)
uint32_t dstIds[] = { 0x0000, 0x0001, 0x1234, 0xFFFE, 0xFFFF };
for (auto dstId : dstIds) {
LC_GROUP tdulc;
tdulc.setDstId(dstId & 0xFFFF); // Mask to 16 bits
REQUIRE(tdulc.getDstId() == (dstId & 0xFFFF));
}
}
SECTION("MfgId_Values") {
// Test manufacturer ID values
uint8_t mfgIds[] = { 0x00, 0x01, 0x90, 0xFF };
for (auto mfgId : mfgIds) {
LC_GROUP tdulc;
tdulc.setMFId(mfgId);
REQUIRE(tdulc.getMFId() == mfgId);
}
}
SECTION("AllZeros_Pattern") {
// Test all-zeros pattern
LC_GROUP tdulc;
tdulc.setLCO(0x00);
tdulc.setMFId(0x00);
tdulc.setSrcId(0x000000);
tdulc.setDstId(0x0000);
tdulc.setEmergency(false);
tdulc.setEncrypted(false);
tdulc.setPriority(0);
REQUIRE(tdulc.getLCO() == 0x00);
REQUIRE(tdulc.getMFId() == 0x00);
REQUIRE(tdulc.getSrcId() == 0x000000);
REQUIRE(tdulc.getDstId() == 0x0000);
REQUIRE(tdulc.getEmergency() == false);
REQUIRE(tdulc.getEncrypted() == false);
REQUIRE(tdulc.getPriority() == 0);
}
SECTION("MaxValues_Pattern") {
// Test maximum values pattern
LC_GROUP tdulc;
tdulc.setLCO(0x3F); // 6 bits max
tdulc.setMFId(0xFF); // 8 bits max
tdulc.setSrcId(0xFFFFFF); // 24 bits max
tdulc.setDstId(0xFFFF); // 16 bits max
tdulc.setEmergency(true);
tdulc.setEncrypted(true);
tdulc.setPriority(7); // 3 bits max
REQUIRE(tdulc.getLCO() == 0x3F);
REQUIRE(tdulc.getMFId() == 0xFF);
REQUIRE(tdulc.getSrcId() == 0xFFFFFF);
REQUIRE(tdulc.getDstId() == 0xFFFF);
REQUIRE(tdulc.getEmergency() == true);
REQUIRE(tdulc.getEncrypted() == true);
REQUIRE(tdulc.getPriority() == 7);
}
SECTION("Group_Copy_Constructor") {
// Test copy constructor for LC_GROUP
LC_GROUP tdulc1;
tdulc1.setLCO(0x00);
tdulc1.setMFId(0x90);
tdulc1.setSrcId(0x123456);
tdulc1.setDstId(0xABCD);
tdulc1.setEmergency(true);
tdulc1.setEncrypted(false);
tdulc1.setPriority(5);
LC_GROUP tdulc2(tdulc1);
REQUIRE(tdulc2.getLCO() == tdulc1.getLCO());
REQUIRE(tdulc2.getMFId() == tdulc1.getMFId());
REQUIRE(tdulc2.getSrcId() == tdulc1.getSrcId());
REQUIRE(tdulc2.getDstId() == tdulc1.getDstId());
REQUIRE(tdulc2.getEmergency() == tdulc1.getEmergency());
REQUIRE(tdulc2.getEncrypted() == tdulc1.getEncrypted());
REQUIRE(tdulc2.getPriority() == tdulc1.getPriority());
}
SECTION("Private_Copy_Constructor") {
// Test copy constructor for LC_PRIVATE
LC_PRIVATE tdulc1;
tdulc1.setLCO(0x03);
tdulc1.setMFId(0x00);
tdulc1.setSrcId(0xABCDEF);
tdulc1.setDstId(0x123456);
tdulc1.setEmergency(false);
tdulc1.setEncrypted(true);
tdulc1.setPriority(3);
LC_PRIVATE tdulc2(tdulc1);
REQUIRE(tdulc2.getLCO() == tdulc1.getLCO());
REQUIRE(tdulc2.getMFId() == tdulc1.getMFId());
REQUIRE(tdulc2.getSrcId() == tdulc1.getSrcId());
REQUIRE(tdulc2.getDstId() == tdulc1.getDstId());
REQUIRE(tdulc2.getEmergency() == tdulc1.getEmergency());
REQUIRE(tdulc2.getEncrypted() == tdulc1.getEncrypted());
REQUIRE(tdulc2.getPriority() == tdulc1.getPriority());
}
SECTION("Golay_ErrorCorrection") {
// Test Golay error correction capability
uint8_t input[P25_TDULC_LENGTH_BYTES];
::memset(input, 0x00, P25_TDULC_LENGTH_BYTES);
// Set known pattern
input[0] = 0xAA;
input[1] = 0x55;
input[2] = 0xF0;
input[3] = 0x0F;
// Encode
uint8_t encoded[P25_TDULC_FEC_LENGTH_BYTES + 1];
::memset(encoded, 0x00, P25_TDULC_FEC_LENGTH_BYTES + 1);
edac::Golay24128::encode24128(encoded, input, P25_TDULC_LENGTH_BYTES);
// Introduce single bit error (Golay can correct up to 3 bit errors)
encoded[5] ^= 0x01;
// Decode (should correct the error)
uint8_t decoded[P25_TDULC_LENGTH_BYTES];
::memset(decoded, 0x00, P25_TDULC_LENGTH_BYTES);
edac::Golay24128::decode24128(decoded, encoded, P25_TDULC_LENGTH_BYTES);
// Verify correction
REQUIRE(decoded[0] == input[0]);
REQUIRE(decoded[1] == input[1]);
REQUIRE(decoded[2] == input[2]);
REQUIRE(decoded[3] == input[3]);
}
SECTION("RS_ErrorCorrection") {
// Test RS error correction capability
edac::RS634717 rs;
uint8_t data[P25_TDULC_LENGTH_BYTES];
::memset(data, 0x00, P25_TDULC_LENGTH_BYTES);
// Set known data pattern in first 12 bytes
for (uint32_t i = 0; i < 12; i++) {
data[i] = (uint8_t)(0xAA - i);
}
// Encode RS
rs.encode241213(data);
// Save original
uint8_t original[P25_TDULC_LENGTH_BYTES];
::memcpy(original, data, P25_TDULC_LENGTH_BYTES);
// Introduce errors (RS can correct up to 6 byte errors with (24,12,13))
data[2] ^= 0xFF;
data[5] ^= 0xFF;
// Decode (should correct the errors)
bool result = rs.decode241213(data);
REQUIRE(result == true);
// Verify correction
for (uint32_t i = 0; i < 12; i++) {
REQUIRE(data[i] == original[i]);
}
}
}

@ -0,0 +1,329 @@
// 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/p25/lc/tsbk/OSP_SCCB.h"
#include "common/p25/lc/tsbk/OSP_TSBK_RAW.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("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)
// 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);
// Verify encoded matches input
for (uint32_t i = 0; i < P25_TSBK_LENGTH_BYTES - 2; i++) {
REQUIRE(encoded[i] == testTSBK[i]);
}
// Decode back
OSP_TSBK_RAW tsbk2;
bool result = tsbk2.decode(encoded, true);
REQUIRE(result == true);
REQUIRE(tsbk2.getLCO() == (testTSBK[0] & 0x3F));
REQUIRE(tsbk2.getMFId() == testTSBK[1]);
}
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 + 2, 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("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));
}
}
}
Loading…
Cancel
Save

Powered by TurnKey Linux.