finally what I was trying to get to -- add testcases for the new P2 MAC PDU logic; correct implementation problems with the P2 MAC PDU handling for OEMI and IEMI (the implementation still requires handling scrambling);

r05a04_dev
Bryan Biedenkapp 3 weeks ago
parent d1fdf590ee
commit dc6d92b702

@ -39,6 +39,7 @@ namespace p25
*/ */
/** @name Frame Lengths and Misc Constants */ /** @name Frame Lengths and Misc Constants */
// TIA-102.BAAA-B Section 4 and 5; TIA-102.AABB-B Section 5
const uint32_t P25_HDU_FRAME_LENGTH_BYTES = 99U; const uint32_t P25_HDU_FRAME_LENGTH_BYTES = 99U;
const uint32_t P25_HDU_FRAME_LENGTH_BITS = P25_HDU_FRAME_LENGTH_BYTES * 8U; const uint32_t P25_HDU_FRAME_LENGTH_BITS = P25_HDU_FRAME_LENGTH_BYTES * 8U;
@ -63,20 +64,28 @@ namespace p25
const uint32_t P25_TDULC_FRAME_LENGTH_BYTES = 54U; const uint32_t P25_TDULC_FRAME_LENGTH_BYTES = 54U;
const uint32_t P25_TDULC_FRAME_LENGTH_BITS = P25_TDULC_FRAME_LENGTH_BYTES * 8U; const uint32_t P25_TDULC_FRAME_LENGTH_BITS = P25_TDULC_FRAME_LENGTH_BYTES * 8U;
const uint32_t P25_NID_LENGTH_BYTES = 8U;
const uint32_t P25_NID_LENGTH_BITS = P25_NID_LENGTH_BYTES * 8U;
// TIA-102.BBAC-A Section 4
const uint32_t P25_P2_FRAME_LENGTH_BYTES = 40U; const uint32_t P25_P2_FRAME_LENGTH_BYTES = 40U;
const uint32_t P25_P2_FRAME_LENGTH_BITS = P25_P2_FRAME_LENGTH_BYTES * 8U; const uint32_t P25_P2_FRAME_LENGTH_BITS = P25_P2_FRAME_LENGTH_BYTES * 8U;
const uint32_t P25_P2_IEMI_LENGTH_BITS = 312U; const uint32_t P25_P2_IEMI_LENGTH_BITS = 312U;
const uint32_t P25_P2_IEMI_LENGTH_BYTES = (P25_P2_IEMI_LENGTH_BITS / 8U) + 1U; const uint32_t P25_P2_IEMI_LENGTH_BYTES = (P25_P2_IEMI_LENGTH_BITS / 8U) + 1U;
const uint32_t P25_P2_IEMI_MAC_LENGTH_BITS = 148U; const uint32_t P25_P2_IEMI_WSYNC_LENGTH_BITS = 276U;
const uint32_t P25_P2_IEMI_WSYNC_LENGTH_BYTES = (P25_P2_IEMI_WSYNC_LENGTH_BITS / 8U) + 1U;
const uint32_t P25_P2_IEMI_MAC_LENGTH_BITS = 156U;
const uint32_t P25_P2_IEMI_MAC_LENGTH_BYTES = (P25_P2_IEMI_MAC_LENGTH_BITS / 8U) + 1U; const uint32_t P25_P2_IEMI_MAC_LENGTH_BYTES = (P25_P2_IEMI_MAC_LENGTH_BITS / 8U) + 1U;
const uint32_t P25_P2_SOEMI_MAC_LENGTH_BITS = 156U;
const uint32_t P25_P2_SOEMI_MAC_LENGTH_BYTES = (P25_P2_SOEMI_MAC_LENGTH_BITS / 8U) + 1U;
const uint32_t P25_P2_SOEMI_LENGTH_BITS = 270U; const uint32_t P25_P2_SOEMI_LENGTH_BITS = 270U;
const uint32_t P25_P2_SOEMI_LENGTH_BYTES = (P25_P2_SOEMI_LENGTH_BITS / 8U) + 1U; const uint32_t P25_P2_SOEMI_LENGTH_BYTES = (P25_P2_SOEMI_LENGTH_BITS / 8U) + 1U;
const uint32_t P25_P2_IOEMI_MAC_LENGTH_BITS = 172U;
const uint32_t P25_P2_IOEMI_MAC_LENGTH_BYTES = (P25_P2_IOEMI_MAC_LENGTH_BITS / 8U) + 1U;
const uint32_t P25_NID_LENGTH_BYTES = 8U; const uint32_t P25_P2_IOEMI_MAC_LENGTH_BITS = 180U;
const uint32_t P25_NID_LENGTH_BITS = P25_NID_LENGTH_BYTES * 8U; const uint32_t P25_P2_IOEMI_MAC_LENGTH_BYTES = (P25_P2_IOEMI_MAC_LENGTH_BITS / 8U) + 1U;
// TIA-102.BAAA-B Section 7.3 // TIA-102.BAAA-B Section 7.3
// 5 5 7 5 F 5 F F 7 7 F F // 5 5 7 5 F 5 F F 7 7 F F

@ -486,52 +486,75 @@ void LC::encodeLDU2(uint8_t* data)
/* Decode a IEMI VCH MAC PDU. */ /* Decode a IEMI VCH MAC PDU. */
bool LC::decodeVCH_MACPDU_IEMI(const uint8_t* data) bool LC::decodeVCH_MACPDU_IEMI(const uint8_t* data, bool sync)
{ {
assert(data != nullptr); assert(data != nullptr);
// determine buffer size based on sync flag
uint32_t lengthBits = sync ? P25_P2_IEMI_WSYNC_LENGTH_BITS : P25_P2_IEMI_LENGTH_BITS;
uint32_t lengthBytes = sync ? P25_P2_IEMI_WSYNC_LENGTH_BYTES : P25_P2_IEMI_LENGTH_BYTES;
// decode the Phase 2 DUID // decode the Phase 2 DUID
uint8_t duid[1U], raw[P25_P2_IEMI_LENGTH_BYTES]; uint8_t duid[1U], raw[P25_P2_IEMI_LENGTH_BYTES]; // Use max size for stack allocation
::memset(duid, 0x00U, 1U); ::memset(duid, 0x00U, 1U);
::memset(raw, 0x00U, P25_P2_IEMI_LENGTH_BYTES); ::memset(raw, 0x00U, lengthBytes);
for (uint8_t i = 0U; i < 8U; i++) { // DUID bit extraction differs based on sync flag
uint32_t n = i + 72U; // skip field 1 if (sync) {
if (i >= 2U) // IEMI with sync: 14 LSB sync bits + 36-bit field 1, then DUIDs at different positions
n += 72U; // skip field 2 for (uint8_t i = 0U; i < 8U; i++) {
if (i >= 4U) uint32_t n = i + 14U + 36U; // skip 14-bit sync + field 1 (36 bits)
n += 96U; // skip field 3 if (i >= 2U)
if (i >= 6U) n += 72U; // skip field 2
n += 72U; // skip field 4 if (i >= 4U)
n += 96U; // skip field 3
if (i >= 6U)
n += 72U; // skip field 4
bool b = READ_BIT(data, n); bool b = READ_BIT(data, n);
WRITE_BIT(raw, i, b); WRITE_BIT(raw, i, b);
}
} else {
// IEMI without sync: 72-bit field 1, then DUIDs
for (uint8_t i = 0U; i < 8U; i++) {
uint32_t n = i + 72U; // skip field 1
if (i >= 2U)
n += 72U; // skip field 2
if (i >= 4U)
n += 96U; // skip field 3
if (i >= 6U)
n += 72U; // skip field 4
bool b = READ_BIT(data, n);
WRITE_BIT(raw, i, b);
}
} }
decodeP2_DUIDHamming(raw, duid); decodeP2_DUIDHamming(raw, duid);
m_p2DUID = duid[0U] >> 4U; m_p2DUID = duid[0U] >> 4U;
if (m_p2DUID == P2_DUID::VTCH_4V || m_p2DUID == P2_DUID::VTCH_2V) if (m_p2DUID == P2_DUID::VTCH_4V || m_p2DUID == P2_DUID::VTCH_2V || !sync)
return true; // don't handle 4V or 2V voice PDUs here -- user code will handle return true; // don't handle 4V or 2V voice PDUs here -- user code will handle
else { else {
::memset(raw, 0x00U, P25_P2_IEMI_LENGTH_BYTES); ::memset(raw, 0x00U, lengthBytes);
for (uint32_t i = 0U; i < P25_P2_IEMI_LENGTH_BITS; i++) { // IEMI with sync: extract data bits (skip 14-bit sync and DUIDs)
uint32_t n = i; for (uint32_t i = 0U; i < lengthBits; i++) {
if (i >= 72U) uint32_t n = i + 14U; // Skip 14-bit sync
n += 2U; // skip DUID 1 if (i >= 36U)
if (i >= 146U) n += 2U; // skip DUID 1 after field 1 (36 bits)
n += 2U; // skip DUID 2 if (i >= 108U)
if (i >= 242U) n += 2U; // skip DUID 2 after field 2 (36+72)
n += 2U; // skip DUID 3 if (i >= 204U)
n += 2U; // skip DUID 3 after field 3 (36+72+96)
bool b = READ_BIT(data, n); bool b = READ_BIT(data, n);
WRITE_BIT(raw, i, b); WRITE_BIT(raw, i, b);
} }
#if DEBUG_P25_MAC_PDU #if DEBUG_P25_MAC_PDU
Utils::dump(2U, "P25, LC::decodeVCH_MACPDU_IEMI(), MAC PDU", raw, P25_P2_IEMI_LENGTH_BYTES); Utils::dump(2U, "P25, LC::decodeVCH_MACPDU_IEMI(), MAC PDU", raw, lengthBytes);
#endif #endif
// decode RS (46,26,21) FEC // decode RS (46,26,21) FEC
@ -543,12 +566,12 @@ bool LC::decodeVCH_MACPDU_IEMI(const uint8_t* data)
} }
} }
catch (...) { catch (...) {
Utils::dump(2U, "P25, LC::decodeVCH_MACPDU_IEMI(), RS excepted with input data", raw, P25_P2_IEMI_LENGTH_BYTES); Utils::dump(2U, "P25, LC::decodeVCH_MACPDU_IEMI(), RS excepted with input data", raw, lengthBytes);
return false; return false;
} }
#if DEBUG_P25_MAC_PDU #if DEBUG_P25_MAC_PDU
Utils::dump(2U, "P25, LC::decodeVCH_MACPDU_IEMI(), MAC PDU", raw, P25_P2_IEMI_LENGTH_BYTES); Utils::dump(2U, "P25, LC::decodeVCH_MACPDU_IEMI(), MAC PDU", raw, lengthBytes);
#endif #endif
// are we decoding a FACCH with scrambling? // are we decoding a FACCH with scrambling?
@ -561,7 +584,7 @@ bool LC::decodeVCH_MACPDU_IEMI(const uint8_t* data)
/* TODO: if scrambled handle scrambling */ /* TODO: if scrambled handle scrambling */
} }
return decodeMACPDU(raw); return decodeMACPDU(raw, P25_P2_IEMI_MAC_LENGTH_BITS);
} }
return true; return true;
@ -610,8 +633,8 @@ bool LC::decodeVCH_MACPDU_OEMI(const uint8_t* data, bool sync)
if (i >= 198U) if (i >= 198U)
n += 2U; // skip DUID 3 n += 2U; // skip DUID 3
bool b = READ_BIT(data, i); bool b = READ_BIT(data, n);
WRITE_BIT(raw, n, b); WRITE_BIT(raw, i, b);
} }
#if DEBUG_P25_MAC_PDU #if DEBUG_P25_MAC_PDU
@ -642,8 +665,8 @@ bool LC::decodeVCH_MACPDU_OEMI(const uint8_t* data, bool sync)
if (i >= 168U) if (i >= 168U)
n += 2U; // skip DUID 3 n += 2U; // skip DUID 3
bool b = READ_BIT(data, i); bool b = READ_BIT(data, n);
WRITE_BIT(raw, n, b); WRITE_BIT(raw, i, b);
} }
#if DEBUG_P25_MAC_PDU #if DEBUG_P25_MAC_PDU
@ -678,7 +701,7 @@ bool LC::decodeVCH_MACPDU_OEMI(const uint8_t* data, bool sync)
/* TODO: if scrambled handle scrambling */ /* TODO: if scrambled handle scrambling */
} }
return decodeMACPDU(raw); return decodeMACPDU(raw, sync ? P25_P2_SOEMI_MAC_LENGTH_BITS : P25_P2_IOEMI_MAC_LENGTH_BITS);
} }
return true; return true;
@ -694,7 +717,7 @@ void LC::encodeVCH_MACPDU(uint8_t* data, bool sync)
::memset(raw, 0x00U, P25_P2_IEMI_LENGTH_BYTES); ::memset(raw, 0x00U, P25_P2_IEMI_LENGTH_BYTES);
if (m_p2DUID != P2_DUID::VTCH_4V && m_p2DUID != P2_DUID::VTCH_2V) { if (m_p2DUID != P2_DUID::VTCH_4V && m_p2DUID != P2_DUID::VTCH_2V) {
encodeMACPDU(raw, sync); encodeMACPDU(raw, sync ? P25_P2_SOEMI_MAC_LENGTH_BITS : P25_P2_IOEMI_MAC_LENGTH_BITS);
#if DEBUG_P25_MAC_PDU #if DEBUG_P25_MAC_PDU
Utils::dump(2U, "P25, LC::encodeVCH_MACPDU(), MAC PDU", raw, P25_P2_IEMI_LENGTH_BYTES); Utils::dump(2U, "P25, LC::encodeVCH_MACPDU(), MAC PDU", raw, P25_P2_IEMI_LENGTH_BYTES);
@ -1057,11 +1080,11 @@ void LC::encodeLC(uint8_t* rs)
/* Decode MAC PDU. */ /* Decode MAC PDU. */
bool LC::decodeMACPDU(const uint8_t* raw) bool LC::decodeMACPDU(const uint8_t* raw, uint32_t macLength)
{ {
assert(raw != nullptr); assert(raw != nullptr);
bool ret = edac::CRC::checkCRC12(raw, P25_P2_IEMI_MAC_LENGTH_BITS); bool ret = edac::CRC::checkCRC12(raw, macLength - 12U);
if (!ret) { if (!ret) {
if (s_warnCRC) { if (s_warnCRC) {
LogWarning(LOG_P25, "TSBK::decode(), failed CRC CCITT-162 check"); LogWarning(LOG_P25, "TSBK::decode(), failed CRC CCITT-162 check");
@ -1173,11 +1196,12 @@ bool LC::decodeMACPDU(const uint8_t* raw)
if (p2MCOData != nullptr) if (p2MCOData != nullptr)
delete[] p2MCOData; delete[] p2MCOData;
p2MCOData = new uint8_t[P25_P2_IOEMI_MAC_LENGTH_BYTES]; uint32_t macLengthBytes = (macLength / 8U) + ((macLength % 8U) ? 1U : 0U);
::memset(p2MCOData, 0x00U, P25_P2_IOEMI_MAC_LENGTH_BYTES); p2MCOData = new uint8_t[macLengthBytes];
::memset(p2MCOData, 0x00U, macLengthBytes);
// this will include the entire MCO (and depending on message length multiple MCOs) // this will include the entire MCO (and depending on message length multiple MCOs)
::memcpy(p2MCOData, raw + 1U, P25_P2_IEMI_MAC_LENGTH_BYTES - 3U); // excluding MAC PDU header and CRC ::memcpy(p2MCOData, raw + 1U, macLengthBytes - 3U); // excluding MAC PDU header and CRC
} }
break; break;
@ -1191,7 +1215,7 @@ bool LC::decodeMACPDU(const uint8_t* raw)
/* Encode MAC PDU. */ /* Encode MAC PDU. */
void LC::encodeMACPDU(uint8_t* raw, bool sync) void LC::encodeMACPDU(uint8_t* raw, uint32_t macLength)
{ {
assert(raw != nullptr); assert(raw != nullptr);
@ -1267,11 +1291,8 @@ void LC::encodeMACPDU(uint8_t* raw, bool sync)
} else { } else {
if (p2MCOData != nullptr) { if (p2MCOData != nullptr) {
// this will include the entire MCO (and depending on message length multiple MCOs) // this will include the entire MCO (and depending on message length multiple MCOs)
if (sync) { uint32_t macLengthBytes = (macLength / 8U) + ((macLength % 8U) ? 1U : 0U);
::memcpy(raw + 1U, p2MCOData, P25_P2_IEMI_MAC_LENGTH_BYTES - 3U); // excluding MAC PDU header and CRC ::memcpy(raw + 1U, p2MCOData, macLengthBytes - 3U); // excluding MAC PDU header and CRC
} else {
::memcpy(raw + 1U, p2MCOData, P25_P2_IOEMI_MAC_LENGTH_BYTES - 3U); // excluding MAC PDU header and CRC
}
} }
} }
break; break;
@ -1281,11 +1302,7 @@ void LC::encodeMACPDU(uint8_t* raw, bool sync)
break; break;
} }
if (sync) { edac::CRC::addCRC12(raw, macLength - 12U);
edac::CRC::addCRC12(raw, P25_P2_IEMI_MAC_LENGTH_BITS);
} else {
edac::CRC::addCRC12(raw, P25_P2_IOEMI_MAC_LENGTH_BITS);
}
} }
/* /*

@ -116,9 +116,10 @@ namespace p25
/** /**
* @brief Decode a IEMI VCH MAC PDU. * @brief Decode a IEMI VCH MAC PDU.
* @param data Buffer containing the MAC PDU to decode. * @param data Buffer containing the MAC PDU to decode.
* @param sync Flag indicating if sync is included (true=276 bits with RS, false=312 bits no RS).
* @return bool True, if MAC PDU decoded, otherwise false. * @return bool True, if MAC PDU decoded, otherwise false.
*/ */
bool decodeVCH_MACPDU_IEMI(const uint8_t* data); bool decodeVCH_MACPDU_IEMI(const uint8_t* data, bool sync);
/** /**
* @brief Decode a xOEMI VCH MAC PDU. * @brief Decode a xOEMI VCH MAC PDU.
* @param data Buffer containing the MAC PDU to decode. * @param data Buffer containing the MAC PDU to decode.
@ -155,15 +156,16 @@ namespace p25
/** /**
* @brief Decode MAC PDU. * @brief Decode MAC PDU.
* @param[in] raw Buffer containing the decoded Reed-Solomon MAC PDU data. * @param[in] raw Buffer containing the decoded Reed-Solomon MAC PDU data.
* @param macLength MAC PDU length in bits (156 for IEMI/S-OEMI, 180 for I-OEMI).
* @returns bool True, if MAC PDU is decoded, otherwise false. * @returns bool True, if MAC PDU is decoded, otherwise false.
*/ */
bool decodeMACPDU(const uint8_t* raw); bool decodeMACPDU(const uint8_t* raw, uint32_t macLength = defines::P25_P2_IOEMI_MAC_LENGTH_BITS);
/** /**
* @brief Encode MAC PDU. * @brief Encode MAC PDU.
* @param[out] raw Buffer to encode MAC PDU data. * @param[out] raw Buffer to encode MAC PDU data.
* @param sync Flag indicating if sync is to be included. * @param macLength MAC PDU length in bits (156 for IEMI/S-OEMI, 180 for I-OEMI).
*/ */
void encodeMACPDU(uint8_t* raw, bool sync); void encodeMACPDU(uint8_t* raw, uint32_t macLength = defines::P25_P2_IOEMI_MAC_LENGTH_BITS);
/** @name Encryption data */ /** @name Encryption data */
/** /**

@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Test Suite
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
*
*/
#include "host/Defines.h"
#include "common/edac/RS634717.h"
#include "common/p25/P25Defines.h"
#include "common/p25/lc/LC.h"
#include "common/p25/Sync.h"
#include "common/Log.h"
#include "common/Utils.h"
using namespace edac;
using namespace p25;
using namespace p25::defines;
using namespace p25::lc;
#include <catch2/catch_test_macros.hpp>
#include <stdlib.h>
#include <time.h>
TEST_CASE("P25 Phase 2 VCH MAC PDU I-OEMI (RS 52,30,23) Test", "[p25][p2_vch_macpdu_ioemi]") {
bool failed = false;
INFO("P25 Phase 2 VCH MAC PDU I-OEMI RS (52,30,23) FEC Test");
srand((unsigned int)time(NULL));
// Create LC instance
LC lc;
// Set up test MAC PDU data for Phase 2
lc.setMFId(MFG_STANDARD);
lc.setLCO(P2_MAC_MCO::GROUP); // Phase 2 MAC MCO
lc.setSrcId(1234);
lc.setDstId(9876);
lc.setEmergency(false);
lc.setEncrypted(false);
lc.setPriority(4U);
lc.setGroup(true);
// Set Phase 2 specific fields
lc.setP2DUID(P2_DUID::FACCH_UNSCRAMBLED);
lc.setMACPDUOpcode(P2_MAC_HEADER_OPCODE::IDLE); // IDLE opcode for MAC PDU
lc.setMACPartition(P2_MAC_MCO_PARTITION::UNIQUE); // UNIQUE partition
// Encode VCH MAC PDU (I-OEMI, no sync)
uint8_t encodedData[P25_P2_FRAME_LENGTH_BYTES];
::memset(encodedData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
lc.encodeVCH_MACPDU(encodedData, false);
Utils::dump(2U, "LC::encodeVCH_MACPDU(), I-OEMI Encoded Data", encodedData, P25_P2_FRAME_LENGTH_BYTES);
// Inject 2 errors to test error correction (RS can correct up to 11 errors)
uint32_t errorPos1 = 100;
uint32_t errorPos2 = 150;
uint8_t originalBit1 = READ_BIT(encodedData, errorPos1);
uint8_t originalBit2 = READ_BIT(encodedData, errorPos2);
WRITE_BIT(encodedData, errorPos1, !originalBit1);
WRITE_BIT(encodedData, errorPos2, !originalBit2);
Utils::dump(2U, "LC::decodeVCH_MACPDU_OEMI(), I-OEMI Data (errors injected)", encodedData, P25_P2_FRAME_LENGTH_BYTES);
// Decode VCH MAC PDU (I-OEMI, no sync)
LC decodedLc;
bool ret = decodedLc.decodeVCH_MACPDU_OEMI(encodedData, false);
if (!ret) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), failed to decode I-OEMI MAC PDU");
failed = true;
}
// Verify decoded data matches original
if (decodedLc.getLCO() != lc.getLCO()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), LCO mismatch: expected %02X, got %02X", lc.getLCO(), decodedLc.getLCO());
failed = true;
}
if (decodedLc.getSrcId() != lc.getSrcId()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), Source ID mismatch: expected %u, got %u", lc.getSrcId(), decodedLc.getSrcId());
failed = true;
}
if (decodedLc.getDstId() != lc.getDstId()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), Dest ID mismatch: expected %u, got %u", lc.getDstId(), decodedLc.getDstId());
failed = true;
}
if (decodedLc.getP2DUID() != lc.getP2DUID()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), P2 DUID mismatch: expected %02X, got %02X", lc.getP2DUID(), decodedLc.getP2DUID());
failed = true;
}
REQUIRE(failed == false);
}
TEST_CASE("P25 Phase 2 VCH MAC PDU S-OEMI (RS 45,26,20) Test", "[p25][p2_vch_macpdu_soemi]") {
bool failed = false;
INFO("P25 Phase 2 VCH MAC PDU S-OEMI RS (45,26,20) FEC Test");
srand((unsigned int)time(NULL));
// Create LC instance
LC lc;
// Set up test MAC PDU data for Phase 2
lc.setMFId(MFG_STANDARD);
lc.setLCO(P2_MAC_MCO::PRIVATE); // Phase 2 MAC MCO
lc.setSrcId(5678);
lc.setDstId(1234);
lc.setEmergency(true);
lc.setEncrypted(true);
lc.setPriority(7U);
lc.setGroup(false);
// Set Phase 2 specific fields
lc.setP2DUID(P2_DUID::SACCH_UNSCRAMBLED);
lc.setMACPDUOpcode(P2_MAC_HEADER_OPCODE::IDLE); // IDLE opcode for MAC PDU
lc.setMACPartition(P2_MAC_MCO_PARTITION::UNIQUE); // UNIQUE partition
lc.setAlgId(ALGO_UNENCRYPT); // For test, use unencrypted
// Encode VCH MAC PDU (S-OEMI, with sync)
uint8_t encodedData[P25_P2_FRAME_LENGTH_BYTES];
::memset(encodedData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
lc.encodeVCH_MACPDU(encodedData, true);
Utils::dump(2U, "LC::encodeVCH_MACPDU(), S-OEMI Encoded Data", encodedData, P25_P2_FRAME_LENGTH_BYTES);
// Note: Error injection test is skipped for S-OEMI because hexbit-level errors
// are complex to inject correctly. The I-OEMI test demonstrates RS error correction.
// Decode VCH MAC PDU (S-OEMI, with sync) without error injection
LC decodedLc;
bool ret = decodedLc.decodeVCH_MACPDU_OEMI(encodedData, true);
if (!ret) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), failed to decode S-OEMI MAC PDU");
failed = true;
}
// Verify decoded data matches original
if (decodedLc.getLCO() != lc.getLCO()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), LCO mismatch: expected %02X, got %02X", lc.getLCO(), decodedLc.getLCO());
failed = true;
}
if (decodedLc.getSrcId() != lc.getSrcId()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), Source ID mismatch: expected %u, got %u", lc.getSrcId(), decodedLc.getSrcId());
failed = true;
}
if (decodedLc.getDstId() != lc.getDstId()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), Dest ID mismatch: expected %u, got %u", lc.getDstId(), decodedLc.getDstId());
failed = true;
}
if (decodedLc.getP2DUID() != lc.getP2DUID()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), P2 DUID mismatch: expected %02X, got %02X", lc.getP2DUID(), decodedLc.getP2DUID());
failed = true;
}
if (decodedLc.getEmergency() != lc.getEmergency()) {
::LogError("T", "LC::decodeVCH_MACPDU_OEMI(), Emergency flag mismatch");
failed = true;
}
REQUIRE(failed == false);
}
TEST_CASE("P25 Phase 2 VCH MAC PDU Round-Trip I-OEMI Test", "[p25][p2_vch_macpdu_roundtrip_ioemi]") {
bool failed = false;
INFO("P25 Phase 2 VCH MAC PDU I-OEMI Round-Trip Test");
// Create LC instance with various configurations
LC lc;
lc.setMFId(MFG_STANDARD);
lc.setLCO(P2_MAC_MCO::GROUP); // Phase 2 MAC MCO
lc.setSrcId(12345);
lc.setDstId(67890);
lc.setEmergency(false);
lc.setEncrypted(false);
lc.setPriority(5U);
lc.setGroup(true);
lc.setP2DUID(P2_DUID::FACCH_UNSCRAMBLED);
lc.setMACPDUOpcode(P2_MAC_HEADER_OPCODE::IDLE);
lc.setMACPartition(P2_MAC_MCO_PARTITION::UNIQUE);
// Encode without sync (I-OEMI)
uint8_t encodedData[P25_P2_FRAME_LENGTH_BYTES];
::memset(encodedData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
lc.encodeVCH_MACPDU(encodedData, false);
Utils::dump(2U, "Round-Trip Test: Encoded I-OEMI", encodedData, P25_P2_FRAME_LENGTH_BYTES);
// Decode
LC decodedLc;
bool ret = decodedLc.decodeVCH_MACPDU_OEMI(encodedData, false);
if (!ret) {
::LogError("T", "Round-trip decode failed");
failed = true;
}
// Re-encode
uint8_t reencodedData[P25_P2_FRAME_LENGTH_BYTES];
::memset(reencodedData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
decodedLc.encodeVCH_MACPDU(reencodedData, false);
Utils::dump(2U, "Round-Trip Test: Re-encoded I-OEMI", reencodedData, P25_P2_FRAME_LENGTH_BYTES);
// Compare original and re-encoded data
for (uint32_t i = 0; i < P25_P2_FRAME_LENGTH_BYTES; i++) {
if (encodedData[i] != reencodedData[i]) {
::LogError("T", "Round-trip data mismatch at byte %u: expected %02X, got %02X", i, encodedData[i], reencodedData[i]);
failed = true;
break;
}
}
REQUIRE(failed == false);
}
TEST_CASE("P25 Phase 2 VCH MAC PDU Round-Trip S-OEMI Test", "[p25][p2_vch_macpdu_roundtrip_soemi]") {
bool failed = false;
INFO("P25 Phase 2 VCH MAC PDU S-OEMI Round-Trip Test");
// Create LC instance with various configurations
LC lc;
lc.setMFId(MFG_STANDARD);
lc.setLCO(P2_MAC_MCO::TEL_INT_VCH_USER); // Phase 2 MAC MCO
lc.setSrcId(11111);
lc.setDstId(22222);
lc.setEmergency(true);
lc.setEncrypted(false);
lc.setPriority(6U);
lc.setGroup(false);
lc.setP2DUID(P2_DUID::SACCH_UNSCRAMBLED);
lc.setMACPDUOpcode(P2_MAC_HEADER_OPCODE::IDLE);
lc.setMACPartition(P2_MAC_MCO_PARTITION::UNIQUE);
// Encode with sync (S-OEMI)
uint8_t encodedData[P25_P2_FRAME_LENGTH_BYTES];
::memset(encodedData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
lc.encodeVCH_MACPDU(encodedData, true);
Utils::dump(2U, "Round-Trip Test: Encoded S-OEMI", encodedData, P25_P2_FRAME_LENGTH_BYTES);
// Decode
LC decodedLc;
bool ret = decodedLc.decodeVCH_MACPDU_OEMI(encodedData, true);
if (!ret) {
::LogError("T", "Round-trip decode failed");
failed = true;
}
// Re-encode
uint8_t reencodedData[P25_P2_FRAME_LENGTH_BYTES];
::memset(reencodedData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
decodedLc.encodeVCH_MACPDU(reencodedData, true);
Utils::dump(2U, "Round-Trip Test: Re-encoded S-OEMI", reencodedData, P25_P2_FRAME_LENGTH_BYTES);
// Compare original and re-encoded data
for (uint32_t i = 0; i < P25_P2_FRAME_LENGTH_BYTES; i++) {
if (encodedData[i] != reencodedData[i]) {
::LogError("T", "Round-trip data mismatch at byte %u: expected %02X, got %02X", i, encodedData[i], reencodedData[i]);
failed = true;
break;
}
}
REQUIRE(failed == false);
}
TEST_CASE("P25 Phase 2 VCH MAC PDU Voice PDU Bypass Test", "[p25][p2_vch_macpdu_voice_bypass]") {
bool failed = false;
INFO("P25 Phase 2 VCH MAC PDU Voice PDU Bypass Test");
// Test that 4V and 2V voice PDUs are properly bypassed
uint8_t testData[P25_P2_FRAME_LENGTH_BYTES];
::memset(testData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
// Test VTCH_4V bypass
LC lc4v;
lc4v.setP2DUID(P2_DUID::VTCH_4V);
lc4v.encodeVCH_MACPDU(testData, false);
LC decoded4v;
bool ret4v = decoded4v.decodeVCH_MACPDU_OEMI(testData, false);
if (!ret4v) {
::LogError("T", "Failed to handle VTCH_4V bypass");
failed = true;
}
// Test VTCH_2V bypass
::memset(testData, 0x00U, P25_P2_FRAME_LENGTH_BYTES);
LC lc2v;
lc2v.setP2DUID(P2_DUID::VTCH_2V);
lc2v.encodeVCH_MACPDU(testData, false);
LC decoded2v;
bool ret2v = decoded2v.decodeVCH_MACPDU_OEMI(testData, false);
if (!ret2v) {
::LogError("T", "Failed to handle VTCH_2V bypass");
failed = true;
}
REQUIRE(failed == false);
}
Loading…
Cancel
Save

Powered by TurnKey Linux.