use crate::fec::{
golay_23_12_decode, golay_23_12_decode_soft, golay_23_12_encode, hamming_15_11_decode,
hamming_15_11_decode_soft, hamming_15_11_encode,
};
use super::fec::{
deinterleave, interleave, modulation_masks, soft_deinterleave, soft_demodulate_vector,
SOFT_BITS,
};
pub const INFO_WIDTHS: [u8; 8] = [12, 12, 12, 12, 11, 11, 11, 7];
pub const CODE_WIDTHS: [u8; 8] = [23, 23, 23, 23, 15, 15, 15, 7];
pub const INFO_BITS_TOTAL: u16 = 88;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Frame {
pub info: [u16; 8],
pub errors: [u8; 8],
}
impl Frame {
pub fn error_total(&self) -> u16 {
self.errors.iter().map(|&e| e as u16).sum()
}
}
pub fn decode_frame(dibits: &[u8; 72]) -> Frame {
let c = deinterleave(dibits);
let d0 = golay_23_12_decode(c[0]);
let u0 = d0.info;
let masks = modulation_masks(u0);
let d1 = golay_23_12_decode(c[1] ^ masks[1]);
let d2 = golay_23_12_decode(c[2] ^ masks[2]);
let d3 = golay_23_12_decode(c[3] ^ masks[3]);
let d4 = hamming_15_11_decode((c[4] ^ masks[4]) as u16);
let d5 = hamming_15_11_decode((c[5] ^ masks[5]) as u16);
let d6 = hamming_15_11_decode((c[6] ^ masks[6]) as u16);
let u7 = (c[7] & 0x7F) as u16;
Frame {
info: [d0.info, d1.info, d2.info, d3.info, d4.info, d5.info, d6.info, u7],
errors: [d0.errors, d1.errors, d2.errors, d3.errors, d4.errors, d5.errors, d6.errors, 0],
}
}
pub fn decode_frame_soft(soft: &[i8; SOFT_BITS]) -> Frame {
let mut c = soft_deinterleave(soft);
let d0 = golay_23_12_decode_soft(&c.golay[0]);
let u0 = d0.info;
let masks = modulation_masks(u0);
soft_demodulate_vector(&mut c.golay[1], masks[1]);
soft_demodulate_vector(&mut c.golay[2], masks[2]);
soft_demodulate_vector(&mut c.golay[3], masks[3]);
soft_demodulate_vector(&mut c.hamming[0], masks[4]);
soft_demodulate_vector(&mut c.hamming[1], masks[5]);
soft_demodulate_vector(&mut c.hamming[2], masks[6]);
let d1 = golay_23_12_decode_soft(&c.golay[1]);
let d2 = golay_23_12_decode_soft(&c.golay[2]);
let d3 = golay_23_12_decode_soft(&c.golay[3]);
let d4 = hamming_15_11_decode_soft(&c.hamming[0]);
let d5 = hamming_15_11_decode_soft(&c.hamming[1]);
let d6 = hamming_15_11_decode_soft(&c.hamming[2]);
let mut u7 = 0u16;
for (i, &s) in c.uncoded.iter().enumerate() {
if s > 0 {
u7 |= 1u16 << (7 - 1 - i);
}
}
Frame {
info: [d0.info, d1.info, d2.info, d3.info, d4.info, d5.info, d6.info, u7],
errors: [d0.errors, d1.errors, d2.errors, d3.errors, d4.errors, d5.errors, d6.errors, 0],
}
}
pub fn encode_frame(info: &[u16; 8]) -> [u8; 72] {
let u: [u16; 8] = core::array::from_fn(|i| {
let w = INFO_WIDTHS[i] as u32;
let m = if w == 16 { u16::MAX } else { ((1u32 << w) - 1) as u16 };
info[i] & m
});
let v0 = golay_23_12_encode(u[0]);
let v1 = golay_23_12_encode(u[1]);
let v2 = golay_23_12_encode(u[2]);
let v3 = golay_23_12_encode(u[3]);
let v4 = u32::from(hamming_15_11_encode(u[4]));
let v5 = u32::from(hamming_15_11_encode(u[5]));
let v6 = u32::from(hamming_15_11_encode(u[6]));
let v7 = u32::from(u[7]);
let masks = modulation_masks(u[0]);
let c = [
v0, v1 ^ masks[1],
v2 ^ masks[2],
v3 ^ masks[3],
v4 ^ masks[4],
v5 ^ masks[5],
v6 ^ masks[6],
v7, ];
interleave(&c)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_info(seed: u32) -> [u16; 8] {
let mut state = seed;
let mut out = [0u16; 8];
for i in 0..8 {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
let w = INFO_WIDTHS[i] as u32;
let m = if w == 16 { u16::MAX } else { ((1u32 << w) - 1) as u16 };
out[i] = (state as u16) & m;
}
out
}
#[test]
fn encode_decode_roundtrip_zero() {
let u = [0u16; 8];
let f = encode_frame(&u);
for (s, d) in f.iter().enumerate() {
assert!(*d < 4, "symbol {s} is not a dibit");
}
let back = decode_frame(&f);
assert_eq!(back.info, u);
assert_eq!(back.errors, [0u8; 8]);
}
#[test]
fn encode_decode_roundtrip_sampled() {
for seed in [1u32, 0xDEADBEEF, 0xCAFEBABE, 0x12345678, 42] {
let u = sample_info(seed);
let f = encode_frame(&u);
let back = decode_frame(&f);
assert_eq!(back.info, u, "seed 0x{seed:08x}");
assert_eq!(back.errors, [0u8; 8], "seed 0x{seed:08x}");
}
}
#[test]
fn high_bits_are_masked_off() {
let mut u = [0u16; 8];
for i in 0..8 {
u[i] = 0xFFFF; }
let f = encode_frame(&u);
let back = decode_frame(&f);
for i in 0..8 {
let w = INFO_WIDTHS[i] as u32;
let m = ((1u32 << w) - 1) as u16;
assert_eq!(back.info[i], m, "vector {i} width mask failed");
}
}
#[test]
fn single_bit_flip_in_golay_vector_is_corrected() {
let u = sample_info(0xA5A5A5A5);
let clean = encode_frame(&u);
for bit in 0..23 {
let c = deinterleave(&clean);
let mut c_flipped = c;
c_flipped[0] ^= 1u32 << bit;
let dibits = interleave(&c_flipped);
let out = decode_frame(&dibits);
assert_eq!(out.info, u, "bit {bit} flip in c̃₀");
assert_eq!(out.errors[0], 1, "bit {bit} flip in c̃₀");
}
}
#[test]
fn single_bit_flip_in_hamming_vector_is_corrected() {
let u = sample_info(0x5A5A5A5A);
let clean = encode_frame(&u);
for bit in 0..15 {
let c = deinterleave(&clean);
let mut c_flipped = c;
c_flipped[4] ^= 1u32 << bit;
let dibits = interleave(&c_flipped);
let out = decode_frame(&dibits);
assert_eq!(out.info, u, "bit {bit} flip in c̃₄");
assert_eq!(out.errors[4], 1, "bit {bit} flip in c̃₄");
}
}
#[test]
fn uncoded_vector_has_no_error_correction() {
let u = sample_info(0xFACEFEED);
let clean = encode_frame(&u);
let c = deinterleave(&clean);
let mut c_flipped = c;
c_flipped[7] ^= 1u32 << 3;
let dibits = interleave(&c_flipped);
let out = decode_frame(&dibits);
assert_eq!(out.info[7], u[7] ^ 0b1000);
assert_eq!(out.errors[7], 0);
}
#[test]
fn triple_error_in_golay_vector_is_corrected() {
let u = sample_info(0x01020304);
let clean = encode_frame(&u);
let c = deinterleave(&clean);
let mut c_flipped = c;
c_flipped[1] ^= (1u32 << 0) | (1u32 << 7) | (1u32 << 19);
let dibits = interleave(&c_flipped);
let out = decode_frame(&dibits);
assert_eq!(out.info, u);
assert_eq!(out.errors[1], 3);
assert_eq!(out.error_total(), 3);
}
#[test]
fn widths_sum_to_88_and_144() {
assert_eq!(INFO_WIDTHS.iter().map(|&w| w as u16).sum::<u16>(), 88);
assert_eq!(CODE_WIDTHS.iter().map(|&w| w as u16).sum::<u16>(), 144);
assert_eq!(INFO_BITS_TOTAL, 88);
}
fn dibits_to_soft(dibits: &[u8; 72], confidence: i8) -> [i8; 144] {
let mut out = [0i8; 144];
for (sym, &d) in dibits.iter().enumerate() {
let hi = (d >> 1) & 1;
let lo = d & 1;
out[2 * sym] = if hi == 1 { confidence } else { -confidence };
out[2 * sym + 1] = if lo == 1 { confidence } else { -confidence };
}
out
}
#[test]
fn soft_decode_matches_hard_on_clean_input() {
for seed in [0u32, 1, 0xDEADBEEF, 0xCAFEBABE, 0x12345678] {
let u = if seed == 0 { [0u16; 8] } else { sample_info(seed) };
let dibits = encode_frame(&u);
let soft = dibits_to_soft(&dibits, 120);
let soft_frame = decode_frame_soft(&soft);
let hard_frame = decode_frame(&dibits);
assert_eq!(soft_frame.info, hard_frame.info, "seed 0x{seed:08x}");
assert_eq!(soft_frame.info, u, "seed 0x{seed:08x}");
assert_eq!(soft_frame.errors, [0u8; 8]);
}
}
#[test]
fn soft_decode_recovers_weak_bit_errors_on_golay_vector() {
let u = sample_info(0x01020304);
let dibits = encode_frame(&u);
let mut soft = dibits_to_soft(&dibits, 120);
use super::super::fec::ANNEX_H;
let targets = [(1u8, 0u8), (1, 4), (1, 10), (1, 18)]; for (vi, msb_pos) in targets {
let lsb_pos = CODE_WIDTHS[vi as usize] - 1 - msb_pos;
let (sym, is_hi) = (0..ANNEX_H.len())
.find_map(|s| {
let e = ANNEX_H[s];
if e.bit1_vec == vi && e.bit1_idx == lsb_pos {
Some((s, true))
} else if e.bit0_vec == vi && e.bit0_idx == lsb_pos {
Some((s, false))
} else {
None
}
})
.expect("annex H covers every (vec, idx)");
let soft_idx = 2 * sym + if is_hi { 0 } else { 1 };
soft[soft_idx] = -soft[soft_idx].signum() * 4;
}
let frame = decode_frame_soft(&soft);
assert_eq!(frame.info, u, "soft recovered 4-error c̃₁");
}
}