#![expect(
clippy::indexing_slicing,
reason = "all indexing in this module is statically bounded by the codec / table geometry"
)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SecdedOutcome {
Clean,
Corrected,
Uncorrectable,
}
pub trait SecdedCodec {
const DATA_WORD_BYTES: usize;
const PARITY_BYTES: usize;
fn encode_word(data: &[u8], parity_out: &mut [u8]);
fn decode_word(data: &mut [u8], parity: &[u8]) -> SecdedOutcome;
}
pub struct Hsiao7264;
const DATA_BITS: usize = 64;
const DATA_COLS: [u8; DATA_BITS] = build_data_cols();
const fn build_data_cols() -> [u8; DATA_BITS] {
let mut cols = [0u8; DATA_BITS];
let mut filled = 0usize;
let mut byte: u8 = 0;
loop {
let ones = byte.count_ones();
if ones % 2 == 1 && ones >= 3 {
cols[filled] = byte;
filled += 1;
}
if byte == u8::MAX || filled == DATA_BITS {
break;
}
byte += 1;
}
cols
}
const TABLES: [[u8; 256]; 8] = build_tables();
const fn build_tables() -> [[u8; 256]; 8] {
let mut tables = [[0u8; 256]; 8];
let mut lane = 0usize;
while lane < 8 {
let mut b = 0usize;
while b < 256 {
let mut acc = 0u8;
let mut bit = 0usize;
while bit < 8 {
if (b >> bit) & 1 == 1 {
acc ^= DATA_COLS[lane * 8 + bit];
}
bit += 1;
}
tables[lane][b] = acc;
b += 1;
}
lane += 1;
}
tables
}
#[inline]
fn check_byte(word: u64) -> u8 {
let b = word.to_le_bytes();
TABLES[0][b[0] as usize]
^ TABLES[1][b[1] as usize]
^ TABLES[2][b[2] as usize]
^ TABLES[3][b[3] as usize]
^ TABLES[4][b[4] as usize]
^ TABLES[5][b[5] as usize]
^ TABLES[6][b[6] as usize]
^ TABLES[7][b[7] as usize]
}
impl SecdedCodec for Hsiao7264 {
const DATA_WORD_BYTES: usize = 8;
const PARITY_BYTES: usize = 1;
fn encode_word(data: &[u8], parity_out: &mut [u8]) {
assert!(
data.len() == Self::DATA_WORD_BYTES,
"Hsiao7264 word must be 8 bytes"
);
assert!(
parity_out.len() >= Self::PARITY_BYTES,
"Hsiao7264 needs 1 parity byte",
);
let mut word = [0u8; 8];
word.copy_from_slice(data);
parity_out[0] = check_byte(u64::from_le_bytes(word));
}
fn decode_word(data: &mut [u8], parity: &[u8]) -> SecdedOutcome {
assert!(
data.len() == Self::DATA_WORD_BYTES,
"Hsiao7264 word must be 8 bytes"
);
assert!(
parity.len() >= Self::PARITY_BYTES,
"Hsiao7264 needs 1 parity byte",
);
let mut word_bytes = [0u8; 8];
word_bytes.copy_from_slice(data);
let word = u64::from_le_bytes(word_bytes);
let syndrome = parity[0] ^ check_byte(word);
if syndrome == 0 {
return SecdedOutcome::Clean;
}
if syndrome.is_power_of_two() {
return SecdedOutcome::Corrected;
}
if syndrome.count_ones() % 2 == 1 {
let mut bit = 0usize;
while bit < DATA_BITS {
if DATA_COLS[bit] == syndrome {
let corrected = word ^ (1u64 << bit);
data.copy_from_slice(&corrected.to_le_bytes());
return SecdedOutcome::Corrected;
}
bit += 1;
}
}
SecdedOutcome::Uncorrectable
}
}
#[must_use]
pub fn block_parity_len(payload_len: usize) -> usize {
payload_len.div_ceil(Hsiao7264::DATA_WORD_BYTES) * Hsiao7264::PARITY_BYTES
}
#[must_use]
pub fn encode_block_parity(payload: &[u8]) -> alloc::vec::Vec<u8> {
let mut out = alloc::vec::Vec::with_capacity(block_parity_len(payload.len()));
let mut word = [0u8; Hsiao7264::DATA_WORD_BYTES];
for chunk in payload.chunks(Hsiao7264::DATA_WORD_BYTES) {
word.fill(0);
word[..chunk.len()].copy_from_slice(chunk);
let mut check = [0u8; Hsiao7264::PARITY_BYTES];
Hsiao7264::encode_word(&word, &mut check);
out.extend_from_slice(&check);
}
out
}
#[must_use]
pub fn try_correct_block(payload: &mut [u8], parity: &[u8]) -> SecdedOutcome {
if parity.len() < block_parity_len(payload.len()) {
return SecdedOutcome::Uncorrectable;
}
let mut any_corrected = false;
let mut word = [0u8; Hsiao7264::DATA_WORD_BYTES];
for (i, chunk) in payload.chunks_mut(Hsiao7264::DATA_WORD_BYTES).enumerate() {
let len = chunk.len();
word.fill(0);
word[..len].copy_from_slice(chunk);
let p = &parity[i * Hsiao7264::PARITY_BYTES..(i + 1) * Hsiao7264::PARITY_BYTES];
match Hsiao7264::decode_word(&mut word, p) {
SecdedOutcome::Clean => {}
SecdedOutcome::Corrected => {
chunk.copy_from_slice(&word[..len]);
any_corrected = true;
}
SecdedOutcome::Uncorrectable => return SecdedOutcome::Uncorrectable,
}
}
if any_corrected {
SecdedOutcome::Corrected
} else {
SecdedOutcome::Clean
}
}
#[cfg(test)]
mod tests;