secded 1.1.0

Single Error Correction, Double Error Detection Codes for Everyone
Documentation
/// input data
/// output parity data
pub fn encode(data: [u8; 8]) -> u8 {
    let data = u64::from_le_bytes(data);
    let mut ret = 0u8;
    for (i, mask) in generated::PARITY_BITS_MASK.iter().enumerate() {
        let parity_bit = ((data & mask).count_ones() & 1) as u8;
        ret |= parity_bit << i;
    }
    ret |= ((data.count_ones() & 1) as u8) << 7;
    ret
}

/// correct input data if there is one bit error,
/// return Err(()) if double bit error was detected
pub fn decode(src_data: &mut [u8; 8], exp_parity: u8) -> Result<(), ()> {
    let mut data = u64::from_le_bytes(*src_data);

    let mut index_of_error = 0;
    debug_assert_eq!(7, generated::PARITY_BITS_MASK.len());
    for (i, mask) in generated::PARITY_BITS_MASK.iter().enumerate() {
        let parity = ((data & mask).count_ones() & 1) as u8;
        let expected = (exp_parity >> i) & 1;
        if parity != expected {
            index_of_error |= 1u8 << i;
        }
    }

    let overall_parity = (data.count_ones() & 1) as u8;
    let overall_expected = (exp_parity >> 7) & 1;
    let overall_correct = overall_expected == overall_parity;

    match (index_of_error, overall_correct) {
        (0, true) => Ok(()),
        (0, false) => Err(()),
        (_, true) => Err(()),
        (_, false) => {
            let skip = if !index_of_error.is_power_of_two() {
                8 - (index_of_error.leading_zeros() as u8) + 1
            } else {
                8 - (index_of_error.leading_zeros() as u8)
            };
            index_of_error -= skip;
            data ^= 1u64 << index_of_error;
            *src_data = data.to_le_bytes();
            Ok(())
        }
    }
}

mod generated {
    /*
        Generated with:
    ```python
        from math import floor, ceil, log2

        def is_power_of_two(n: int) -> bool:
            return log2(n).is_integer()

        def next_power_of_two(n: int) -> int:
            if is_power_of_two(n):
                return n << 1
            else:
                return 2 ** ceil(log2(n))

        def data_bits_covered_by_parity(parity: int, lim: int):
            assert is_power_of_two(parity), "parity bits should be powert of two"

            # use 1-based indexing for simpler computational logic
            data_index  = 1 # bit we're currently at in the DATA bitstring
            total_index = 3 # bit we're currently at in the OVERALL bitstring

            while data_index <= lim:
                curr_bit_is_data = not is_power_of_two(total_index)
                if curr_bit_is_data and (total_index % (parity << 1)) >= parity:
                    yield data_index - 1 # adjust output to be zero indexed
                data_index += curr_bit_is_data
                total_index += 1
            return None


        def parity_bits_needed(data_len):
            n = next_power_of_two(data_len)
            lower_bin = floor(log2(n))
            upper_bin = lower_bin + 1
            data_bit_boundary = n - lower_bin - 1
            return lower_bin if data_len <= data_bit_boundary else upper_bin

        def calc_parity_bit_mask(parity: int, data_len: int):
            mask = 0
            for data_index in data_bits_covered_by_parity(parity, data_len):
                mask = mask | (1 << data_index)
            return mask


        data_len = 64
        n_parity_bits = parity_bits_needed(data_len)
        print("[")
        i = 0
        power = 1
        while i < n_parity_bits:
            print("0x%x," % calc_parity_bit_mask(power, data_len))
            power <<= 1
            i += 1
        print("]")
    ```
        */
    pub const PARITY_BITS_MASK: [u64; 7] = [
        0xab55555556aaad5b,
        0xcd9999999b33366d,
        0xf1e1e1e1e3c3c78e,
        0x1fe01fe03fc07f0,
        0x1fffe0003fff800,
        0x1fffffffc000000,
        0xfe00000000000000,
    ];
}

#[cfg(test)]
mod tests {
    use super::*;
    use quickcheck::{QuickCheck, TestResult};

    #[test]
    fn somke_test() {
        for data in &[[0xff; 8], [0; 8], *b"ABCDEFGH"] {
            assert!(enc_dec(*data, IntroduceError::None,));
            for i in 0..=63 {
                println!("ERROR for {}", i);
                assert!(enc_dec(*data, IntroduceError::Single(i),));
            }

            for i in 0..=63 {
                for j in 0..=63 {
                    if i != j {
                        assert!(enc_dec(*data, IntroduceError::Double(i, j),));
                    }
                }
            }
        }
    }

    #[test]
    fn random_data_with_opt_single_error() {
        fn prop(data: u64, pos: Option<u8>) -> TestResult {
            let err = if let Some(pos) = pos {
                if pos >= 64 {
                    return TestResult::discard();
                }
                IntroduceError::Single(pos)
            } else {
                IntroduceError::None
            };
            TestResult::from_bool(enc_dec(data.to_le_bytes(), err))
        }
        QuickCheck::new()
            .tests(10_000_000_000u64)
            .quickcheck(prop as fn(data: u64, pos: Option<u8>) -> TestResult);
    }

    #[test]
    fn random_data_with_opt_double_error() {
        fn prop(data: u64, pos1: Option<u8>, pos2: Option<u8>) -> TestResult {
            let err = match (pos1, pos2) {
                (Some(pos1), Some(pos2)) => {
                    if pos1 >= 64 || pos2 >= 64 {
                        return TestResult::discard();
                    }
                    if pos1 == pos2 {
                        IntroduceError::Single(pos1)
                    } else {
                        IntroduceError::Double(pos1, pos2)
                    }
                }
                (Some(pos), None) | (None, Some(pos)) => {
                    if pos >= 64 {
                        return TestResult::discard();
                    }
                    IntroduceError::Single(pos)
                }
                (None, None) => IntroduceError::None,
            };
            TestResult::from_bool(enc_dec(data.to_le_bytes(), err))
        }
        QuickCheck::new()
            .tests(10_000_000_000u64)
            .quickcheck(prop as fn(data: u64, pos1: Option<u8>, pos2: Option<u8>) -> TestResult);
    }

    enum IntroduceError {
        None,
        Single(u8),
        Double(u8, u8),
    }

    fn enc_dec(data: [u8; 8], intr_err: IntroduceError) -> bool {
        let parity = encode(data);
        let mut enc_data = data;
        match intr_err {
            IntroduceError::None => {
                let is_err = decode(&mut enc_data, parity).is_err() || data != enc_data;
                if is_err {
                    eprintln!(
                        "Error (without error) enc_data {:x?}, parity {:x}",
                        enc_data, parity
                    );
                    false
                } else {
                    true
                }
            }
            IntroduceError::Single(single_err_pos) => {
                let mut word = u64::from_le_bytes(enc_data);
                word ^= 1u64 << single_err_pos;
                enc_data = word.to_le_bytes();
                let is_err = decode(&mut enc_data, parity).is_err() || data != enc_data;
                if is_err {
                    println!("introduce error: {:x?} => {:x?}", data, enc_data);
                    eprintln!(
                        "Error (with 1 error) enc_data {:x?}, parity {:x}",
                        enc_data, parity
                    );
                    false
                } else {
                    true
                }
            }
            IntroduceError::Double(err_pos1, err_pos2) => {
                let mut word = u64::from_le_bytes(enc_data);
                word ^= 1u64 << err_pos1;
                word ^= 1u64 << err_pos2;
                enc_data = word.to_le_bytes();

                if decode(&mut enc_data, parity).is_ok() {
                    println!("introduce error: {:x?} => {:x?}", data, enc_data);
                    eprintln!(
                        "Error (with 2 errors) enc_data {:x?}, parity {:x}",
                        enc_data, parity
                    );
                    false
                } else {
                    true
                }
            }
        }
    }
}