diff --git a/src/common/edac/Hamming.cpp b/src/common/edac/Hamming.cpp index 0b290fe5..6b094f79 100644 --- a/src/common/edac/Hamming.cpp +++ b/src/common/edac/Hamming.cpp @@ -5,6 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX + * Copyright (C) 2026 Bryan Biedenkapp, N2PLL * */ #include "edac/Hamming.h" @@ -360,3 +361,93 @@ void Hamming::encode17123(bool* d) d[15] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10]; d[16] = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11]; } + +/* Decode Hamming (8,4,4). */ + +bool Hamming::decode844(bool* d) +{ + assert(d != nullptr); + + // Hamming(8,4,4) extended code layout: + // d[0..3] = data bits (4 bits) + // d[4..6] = parity bits (3 bits) - Hamming(7,4,3) parity + // d[7] = overall parity bit (1 bit) + // + // Parity check matrix: + // P0 (d[4]) = d[0] ^ d[1] ^ d[3] + // P1 (d[5]) = d[0] ^ d[2] ^ d[3] + // P2 (d[6]) = d[1] ^ d[2] ^ d[3] + // P3 (d[7]) = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6] (overall parity) + + // Calculate syndrome bits for Hamming(7,4,3) portion + bool c0 = d[0] ^ d[1] ^ d[3] ^ d[4]; // Check P0 + bool c1 = d[0] ^ d[2] ^ d[3] ^ d[5]; // Check P1 + bool c2 = d[1] ^ d[2] ^ d[3] ^ d[6]; // Check P2 + + // Calculate overall parity + bool c3 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6] ^ d[7]; + + // Build syndrome + unsigned char syndrome = 0x00U; + syndrome |= c0 ? 0x01U : 0x00U; + syndrome |= c1 ? 0x02U : 0x00U; + syndrome |= c2 ? 0x04U : 0x00U; + + // If overall parity is wrong and syndrome is non-zero, single bit error + // If overall parity is wrong and syndrome is zero, error in parity bit d[7] + // If overall parity is correct and syndrome is non-zero, double bit error (uncorrectable) + // If both are correct, no error + + if (c3) { + // Overall parity error detected + if (syndrome == 0x00U) { + // Error in overall parity bit + d[7] = !d[7]; + return true; + } + else { + // Single bit error - syndrome tells us which bit + switch (syndrome) { + case 0x03U: d[0] = !d[0]; return true; // d0 position + case 0x05U: d[1] = !d[1]; return true; // d1 position + case 0x06U: d[2] = !d[2]; return true; // d2 position + case 0x07U: d[3] = !d[3]; return true; // d3 position + case 0x01U: d[4] = !d[4]; return true; // P0 position + case 0x02U: d[5] = !d[5]; return true; // P1 position + case 0x04U: d[6] = !d[6]; return true; // P2 position + default: return false; // Should not happen + } + } + } + else { + // Overall parity correct + if (syndrome == 0x00U) { + // No errors + return false; + } + else { + // Double bit error detected - uncorrectable + return false; + } + } +} + +/* Encode Hamming (8,4,4). */ + +void Hamming::encode844(bool* d) +{ + assert(d != nullptr); + + // Hamming(8,4,4) extended code + // d[0..3] = data bits (input) + // d[4..6] = parity bits (calculated) + // d[7] = overall parity bit (calculated) + + // Calculate Hamming(7,4,3) parity bits + d[4] = d[0] ^ d[1] ^ d[3]; // P0 + d[5] = d[0] ^ d[2] ^ d[3]; // P1 + d[6] = d[1] ^ d[2] ^ d[3]; // P2 + + // Calculate overall parity bit + d[7] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6]; +} diff --git a/src/common/edac/Hamming.h b/src/common/edac/Hamming.h index 6db47533..f4ba51c0 100644 --- a/src/common/edac/Hamming.h +++ b/src/common/edac/Hamming.h @@ -5,6 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX + * Copyright (C) 2026 Bryan Biedenkapp, N2PLL * */ /** @@ -102,6 +103,18 @@ namespace edac * @param d Boolean bit array. */ static void encode17123(bool* d); + + /** + * @brief Decode Hamming (8,4,4). + * @param d Boolean bit array. + * @returns bool True, if bit errors are detected, otherwise false. + */ + static bool decode844(bool* d); + /** + * @brief Encode Hamming (8,4,4). + * @param d Boolean bit array. + */ + static void encode844(bool* d); }; } // namespace edac diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index b54b1e85..1a536e11 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -66,6 +66,11 @@ namespace p25 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_IEMI_LENGTH_BITS = 312U; + const uint32_t P25_P2_IEMI_LENGTH_BYTES = (P25_P2_IEMI_LENGTH_BITS / 8U) + 1U; + 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_NID_LENGTH_BYTES = 8U; const uint32_t P25_NID_LENGTH_BITS = P25_NID_LENGTH_BYTES * 8U; @@ -852,6 +857,24 @@ namespace p25 }; } + // TIA-102.BBAD-D Section 4.2 + /** @brief Phase 2 MAC 4V/SACCH Offset(s) */ + namespace P2_MAC_HEADER_OFFSET { + /** @brief Phase 2 MAC 4V/SACCH Offset(s) */ + enum : uint8_t { + FIRST_4V_NEXT = 0x00U, //!< First 4V Next non-SACCH Burst on Slot + FIRST_4V_2ND = 0x01U, //!< First 4V Second non-SACCH Burst on Slot + FIRST_4V_3RD = 0x02U, //!< First 4V Third non-SACCH Burst on Slot + FIRST_4V_4TH = 0x03U, //!< First 4V Fourth non-SACCH Burst on Slot + FIRST_4V_5TH = 0x04U, //!< First 4V Fifth non-SACCH Burst on Slot + FIRST_4V_6TH = 0x05U, //!< First 4V Sixth non-SACCH Burst on Slot (Inbound Reserved) + + INBOUND_RANDOM_SACCH = 0x06U, //!< Inbound Random SACCH (Outbound Reserved) + + NO_VOICE_OR_UNK = 0x07U //!< No Voice or Unknown + }; + } + // TIA-102.BBAD-D Section 3 /** @brief Phase 2 MAC MCO Partitioning */ namespace P2_MAC_MCO_PARTITION { diff --git a/src/common/p25/Sync.cpp b/src/common/p25/Sync.cpp index a9b51b8d..657795e1 100644 --- a/src/common/p25/Sync.cpp +++ b/src/common/p25/Sync.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024,2026 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -30,3 +30,17 @@ void Sync::addP25Sync(uint8_t* data) ::memcpy(data, P25_SYNC_BYTES, P25_SYNC_LENGTH_BYTES); } + +/* Helper to append P25 Phase 2 S-OEMI sync bytes to the passed buffer. */ + +void Sync::addP25P2_SOEMISync(uint8_t* data) +{ + assert(data != nullptr); + + for (uint32_t i = 0U; i < P25_P2_OEMI_SYNC_LENGTH_BITS; i++) { + uint32_t n = i + 4U + 134U; // this skips the 4 bits of the DUID and remaining 134 bits of Field 1 and 2 for + // a S-OEMI + bool b = READ_BIT(P25_P2_OEMI_SYNC_BYTES, i); + WRITE_BIT(data, n, b); + } +} diff --git a/src/common/p25/Sync.h b/src/common/p25/Sync.h index 9db35d6f..a51b032a 100644 --- a/src/common/p25/Sync.h +++ b/src/common/p25/Sync.h @@ -5,6 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX + * Copyright (C) 2026 Bryan Biedenkapp, N2PLL * */ /** @@ -35,6 +36,12 @@ namespace p25 * @param data Buffer to append P25 sync bytes to. */ static void addP25Sync(uint8_t* data); + + /** + * @brief Helper to append P25 Phase 2 S-OEMI sync bytes to the passed buffer. + * @param data Buffer to append P25 Phase 2 OEMI sync bytes to. + */ + static void addP25P2_SOEMISync(uint8_t* data); }; } // namespace p25