// 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 "common/Log.h" #include "common/Utils.h" #include #include #include "common/edac/Hamming.h" using namespace edac; // --------------------------------------------------------------------------- // Hamming (15,11,3) Type 1 Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming15113_1 encode preserves zero data", "[edac][hamming]") { bool data[15] = {false}; Hamming::encode15113_1(data); // All parity bits should be zero for all-zero data REQUIRE(data[11] == false); REQUIRE(data[12] == false); REQUIRE(data[13] == false); REQUIRE(data[14] == false); } TEST_CASE("Hamming15113_1 encode/decode round trip", "[edac][hamming]") { bool original[15] = {true, false, true, false, true, false, true, false, true, false, true, false, false, false, false}; bool data[15]; ::memcpy(data, original, sizeof(data)); // Encode Hamming::encode15113_1(data); // Should not have errors bool hasErrors = Hamming::decode15113_1(data); REQUIRE_FALSE(hasErrors); // Data bits should match original for (int i = 0; i < 11; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming15113_1 corrects single data bit errors", "[edac][hamming]") { bool original[15] = {true, true, false, false, true, true, false, false, true, true, false, false, false, false, false}; bool data[15]; // Test single bit errors in all 11 data bit positions for (int bit = 0; bit < 11; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode15113_1(data); // Introduce error data[bit] = !data[bit]; // Decode should correct it bool hasErrors = Hamming::decode15113_1(data); REQUIRE(hasErrors); // Data should match original for (int i = 0; i < 11; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming15113_1 corrects single parity bit errors", "[edac][hamming]") { bool original[15] = {false, true, false, true, false, true, false, true, false, true, false, false, false, false, false}; bool data[15]; // Test single bit errors in all 4 parity bit positions for (int bit = 11; bit < 15; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode15113_1(data); // Save correct parity bool savedParity[4]; for (int i = 0; i < 4; i++) { savedParity[i] = data[11 + i]; } // Introduce error in parity bit data[bit] = !data[bit]; // Decode should correct it bool hasErrors = Hamming::decode15113_1(data); REQUIRE(hasErrors); // Parity should be corrected for (int i = 0; i < 4; i++) { REQUIRE(data[11 + i] == savedParity[i]); } } } TEST_CASE("Hamming15113_1 detects no errors in valid codeword", "[edac][hamming]") { bool data[15] = {true, false, true, true, false, true, false, true, true, false, true, false, false, false, false}; Hamming::encode15113_1(data); bool hasErrors = Hamming::decode15113_1(data); REQUIRE_FALSE(hasErrors); } // --------------------------------------------------------------------------- // Hamming (15,11,3) Type 2 Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming15113_2 encode preserves zero data", "[edac][hamming]") { bool data[15] = {false}; Hamming::encode15113_2(data); // All parity bits should be zero for all-zero data REQUIRE(data[11] == false); REQUIRE(data[12] == false); REQUIRE(data[13] == false); REQUIRE(data[14] == false); } TEST_CASE("Hamming15113_2 encode/decode round trip", "[edac][hamming]") { bool original[15] = {false, true, true, false, true, false, false, true, true, false, true, false, false, false, false}; bool data[15]; ::memcpy(data, original, sizeof(data)); Hamming::encode15113_2(data); bool hasErrors = Hamming::decode15113_2(data); REQUIRE_FALSE(hasErrors); for (int i = 0; i < 11; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming15113_2 corrects single data bit errors", "[edac][hamming]") { bool original[15] = {true, false, true, false, true, true, true, false, false, true, true, false, false, false, false}; bool data[15]; for (int bit = 0; bit < 11; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode15113_2(data); data[bit] = !data[bit]; bool hasErrors = Hamming::decode15113_2(data); REQUIRE(hasErrors); for (int i = 0; i < 11; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming15113_2 corrects single parity bit errors", "[edac][hamming]") { bool original[15] = {true, true, false, false, true, false, true, true, false, false, true, false, false, false, false}; bool data[15]; for (int bit = 11; bit < 15; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode15113_2(data); bool savedParity[4]; for (int i = 0; i < 4; i++) { savedParity[i] = data[11 + i]; } data[bit] = !data[bit]; bool hasErrors = Hamming::decode15113_2(data); REQUIRE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[11 + i] == savedParity[i]); } } } // --------------------------------------------------------------------------- // Hamming (13,9,3) Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming1393 encode preserves zero data", "[edac][hamming]") { bool data[13] = {false}; Hamming::encode1393(data); // All parity bits should be zero for all-zero data REQUIRE(data[9] == false); REQUIRE(data[10] == false); REQUIRE(data[11] == false); REQUIRE(data[12] == false); } TEST_CASE("Hamming1393 encode/decode round trip", "[edac][hamming]") { bool original[13] = {true, false, true, false, true, false, true, false, true, false, false, false, false}; bool data[13]; ::memcpy(data, original, sizeof(data)); Hamming::encode1393(data); bool hasErrors = Hamming::decode1393(data); REQUIRE_FALSE(hasErrors); for (int i = 0; i < 9; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming1393 corrects single data bit errors", "[edac][hamming]") { bool original[13] = {false, true, false, true, false, true, false, true, false, false, false, false, false}; bool data[13]; for (int bit = 0; bit < 9; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode1393(data); data[bit] = !data[bit]; bool hasErrors = Hamming::decode1393(data); REQUIRE(hasErrors); for (int i = 0; i < 9; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming1393 corrects single parity bit errors", "[edac][hamming]") { bool original[13] = {true, true, true, false, false, false, true, true, true, false, false, false, false}; bool data[13]; for (int bit = 9; bit < 13; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode1393(data); bool savedParity[4]; for (int i = 0; i < 4; i++) { savedParity[i] = data[9 + i]; } data[bit] = !data[bit]; bool hasErrors = Hamming::decode1393(data); REQUIRE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[9 + i] == savedParity[i]); } } } // --------------------------------------------------------------------------- // Hamming (10,6,3) Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming1063 encode preserves zero data", "[edac][hamming]") { bool data[10] = {false}; Hamming::encode1063(data); // All parity bits should be zero for all-zero data REQUIRE(data[6] == false); REQUIRE(data[7] == false); REQUIRE(data[8] == false); REQUIRE(data[9] == false); } TEST_CASE("Hamming1063 encode/decode round trip", "[edac][hamming]") { bool original[10] = {true, false, true, false, true, false, false, false, false, false}; bool data[10]; ::memcpy(data, original, sizeof(data)); Hamming::encode1063(data); bool hasErrors = Hamming::decode1063(data); REQUIRE_FALSE(hasErrors); for (int i = 0; i < 6; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming1063 corrects single data bit errors", "[edac][hamming]") { bool original[10] = {false, true, true, false, true, true, false, false, false, false}; bool data[10]; for (int bit = 0; bit < 6; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode1063(data); data[bit] = !data[bit]; bool hasErrors = Hamming::decode1063(data); REQUIRE(hasErrors); for (int i = 0; i < 6; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming1063 corrects single parity bit errors", "[edac][hamming]") { bool original[10] = {true, false, false, true, true, false, false, false, false, false}; bool data[10]; for (int bit = 6; bit < 10; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode1063(data); bool savedParity[4]; for (int i = 0; i < 4; i++) { savedParity[i] = data[6 + i]; } data[bit] = !data[bit]; bool hasErrors = Hamming::decode1063(data); REQUIRE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[6 + i] == savedParity[i]); } } } // --------------------------------------------------------------------------- // Hamming (16,11,4) Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming16114 encode preserves zero data", "[edac][hamming]") { bool data[16] = {false}; Hamming::encode16114(data); // All parity bits should be zero for all-zero data for (int i = 11; i < 16; i++) { REQUIRE(data[i] == false); } } TEST_CASE("Hamming16114 encode/decode round trip", "[edac][hamming]") { bool original[16] = {true, false, true, false, true, false, true, false, true, false, true, false, false, false, false, false}; bool data[16]; ::memcpy(data, original, sizeof(data)); Hamming::encode16114(data); bool hasErrors = Hamming::decode16114(data); REQUIRE(hasErrors); // Returns true even when no errors (see code) for (int i = 0; i < 11; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming16114 corrects single data bit errors", "[edac][hamming]") { bool original[16] = {false, true, true, false, true, false, false, true, true, false, true, false, false, false, false, false}; bool data[16]; for (int bit = 0; bit < 11; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode16114(data); data[bit] = !data[bit]; bool hasErrors = Hamming::decode16114(data); REQUIRE(hasErrors); for (int i = 0; i < 11; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming16114 corrects single parity bit errors", "[edac][hamming]") { bool original[16] = {true, true, false, false, true, true, false, false, true, true, false, false, false, false, false, false}; bool data[16]; for (int bit = 11; bit < 16; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode16114(data); bool savedParity[5]; for (int i = 0; i < 5; i++) { savedParity[i] = data[11 + i]; } data[bit] = !data[bit]; bool hasErrors = Hamming::decode16114(data); REQUIRE(hasErrors); for (int i = 0; i < 5; i++) { REQUIRE(data[11 + i] == savedParity[i]); } } } TEST_CASE("Hamming16114 detects double-bit errors", "[edac][hamming]") { bool original[16] = {true, false, true, false, true, false, true, false, true, false, true, false, false, false, false, false}; bool data[16]; ::memcpy(data, original, sizeof(data)); Hamming::encode16114(data); // Introduce two bit errors data[0] = !data[0]; data[5] = !data[5]; // Should detect but may not correct properly bool result = Hamming::decode16114(data); // With (16,11,4), we can detect 2-bit errors but correction may fail // Result will be false for unrecoverable errors if (!result) { REQUIRE_FALSE(result); // Properly detected as uncorrectable } } // --------------------------------------------------------------------------- // Hamming (17,12,3) Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming17123 encode preserves zero data", "[edac][hamming]") { bool data[17] = {false}; Hamming::encode17123(data); // All parity bits should be zero for all-zero data for (int i = 12; i < 17; i++) { REQUIRE(data[i] == false); } } TEST_CASE("Hamming17123 encode/decode round trip", "[edac][hamming]") { bool original[17] = {true, false, true, false, true, false, true, false, true, false, true, false, false, false, false, false, false}; bool data[17]; ::memcpy(data, original, sizeof(data)); Hamming::encode17123(data); bool hasErrors = Hamming::decode17123(data); REQUIRE(hasErrors); // Returns true even when no errors (see code) for (int i = 0; i < 12; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming17123 corrects single data bit errors", "[edac][hamming]") { bool original[17] = {false, true, true, false, true, false, false, true, true, false, true, false, false, false, false, false, false}; bool data[17]; for (int bit = 0; bit < 12; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode17123(data); data[bit] = !data[bit]; bool hasErrors = Hamming::decode17123(data); REQUIRE(hasErrors); for (int i = 0; i < 12; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming17123 corrects single parity bit errors", "[edac][hamming]") { bool original[17] = {true, true, false, false, true, true, false, false, true, true, false, false, false, false, false, false, false}; bool data[17]; for (int bit = 12; bit < 17; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode17123(data); bool savedParity[5]; for (int i = 0; i < 5; i++) { savedParity[i] = data[12 + i]; } data[bit] = !data[bit]; bool hasErrors = Hamming::decode17123(data); REQUIRE(hasErrors); for (int i = 0; i < 5; i++) { REQUIRE(data[12 + i] == savedParity[i]); } } } TEST_CASE("Hamming17123 detects uncorrectable errors", "[edac][hamming]") { bool original[17] = {true, false, true, false, true, false, true, false, true, false, true, false, false, false, false, false, false}; bool data[17]; ::memcpy(data, original, sizeof(data)); Hamming::encode17123(data); // Introduce multiple bit errors beyond correction capability data[0] = !data[0]; data[3] = !data[3]; data[7] = !data[7]; bool result = Hamming::decode17123(data); // Should return false for unrecoverable errors if (!result) { REQUIRE_FALSE(result); } } // --------------------------------------------------------------------------- // Hamming (8,4,4) Tests // --------------------------------------------------------------------------- TEST_CASE("Hamming844 encode preserves zero data", "[edac][hamming]") { bool data[8] = {false}; Hamming::encode844(data); // All parity bits should be zero for all-zero data for (int i = 4; i < 8; i++) { REQUIRE(data[i] == false); } } TEST_CASE("Hamming844 encode/decode round trip", "[edac][hamming]") { bool original[8] = {true, false, true, false, false, false, false, false}; bool data[8]; ::memcpy(data, original, sizeof(data)); Hamming::encode844(data); bool hasErrors = Hamming::decode844(data); REQUIRE_FALSE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming844 corrects single data bit errors", "[edac][hamming]") { bool original[8] = {false, true, true, false, false, false, false, false}; bool data[8]; for (int bit = 0; bit < 4; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode844(data); data[bit] = !data[bit]; bool hasErrors = Hamming::decode844(data); REQUIRE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[i] == original[i]); } } } TEST_CASE("Hamming844 corrects single parity bit errors", "[edac][hamming]") { bool original[8] = {true, true, false, false, false, false, false, false}; bool data[8]; for (int bit = 4; bit < 8; bit++) { ::memcpy(data, original, sizeof(data)); Hamming::encode844(data); bool savedParity[4]; for (int i = 0; i < 4; i++) { savedParity[i] = data[4 + i]; } data[bit] = !data[bit]; bool hasErrors = Hamming::decode844(data); REQUIRE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[4 + i] == savedParity[i]); } } } TEST_CASE("Hamming844 detects double-bit errors", "[edac][hamming]") { bool original[8] = {true, false, true, false, false, false, false, false}; bool data[8]; ::memcpy(data, original, sizeof(data)); Hamming::encode844(data); // Introduce two bit errors data[0] = !data[0]; data[2] = !data[2]; // Hamming (8,4,4) can detect double-bit errors but not correct them bool result = Hamming::decode844(data); // Should return false for uncorrectable double-bit error REQUIRE_FALSE(result); } TEST_CASE("Hamming844 handles all-ones data", "[edac][hamming]") { bool original[8] = {true, true, true, true, false, false, false, false}; bool data[8]; ::memcpy(data, original, sizeof(data)); Hamming::encode844(data); bool hasErrors = Hamming::decode844(data); REQUIRE_FALSE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[i] == original[i]); } } TEST_CASE("Hamming844 various data patterns", "[edac][hamming]") { const bool patterns[][4] = { {false, false, false, false}, {true, true, true, true}, {true, false, true, false}, {false, true, false, true}, {true, true, false, false}, {false, false, true, true}, {true, false, false, true}, {false, true, true, false} }; for (auto& pattern : patterns) { bool data[8]; for (int i = 0; i < 4; i++) { data[i] = pattern[i]; } Hamming::encode844(data); bool hasErrors = Hamming::decode844(data); REQUIRE_FALSE(hasErrors); for (int i = 0; i < 4; i++) { REQUIRE(data[i] == pattern[i]); } } }