pub const GEN_POLY: u32 = 0x4_95C9;
pub const PARITY_BITS: u32 = 18;
pub const DATA_BITS: u32 = 493;
pub const FRAME_BITS: u32 = 1 + DATA_BITS + PARITY_BITS;
pub const MULTIFRAME_FRAMES: u32 = 8;
pub const ALIGNMENT_PATTERN: [u8; 8] = [0, 0, 0, 1, 1, 0, 1, 1];
pub fn parity18(data: &[u8]) -> u32 {
debug_assert!(
data.len() * 8 >= DATA_BITS as usize,
"parity18 needs at least {} bits of input ({} bytes)",
DATA_BITS,
DATA_BITS.div_ceil(8)
);
let mut reg: u32 = 0;
for i in 0..(DATA_BITS as usize) {
let bit = (data[i / 8] >> (7 - (i & 7))) & 1;
reg = (reg << 1) | bit as u32;
if (reg >> 18) & 1 != 0 {
reg ^= GEN_POLY;
}
}
for _ in 0..PARITY_BITS {
reg <<= 1;
if (reg >> 18) & 1 != 0 {
reg ^= GEN_POLY;
}
}
reg & ((1 << PARITY_BITS) - 1)
}
pub fn syndrome18(data: &[u8], parity: u32) -> u32 {
let mut reg: u32 = 0;
for i in 0..(DATA_BITS as usize) {
let bit = (data[i / 8] >> (7 - (i & 7))) & 1;
reg = (reg << 1) | bit as u32;
if (reg >> 18) & 1 != 0 {
reg ^= GEN_POLY;
}
}
for i in 0..PARITY_BITS {
let bit = (parity >> (PARITY_BITS - 1 - i)) & 1;
reg = (reg << 1) | bit;
if (reg >> 18) & 1 != 0 {
reg ^= GEN_POLY;
}
}
reg & ((1 << PARITY_BITS) - 1)
}
pub fn locate_single_error(syndrome: u32) -> Option<u32> {
let mask = (1u32 << PARITY_BITS) - 1;
let synd = syndrome & mask;
if synd == 0 {
return None;
}
let mut pow: u32 = 1;
for i in 0..(FRAME_BITS - 1) as u32 {
if pow == synd {
return Some((FRAME_BITS - 2) - i);
}
pow <<= 1;
if (pow >> PARITY_BITS) & 1 != 0 {
pow ^= GEN_POLY;
}
pow &= mask;
}
None
}
pub fn encode_multiframe(coded_data: &[u8], coded_data_bits: usize) -> Vec<u8> {
assert!(
coded_data_bits <= coded_data.len() * 8,
"encode_multiframe: coded_data_bits ({}) exceeds buffer ({} bits)",
coded_data_bits,
coded_data.len() * 8
);
let coded_per_frame = (DATA_BITS - 1) as usize; let frames_needed = if coded_data_bits == 0 {
MULTIFRAME_FRAMES as usize
} else {
let n = coded_data_bits.div_ceil(coded_per_frame);
n.div_ceil(MULTIFRAME_FRAMES as usize) * MULTIFRAME_FRAMES as usize
};
let total_bits = frames_needed * FRAME_BITS as usize;
let mut out = vec![0u8; total_bits.div_ceil(8)];
let mut write_pos: usize = 0;
let put_bit = |out: &mut [u8], pos: usize, bit: u8| {
let byte = pos / 8;
let shift = 7 - (pos & 7);
out[byte] = (out[byte] & !(1u8 << shift)) | ((bit & 1) << shift);
};
let read_bit_in = |coded: &[u8], i: usize| -> u8 { (coded[i / 8] >> (7 - (i & 7))) & 1 };
let mut scratch = [0u8; 62];
let mut consumed_bits = 0usize;
for f in 0..frames_needed {
let s_idx = f % MULTIFRAME_FRAMES as usize;
let s_bit = ALIGNMENT_PATTERN[s_idx];
let fi: u8 = if consumed_bits < coded_data_bits {
1
} else {
0
};
for b in scratch.iter_mut() {
*b = 0;
}
scratch[0] = fi << 7;
for j in 0..coded_per_frame {
let bit = if fi == 1 {
if consumed_bits < coded_data_bits {
let v = read_bit_in(coded_data, consumed_bits);
consumed_bits += 1;
v
} else {
1
}
} else {
1
};
let pos = j + 1;
scratch[pos / 8] |= bit << (7 - (pos & 7));
}
let par = parity18(&scratch);
put_bit(&mut out, write_pos, s_bit);
write_pos += 1;
put_bit(&mut out, write_pos, fi);
write_pos += 1;
for j in 0..coded_per_frame {
let bit = (scratch[(j + 1) / 8] >> (7 - ((j + 1) & 7))) & 1;
put_bit(&mut out, write_pos, bit);
write_pos += 1;
}
for j in 0..(PARITY_BITS as usize) {
let bit = ((par >> (PARITY_BITS as usize - 1 - j)) & 1) as u8;
put_bit(&mut out, write_pos, bit);
write_pos += 1;
}
}
debug_assert_eq!(write_pos, total_bits);
out
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DecodedMultiframe {
pub data: Vec<u8>,
pub data_bits: usize,
pub frames_consumed: usize,
pub corrupted_frames: usize,
pub fill_frames: usize,
pub corrected_frames: usize,
pub uncorrectable_frames: usize,
}
pub fn decode_multiframe(framed: &[u8]) -> Option<DecodedMultiframe> {
let total_bits = framed.len() * 8;
let lock_frames = 3 * MULTIFRAME_FRAMES as usize;
let lock_span_bits = lock_frames * FRAME_BITS as usize;
if total_bits < lock_span_bits {
return None;
}
let read_bit = |pos: usize| -> u8 { (framed[pos / 8] >> (7 - (pos & 7))) & 1 };
let mut lock: Option<usize> = None;
'outer: for bit0 in 0..FRAME_BITS as usize {
if bit0 + lock_span_bits > total_bits {
break;
}
for k in 0..lock_frames {
let s = read_bit(bit0 + k * FRAME_BITS as usize);
if s != ALIGNMENT_PATTERN[k % MULTIFRAME_FRAMES as usize] {
continue 'outer;
}
}
lock = Some(bit0);
break;
}
let bit0 = lock?;
let phase0 = 0usize;
let mut data: Vec<u8> = Vec::new();
let mut data_bits = 0usize;
let mut put_data_bit = |bit: u8| {
if data_bits % 8 == 0 {
data.push(0);
}
let byte_idx = data_bits / 8;
let shift = 7 - (data_bits & 7);
data[byte_idx] |= (bit & 1) << shift;
data_bits += 1;
};
let mut frames_consumed = 0usize;
let mut corrupted_frames = 0usize;
let mut fill_frames = 0usize;
let mut cursor = bit0;
while cursor + FRAME_BITS as usize <= total_bits {
let frame_idx = (phase0 + frames_consumed) % MULTIFRAME_FRAMES as usize;
let _expected_s = ALIGNMENT_PATTERN[frame_idx];
let _s = read_bit(cursor);
let fi = read_bit(cursor + 1);
let mut scratch = [0u8; 62];
scratch[0] = fi << 7;
let data_start = cursor + 2;
for j in 0..((DATA_BITS - 1) as usize) {
let bit = read_bit(data_start + j);
let pos = j + 1;
scratch[pos / 8] |= bit << (7 - (pos & 7));
}
let mut par = 0u32;
let par_start = data_start + (DATA_BITS - 1) as usize;
for j in 0..(PARITY_BITS as usize) {
par = (par << 1) | read_bit(par_start + j) as u32;
}
if syndrome18(&scratch, par) != 0 {
corrupted_frames += 1;
}
if fi == 1 {
for j in 0..((DATA_BITS - 1) as usize) {
let bit = read_bit(data_start + j);
put_data_bit(bit);
}
} else {
fill_frames += 1;
}
frames_consumed += 1;
cursor += FRAME_BITS as usize;
}
Some(DecodedMultiframe {
data,
data_bits,
frames_consumed,
corrupted_frames,
fill_frames,
corrected_frames: 0,
uncorrectable_frames: 0,
})
}
pub fn decode_multiframe_with_correction(framed: &[u8]) -> Option<DecodedMultiframe> {
let total_bits = framed.len() * 8;
let lock_frames = 3 * MULTIFRAME_FRAMES as usize;
let lock_span_bits = lock_frames * FRAME_BITS as usize;
if total_bits < lock_span_bits {
return None;
}
let read_bit = |pos: usize| -> u8 { (framed[pos / 8] >> (7 - (pos & 7))) & 1 };
let mut lock: Option<usize> = None;
'outer: for bit0 in 0..FRAME_BITS as usize {
if bit0 + lock_span_bits > total_bits {
break;
}
for k in 0..lock_frames {
let s = read_bit(bit0 + k * FRAME_BITS as usize);
if s != ALIGNMENT_PATTERN[k % MULTIFRAME_FRAMES as usize] {
continue 'outer;
}
}
lock = Some(bit0);
break;
}
let bit0 = lock?;
let mut data: Vec<u8> = Vec::new();
let mut data_bits = 0usize;
let mut put_data_bit = |bit: u8| {
if data_bits % 8 == 0 {
data.push(0);
}
let byte_idx = data_bits / 8;
let shift = 7 - (data_bits & 7);
data[byte_idx] |= (bit & 1) << shift;
data_bits += 1;
};
let mut frames_consumed = 0usize;
let mut corrupted_frames = 0usize;
let mut corrected_frames = 0usize;
let mut uncorrectable_frames = 0usize;
let mut fill_frames = 0usize;
let coded_per_frame = (DATA_BITS - 1) as usize;
let mut cursor = bit0;
while cursor + FRAME_BITS as usize <= total_bits {
let _s = read_bit(cursor);
let mut fi = read_bit(cursor + 1);
let mut scratch = [0u8; 62];
scratch[0] = fi << 7;
let data_start = cursor + 2;
for j in 0..coded_per_frame {
let bit = read_bit(data_start + j);
let pos = j + 1;
scratch[pos / 8] |= bit << (7 - (pos & 7));
}
let mut par = 0u32;
let par_start = data_start + coded_per_frame;
for j in 0..(PARITY_BITS as usize) {
par = (par << 1) | read_bit(par_start + j) as u32;
}
let synd = syndrome18(&scratch, par);
if synd != 0 {
corrupted_frames += 1;
if let Some(p) = locate_single_error(synd) {
let p = p as usize;
if p == 0 {
fi ^= 1;
scratch[0] ^= 0x80;
} else if p < (DATA_BITS as usize) {
let bit_in_scratch = p; scratch[bit_in_scratch / 8] ^= 1 << (7 - (bit_in_scratch & 7));
} else {
let par_bit = p - DATA_BITS as usize;
par ^= 1 << (PARITY_BITS as usize - 1 - par_bit);
}
corrected_frames += 1;
debug_assert_eq!(
syndrome18(&scratch, par),
0,
"post-correction syndrome should be zero (p={p})"
);
} else {
uncorrectable_frames += 1;
}
}
if fi == 1 {
for j in 0..coded_per_frame {
let pos = j + 1;
let bit = (scratch[pos / 8] >> (7 - (pos & 7))) & 1;
put_data_bit(bit);
}
} else {
fill_frames += 1;
}
frames_consumed += 1;
cursor += FRAME_BITS as usize;
}
Some(DecodedMultiframe {
data,
data_bits,
frames_consumed,
corrupted_frames,
fill_frames,
corrected_frames,
uncorrectable_frames,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generator_polynomial_factors_match_spec() {
let a: u32 = 0b10_0001_0001; let b: u32 = 0b10_0101_1001; assert_eq!(a, 0x211);
assert_eq!(b, 0x259);
let mut prod: u32 = 0;
for i in 0..16 {
if (b >> i) & 1 == 1 {
prod ^= a << i;
}
}
assert_eq!(prod, GEN_POLY, "g(x) factors don't match: got 0x{prod:X}");
}
#[test]
fn parity_of_all_zeros_is_zero() {
let zero = [0u8; 62];
assert_eq!(parity18(&zero), 0);
}
#[test]
fn parity_then_syndrome_cancels_for_all_ones() {
let mut ones = [0xFFu8; 62];
ones[61] &= 0b1110_0000;
let par = parity18(&ones);
assert_eq!(syndrome18(&ones, par), 0);
}
#[test]
fn parity_matches_spec_5_4_2_worked_example() {
let mut msg = [0u8; 62];
for i in 1..493 {
msg[i / 8] |= 1 << (7 - (i & 7));
}
let par = parity18(&msg);
assert_eq!(
par, 0x1_B51B,
"spec §5.4.2 worked example: parity should be 0x1B51B (binary 011011010100011011), got 0x{par:X}"
);
assert_eq!(syndrome18(&msg, par), 0);
}
#[test]
fn single_bit_flip_in_data_is_detected() {
let mut msg = [0u8; 62];
msg[3] = 0xA5;
msg[20] = 0x3C;
msg[55] = 0xF0;
let par = parity18(&msg);
assert_eq!(syndrome18(&msg, par), 0, "untouched codeword");
msg[2] ^= 0b0100_0000;
assert_ne!(syndrome18(&msg, par), 0, "single bit flip in data");
}
#[test]
fn single_bit_flip_in_parity_is_detected() {
let msg = [0u8; 62];
let par = parity18(&msg);
assert_eq!(par, 0);
assert_ne!(syndrome18(&msg, 0x20), 0);
}
#[test]
fn encode_then_decode_round_trip() {
let mut payload = [0u8; 50];
let mut s: u32 = 0xDEAD_BEEF;
for b in payload.iter_mut() {
s = s.wrapping_mul(1664525).wrapping_add(1013904223);
*b = (s >> 16) as u8;
}
let mut framed = encode_multiframe(&payload, 50 * 8);
framed.extend(encode_multiframe(&[], 0));
framed.extend(encode_multiframe(&[], 0));
assert_eq!(framed.len(), 3 * 512);
let decoded = decode_multiframe(&framed).expect("frame lock");
assert_eq!(decoded.corrupted_frames, 0);
assert_eq!(decoded.data_bits, 492);
for i in 0..(50 * 8) {
let want = (payload[i / 8] >> (7 - (i & 7))) & 1;
let got = (decoded.data[i / 8] >> (7 - (i & 7))) & 1;
assert_eq!(got, want, "bit {i} mismatch");
}
assert_eq!(decoded.fill_frames, 23);
assert_eq!(decoded.frames_consumed, 24);
}
#[test]
fn encode_then_decode_full_multiframe() {
let mut payload = vec![0u8; 11_808 / 8];
for (i, b) in payload.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(31).wrapping_add(7);
}
let framed = encode_multiframe(&payload, payload.len() * 8);
assert_eq!(framed.len(), 3 * 512);
let decoded = decode_multiframe(&framed).expect("frame lock");
assert_eq!(decoded.corrupted_frames, 0);
assert_eq!(decoded.fill_frames, 0);
assert_eq!(decoded.frames_consumed, 24);
assert_eq!(decoded.data_bits, 11_808);
assert_eq!(&decoded.data[..payload.len()], &payload[..]);
}
#[test]
fn encode_then_decode_two_multiframes() {
let mut payload = vec![0u8; (6 * 3936) / 8];
for (i, b) in payload.iter_mut().enumerate() {
*b = (i as u8 ^ 0x5A).wrapping_mul(13).wrapping_add(1);
}
let framed = encode_multiframe(&payload, payload.len() * 8);
assert_eq!(framed.len(), 6 * 512);
let decoded = decode_multiframe(&framed).expect("frame lock");
assert_eq!(decoded.frames_consumed, 48);
assert_eq!(decoded.fill_frames, 0);
assert_eq!(decoded.corrupted_frames, 0);
assert_eq!(decoded.data_bits, 6 * 3936);
assert_eq!(&decoded.data[..payload.len()], &payload[..]);
}
#[test]
fn data_corruption_surfaces_via_syndrome() {
let payload = vec![0xC3u8; (3 * 3936) / 8];
let mut framed = encode_multiframe(&payload, payload.len() * 8);
assert_eq!(framed.len(), 3 * 512);
framed[700] ^= 0b0001_0000;
let decoded = decode_multiframe(&framed).expect("still lockable");
assert_eq!(decoded.frames_consumed, 24);
assert!(
decoded.corrupted_frames >= 1,
"syndrome should flag at least one corrupted frame"
);
}
#[test]
fn decoder_acquires_lock_with_leading_padding() {
let payload = vec![0x42u8; 11_808 / 8];
let framed = encode_multiframe(&payload, payload.len() * 8);
assert_eq!(framed.len(), 3 * 512);
let mut shifted = vec![0u8; framed.len() + 1];
shifted[0] = 0b1011_0000; for i in 0..(framed.len() * 8) {
let bit = (framed[i / 8] >> (7 - (i & 7))) & 1;
let new_pos = i + 4;
shifted[new_pos / 8] |= bit << (7 - (new_pos & 7));
}
let decoded = decode_multiframe(&shifted).expect("lock with offset");
assert!(
decoded.frames_consumed >= 23,
"should recover at least 23 of 24 frames, got {}",
decoded.frames_consumed
);
assert_eq!(decoded.corrupted_frames, 0);
}
#[test]
fn random_noise_does_not_obtain_lock() {
let buf = vec![0xFFu8; 3 * 512];
let decoded = decode_multiframe(&buf);
assert!(decoded.is_none(), "all-ones stream must not lock");
}
#[test]
fn empty_payload_emits_one_stuffing_multiframe() {
let mut framed = encode_multiframe(&[], 0);
assert_eq!(framed.len(), 512);
framed.extend(encode_multiframe(&[], 0));
framed.extend(encode_multiframe(&[], 0));
let decoded = decode_multiframe(&framed).expect("stuffing locks");
assert_eq!(decoded.frames_consumed, 24);
assert_eq!(decoded.fill_frames, 24);
assert_eq!(decoded.data_bits, 0);
assert_eq!(decoded.corrupted_frames, 0);
}
#[test]
fn locate_single_error_zero_syndrome_returns_none() {
assert_eq!(locate_single_error(0), None);
}
#[test]
fn locate_single_error_round_trips_every_codeword_position() {
for p in 0..((FRAME_BITS - 1) as usize) {
let mut scratch = [0u8; 62];
let mut par: u32 = 0;
if p < (DATA_BITS as usize) {
scratch[p / 8] |= 1 << (7 - (p & 7));
} else {
let pb = p - DATA_BITS as usize;
par |= 1 << (PARITY_BITS as usize - 1 - pb);
}
let s = syndrome18(&scratch, par);
assert_ne!(s, 0, "non-zero error must yield non-zero syndrome (p={p})");
assert_eq!(
locate_single_error(s),
Some(p as u32),
"syndrome 0x{s:X} should map to position {p}"
);
}
}
#[test]
fn locate_single_error_handles_weight_two_patterns() {
for (a, b) in [(0, 5), (3, 200), (100, 492), (1, 510), (0, 510)] {
let mut scratch = [0u8; 62];
let mut par: u32 = 0;
for &p in &[a, b] {
if p < (DATA_BITS as usize) {
scratch[p / 8] ^= 1 << (7 - (p & 7));
} else {
let pb = p - DATA_BITS as usize;
par ^= 1 << (PARITY_BITS as usize - 1 - pb);
}
}
let s = syndrome18(&scratch, par);
let _ = locate_single_error(s);
}
}
#[test]
fn decode_with_correction_no_error_round_trip() {
let mut payload = vec![0u8; 11_808 / 8];
for (i, b) in payload.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(31).wrapping_add(7);
}
let framed = encode_multiframe(&payload, payload.len() * 8);
let decoded = decode_multiframe_with_correction(&framed).expect("lock on clean stream");
assert_eq!(decoded.frames_consumed, 24);
assert_eq!(decoded.corrupted_frames, 0);
assert_eq!(decoded.corrected_frames, 0);
assert_eq!(decoded.uncorrectable_frames, 0);
assert_eq!(&decoded.data[..payload.len()], &payload[..]);
}
#[test]
fn decode_with_correction_recovers_single_bit_data_error() {
let payload = vec![0xC3u8; (3 * 3936) / 8];
let framed_clean = encode_multiframe(&payload, payload.len() * 8);
assert_eq!(framed_clean.len(), 3 * 512);
let mut framed = framed_clean.clone();
framed[700] ^= 0b0001_0000;
let decoded =
decode_multiframe_with_correction(&framed).expect("lock survives single-bit error");
assert_eq!(decoded.frames_consumed, 24);
assert_eq!(decoded.corrupted_frames, 1);
assert_eq!(decoded.corrected_frames, 1);
assert_eq!(decoded.uncorrectable_frames, 0);
assert_eq!(&decoded.data[..payload.len()], &payload[..]);
}
#[test]
fn decode_with_correction_recovers_single_bit_parity_error() {
let payload = vec![0x5Au8; (3 * 3936) / 8];
let framed_clean = encode_multiframe(&payload, payload.len() * 8);
let mut framed = framed_clean.clone();
framed[62] ^= 0b0000_1000;
let decoded =
decode_multiframe_with_correction(&framed).expect("lock survives parity flip");
assert_eq!(decoded.corrupted_frames, 1);
assert_eq!(decoded.corrected_frames, 1);
assert_eq!(decoded.uncorrectable_frames, 0);
assert_eq!(&decoded.data[..payload.len()], &payload[..]);
}
#[test]
fn decode_with_correction_recovers_single_bit_fi_error() {
let payload = vec![0xA5u8; (3 * 3936) / 8];
let framed_clean = encode_multiframe(&payload, payload.len() * 8);
let mut framed = framed_clean.clone();
framed[0] ^= 0b0100_0000;
let decoded = decode_multiframe_with_correction(&framed).expect("lock survives Fi flip");
assert_eq!(decoded.corrupted_frames, 1);
assert_eq!(decoded.corrected_frames, 1);
assert_eq!(decoded.uncorrectable_frames, 0);
assert_eq!(&decoded.data[..payload.len()], &payload[..]);
assert_eq!(decoded.fill_frames, 0);
}
#[test]
fn decode_with_correction_two_bit_error_does_not_panic() {
let payload = vec![0x33u8; (3 * 3936) / 8];
let framed_clean = encode_multiframe(&payload, payload.len() * 8);
let mut framed = framed_clean.clone();
framed[5] ^= 0b0000_0001;
framed[25] ^= 0b0010_0000;
let decoded =
decode_multiframe_with_correction(&framed).expect("frame lock survives weight-2 error");
assert_eq!(decoded.frames_consumed, 24);
assert_eq!(decoded.corrupted_frames, 1);
assert_eq!(
decoded.corrected_frames + decoded.uncorrectable_frames,
decoded.corrupted_frames
);
}
#[test]
fn decode_with_correction_stuffing_only_path() {
let mut framed = encode_multiframe(&[], 0);
framed.extend(encode_multiframe(&[], 0));
framed.extend(encode_multiframe(&[], 0));
let decoded = decode_multiframe_with_correction(&framed).expect("stuffing locks");
assert_eq!(decoded.frames_consumed, 24);
assert_eq!(decoded.fill_frames, 24);
assert_eq!(decoded.data_bits, 0);
assert_eq!(decoded.corrupted_frames, 0);
assert_eq!(decoded.corrected_frames, 0);
assert_eq!(decoded.uncorrectable_frames, 0);
}
#[test]
fn decode_with_correction_sweeps_every_protected_bit() {
let payload = vec![0x9Cu8; (3 * 3936) / 8];
let framed_clean = encode_multiframe(&payload, payload.len() * 8);
for protected_pos in 0..((FRAME_BITS - 1) as usize) {
let bit_idx = 1 + protected_pos;
let mut framed = framed_clean.clone();
framed[bit_idx / 8] ^= 1 << (7 - (bit_idx & 7));
let decoded = decode_multiframe_with_correction(&framed)
.unwrap_or_else(|| panic!("lock at pos {protected_pos}"));
assert_eq!(
decoded.corrupted_frames, 1,
"exactly one frame should be flagged (pos {protected_pos})"
);
assert_eq!(
decoded.corrected_frames, 1,
"single-bit error must be corrected (pos {protected_pos})"
);
assert_eq!(decoded.uncorrectable_frames, 0);
assert_eq!(
&decoded.data[..payload.len()],
&payload[..],
"payload should match after correction at pos {protected_pos}"
);
}
}
}