pub fn encode_block(input: &[u8; 15]) -> [u8; 16] {
let mut p1: u8 = 0;
let mut p2: u8 = 0;
let mut output = [0; 16];
let mut power2: usize = 4;
let mut data: u8 = 0;
let mut insize = input.len();
insize *= 8;
let mut input_idx = 0;
let mut output_idx = 0;
let mut i = 3;
let mut j = 0;
loop {
if j >= insize {
break;
}
if i == power2 {
power2 *= 2;
i += 1;
continue;
}
if j % 8 == 0 {
data = input[input_idx];
output[output_idx] = data;
p2 ^= byte_parity(&data);
input_idx += 1;
output_idx += 1;
}
if data & 1 > 0 {
p1 = p1 ^ (i as u8);
}
data >>= 1;
i += 1;
j += 1;
}
p2 ^= byte_parity(&p1);
output[output_idx] = p1 + (p2 << 7);
output
}
pub fn decode_block(input: &[u8; 16]) -> Result<[u8; 15], FastHammingError> {
let mut p1 = 0;
let mut p2 = 0;
let mut power2: usize = 4;
let mut data = 0;
let check = input[input.len() - 1];
let mut output = [0; 15];
for i in 0..input.len() - 1 {
output[i] = input[i].clone();
}
let insize = 15 * 8;
let mut input_idx = 0;
let mut i: usize = 3;
let mut j: usize = 0;
loop {
if j >= insize {
break;
}
if i == power2 {
power2 *= 2;
i += 1;
continue;
}
if j % 8 == 0 {
data = input[input_idx];
input_idx += 1;
p2 ^= byte_parity(&data);
}
if (data & 1) > 0 {
p1 = p1 ^ (i as u8);
}
data >>= 1;
i += 1;
j += 1;
}
p1 ^= check & 0x7f;
if p1 > 0 {
let mut pm = check;
p2 ^= byte_parity(&pm);
if p2 == 0 {
return Err(FastHammingError::TwoOrMoreErrors);
}
if (p1 & (p1 - 1)) != 0 {
p2 = p1 - log2uint8(&p1) - 2;
pm = p2 / 8;
p2 = p2 % 8;
output[pm as usize] ^= 1 << p2;
}
}
Ok(output)
}
#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum FastHammingError {
TwoOrMoreErrors,
}
#[inline]
fn log2uint8(v: &u8) -> u8 {
return 7 - v.leading_zeros() as u8;
}
#[inline]
fn byte_parity(input: &u8) -> u8 {
let mut p = input.clone() as u32;
p ^= p >> 4;
p ^= p >> 2;
p ^= p >> 1;
p = p & 1;
return p as u8;
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{random, thread_rng, Rng};
const DATA: [u8; 15] = [
0x0b, 0x28, 0x48, 0x69, 0x5e, 0xc8, 0xeb, 0x87, 0x7f, 0x28, 0xdf, 0x52, 0x03, 0xb4, 0x46,
];
const DATA_ENC: [u8; 16] = [
0x0b, 0x28, 0x48, 0x69, 0x5e, 0xc8, 0xeb, 0x87, 0x7f, 0x28, 0xdf, 0x52, 0x03, 0xb4, 0x46,
0xd9,
];
fn flip_random_bit(array: &mut [u8]) -> usize {
let flip_idx: usize = thread_rng().gen_range(0..(array.len() * 8));
let bit_idx = flip_idx % 8;
let byte_idx = flip_idx >> 3;
array[byte_idx] ^= 1 << bit_idx;
return flip_idx;
}
#[test]
fn test_encode_block() {
let input = DATA;
assert_eq!(encode_block(&input), DATA_ENC);
}
#[test]
fn test_decode_block() {
let coded = DATA_ENC;
assert_eq!(decode_block(&coded).unwrap(), DATA);
}
#[test]
fn test_encode_decode() {
let original_data: [u8; 15] = random();
let mut encoded_data: [u8; 16];
let decoded_data: [u8; 15];
encoded_data = encode_block(&original_data);
let flipped_bit_idx = flip_random_bit(&mut encoded_data);
decoded_data = decode_block(&encoded_data).unwrap();
assert_eq!(
original_data,
decoded_data,
"Expected {:?} but got {:?}. Flipped bit {:?} of byte {:?}",
original_data,
decoded_data,
flipped_bit_idx,
flipped_bit_idx / 8
);
}
#[test]
fn stress_test_encode_decode() {
let n_runs = 1000000;
for _ in 0..n_runs {
test_encode_decode();
}
}
#[test]
fn stress_test_two_bit_error() {
let n_runs = 1000000;
for _ in 0..n_runs {
test_two_bit_error();
}
}
#[test]
fn test_two_bit_error() {
let original_data: [u8; 15] = random();
let mut encoded_data: [u8; 16];
encoded_data = encode_block(&original_data);
let bit_idx1 = flip_random_bit(&mut encoded_data);
let bit_idx2 = flip_random_bit(&mut encoded_data);
let decoded_data = decode_block(&encoded_data);
if !(bit_idx1 == bit_idx2) {
assert_eq!(decoded_data, Err(FastHammingError::TwoOrMoreErrors));
} else {
assert_eq!(decoded_data, Ok(original_data));
}
}
#[test]
fn test_log2() {
assert_eq!(log2uint8(&1), 0);
assert_eq!(log2uint8(&2), 1);
assert_eq!(log2uint8(&3), 1);
assert_eq!(log2uint8(&4), 2);
assert_eq!(log2uint8(&7), 2);
assert_eq!(log2uint8(&8), 3);
assert_eq!(log2uint8(&16), 4);
assert_eq!(log2uint8(&32), 5);
assert_eq!(log2uint8(&64), 6);
assert_eq!(log2uint8(&128), 7);
assert_eq!(log2uint8(&255), 7);
}
#[test]
fn test_parity() {
for i in 0..=255 {
assert_eq!(byte_parity(&i), i.count_ones() as u8 % 2);
}
}
}