use crate::crc::{crc_bits, CrcPoly};
use crate::{Error, Result};
pub const SRCPC_TAIL_BITS: usize = 4;
const PUNCTURE_STEPS: [u8; 9] = [0x00, 0x80, 0x88, 0xA8, 0xAA, 0xEA, 0xEE, 0xFE, 0xFF];
pub fn puncture_pattern(class_rate: u8) -> Result<[u8; 4]> {
if class_rate > 24 {
return Err(Error::EpConfigInvalid);
}
let extra = usize::from(class_rate);
Ok([
0xFF,
PUNCTURE_STEPS[extra.min(8)],
PUNCTURE_STEPS[extra.saturating_sub(8).min(8)],
PUNCTURE_STEPS[extra.saturating_sub(16).min(8)],
])
}
pub fn srcpc_coded_len(n_info: usize, class_rate: u8, terminated: bool) -> Result<usize> {
let p = puncture_pattern(class_rate)?;
let steps = n_info + if terminated { SRCPC_TAIL_BITS } else { 0 };
let per_period: usize = p.iter().map(|&b| b.count_ones() as usize).sum();
let full = steps / 8;
let mut len = full * per_period;
for t in (full * 8)..steps {
for &line in &p {
if line & (0x80 >> (t % 8)) != 0 {
len += 1;
}
}
}
Ok(len)
}
#[inline]
fn step(state: u8, u: bool) -> (u8, [bool; 4]) {
let m1 = state & 1 != 0;
let m2 = state & 2 != 0;
let m3 = state & 4 != 0;
let m4 = state & 8 != 0;
let d = m4 ^ m2 ^ m1;
let v = [u, m3 ^ m2 ^ m1 ^ u, m3 ^ m1 ^ u, m3 ^ m2 ^ u];
let next = (u8::from(u ^ d)) | (state << 1) & 0b1110;
(next, v)
}
#[inline]
fn feedback(state: u8) -> bool {
let m1 = state & 1 != 0;
let m2 = state & 2 != 0;
let m4 = state & 8 != 0;
m4 ^ m2 ^ m1
}
pub fn srcpc_encode(info: &[bool], class_rate: u8, terminated: bool) -> Result<Vec<bool>> {
let p = puncture_pattern(class_rate)?;
let mut out = Vec::with_capacity(srcpc_coded_len(info.len(), class_rate, terminated)?);
let mut state = 0u8;
let mut t = 0usize;
let emit = |state: &mut u8, u: bool, t: usize, out: &mut Vec<bool>| {
let (next, v) = step(*state, u);
*state = next;
for (i, &line) in p.iter().enumerate() {
if line & (0x80 >> (t % 8)) != 0 {
out.push(v[i]);
}
}
};
for &u in info {
emit(&mut state, u, t, &mut out);
t += 1;
}
if terminated {
for _ in 0..SRCPC_TAIL_BITS {
let u = feedback(state);
emit(&mut state, u, t, &mut out);
t += 1;
}
debug_assert_eq!(state, 0, "termination must return to state 0");
}
Ok(out)
}
pub fn srcpc_decode(
coded: &[bool],
n_info: usize,
class_rate: u8,
terminated: bool,
) -> Result<Vec<bool>> {
let p = puncture_pattern(class_rate)?;
let steps = n_info + if terminated { SRCPC_TAIL_BITS } else { 0 };
if coded.len() != srcpc_coded_len(n_info, class_rate, terminated)? {
return Err(Error::EpFrameInvalid);
}
const INF: u32 = u32::MAX / 2;
let mut metric = [INF; 16];
metric[0] = 0;
let mut survivors: Vec<[(u8, bool); 16]> = Vec::with_capacity(steps);
let mut pos = 0usize;
for t in 0..steps {
let mut lines: [bool; 4] = [false; 4];
let mut n_lines = 0usize;
for (i, &line) in p.iter().enumerate() {
lines[i] = line & (0x80 >> (t % 8)) != 0;
if lines[i] {
n_lines += 1;
}
}
let received = &coded[pos..pos + n_lines];
pos += n_lines;
let mut next_metric = [INF; 16];
let mut surv = [(0u8, false); 16];
for s in 0u8..16 {
if metric[usize::from(s)] >= INF {
continue;
}
let inputs: &[bool] = if t >= n_info {
if feedback(s) {
&[true]
} else {
&[false]
}
} else {
&[false, true]
};
for &u in inputs {
let (next, v) = step(s, u);
let mut m = metric[usize::from(s)];
let mut ri = 0usize;
for (i, &on) in lines.iter().enumerate() {
if on {
if v[i] != received[ri] {
m += 1;
}
ri += 1;
}
}
let slot = usize::from(next);
if m < next_metric[slot] {
next_metric[slot] = m;
surv[slot] = (s, u);
}
}
}
metric = next_metric;
survivors.push(surv);
}
let mut state: u8 = if terminated {
if metric[0] >= INF {
return Err(Error::EpFrameInvalid);
}
0
} else {
let (best, m) = metric
.iter()
.enumerate()
.min_by_key(|(_, &m)| m)
.map(|(s, &m)| (s as u8, m))
.unwrap_or((0, INF));
if m >= INF {
return Err(Error::EpFrameInvalid);
}
best
};
let mut bits = vec![false; steps];
for t in (0..steps).rev() {
let (prev, u) = survivors[t][usize::from(state)];
bits[t] = u;
state = prev;
}
bits.truncate(n_info);
Ok(bits)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeaderFec {
Majority,
Bch7,
Bch15,
Golay23,
Bch31,
Srcpc16,
}
impl HeaderFec {
pub fn for_len(l: usize) -> Result<Self> {
Ok(match l {
0 => return Err(Error::EpFrameInvalid),
1..=2 => HeaderFec::Majority,
3..=4 => HeaderFec::Bch7,
5..=7 => HeaderFec::Bch15,
8..=12 => HeaderFec::Golay23,
13..=16 => HeaderFec::Bch31,
_ => HeaderFec::Srcpc16,
})
}
fn poly(self) -> Option<(u32, usize, usize)> {
match self {
HeaderFec::Bch7 => Some((0b1011, 3, 1)),
HeaderFec::Bch15 => Some((0b1_1101_0001, 8, 2)),
HeaderFec::Golay23 => Some((0b1010_1110_0011, 11, 3)),
HeaderFec::Bch31 => Some((0b1000_1111_1010_1111, 15, 3)),
_ => None,
}
}
pub fn parity_bits(self, l: usize) -> Result<usize> {
Ok(match self {
HeaderFec::Majority => 2 * l,
HeaderFec::Srcpc16 => {
srcpc_coded_len(l + 4, 8, true)? - l
}
other => other.poly().map(|(_, p, _)| p).unwrap_or(0),
})
}
}
fn poly_parity(info: &[bool], gen: u32, parity: usize) -> Vec<bool> {
let top = 1u32 << parity; let mut reg: u32 = 0;
for &bit in info {
reg = (reg << 1) | u32::from(bit);
if reg & top != 0 {
reg ^= gen;
}
}
for _ in 0..parity {
reg <<= 1;
if reg & top != 0 {
reg ^= gen;
}
}
(0..parity)
.map(|i| reg & (1 << (parity - 1 - i)) != 0)
.collect()
}
pub fn header_fec_encode(info: &[bool]) -> Result<Vec<bool>> {
let fec = HeaderFec::for_len(info.len())?;
Ok(match fec {
HeaderFec::Majority => {
let mut v = Vec::with_capacity(info.len() * 2);
v.extend_from_slice(info);
v.extend_from_slice(info);
v
}
HeaderFec::Srcpc16 => {
let crc = crc_bits(CrcPoly::Crc4, info);
let mut m: Vec<bool> = info.to_vec();
for i in (0..4).rev() {
m.push(crc & (1 << i) != 0);
}
let coded = srcpc_encode(&m, 8, true)?;
let mut parity = Vec::with_capacity(coded.len() - info.len());
for (idx, chunk) in coded.chunks(2).enumerate() {
if idx < info.len() {
parity.push(chunk[1]);
} else {
parity.push(chunk[0]);
parity.push(chunk[1]);
}
}
parity
}
other => {
let (gen, p, _) = other.poly().ok_or(Error::EpFrameInvalid)?;
poly_parity(info, gen, p)
}
})
}
pub fn header_fec_decode(info: &[bool], parity: &[bool]) -> Result<Vec<bool>> {
let l = info.len();
let fec = HeaderFec::for_len(l)?;
if parity.len() != fec.parity_bits(l)? {
return Err(Error::EpFrameInvalid);
}
match fec {
HeaderFec::Majority => {
let mut out = Vec::with_capacity(l);
for i in 0..l {
let votes = u8::from(info[i]) + u8::from(parity[i]) + u8::from(parity[l + i]);
out.push(votes >= 2);
}
Ok(out)
}
HeaderFec::Srcpc16 => {
let mut coded = Vec::with_capacity(l + parity.len());
let mut pi = 0usize;
for &i_bit in info.iter().take(l) {
coded.push(i_bit);
coded.push(parity[pi]);
pi += 1;
}
coded.extend_from_slice(&parity[pi..]);
let decoded = srcpc_decode(&coded, l + 4, 8, true)?;
let (msg, crc_bits_rx) = decoded.split_at(l);
let want = crc_bits(CrcPoly::Crc4, msg);
let mut got = 0u64;
for &b in crc_bits_rx {
got = (got << 1) | u64::from(b);
}
if got != want {
return Err(Error::EpFrameInvalid);
}
Ok(msg.to_vec())
}
other => {
let (gen, p, t) = other.poly().ok_or(Error::EpFrameInvalid)?;
let mut word: Vec<bool> = Vec::with_capacity(l + p);
word.extend_from_slice(info);
word.extend_from_slice(parity);
if poly_syndrome_ok(&word, gen, p) {
return Ok(info.to_vec());
}
let n = word.len();
let mut positions: Vec<usize> = Vec::with_capacity(t);
if search_errors(&mut word, gen, p, t, 0, n, &mut positions) {
return Ok(word[..l].to_vec());
}
Err(Error::EpFrameInvalid)
}
}
}
fn poly_syndrome_ok(word: &[bool], gen: u32, parity: usize) -> bool {
let top = 1u32 << parity;
let mut reg: u32 = 0;
for &bit in word {
reg = (reg << 1) | u32::from(bit);
if reg & top != 0 {
reg ^= gen;
}
}
reg == 0
}
fn search_errors(
word: &mut [bool],
gen: u32,
parity: usize,
budget: usize,
from: usize,
n: usize,
positions: &mut Vec<usize>,
) -> bool {
if budget == 0 {
return false;
}
for i in from..n {
word[i] = !word[i];
positions.push(i);
if poly_syndrome_ok(word, gen, parity)
|| search_errors(word, gen, parity, budget - 1, i + 1, n, positions)
{
return true;
}
positions.pop();
word[i] = !word[i];
}
false
}
#[cfg(test)]
mod tests {
use super::*;
fn prand_bits(n: usize, mut seed: u32) -> Vec<bool> {
let mut v = Vec::with_capacity(n);
for _ in 0..n {
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
v.push(seed & 0x8000_0000 != 0);
}
v
}
#[test]
fn termination_matches_table_1_60() {
let table: [(u8, [u8; 4]); 16] = [
(0b0000, [0, 0, 0, 0]),
(0b0001, [1, 1, 0, 1]),
(0b0010, [1, 0, 1, 0]),
(0b0011, [0, 1, 1, 1]),
(0b0100, [0, 1, 0, 0]),
(0b0101, [1, 0, 0, 1]),
(0b0110, [1, 1, 1, 0]),
(0b0111, [0, 0, 1, 1]),
(0b1000, [1, 0, 0, 0]),
(0b1001, [0, 1, 0, 1]),
(0b1010, [0, 0, 1, 0]),
(0b1011, [1, 1, 1, 1]),
(0b1100, [1, 1, 0, 0]),
(0b1101, [0, 0, 0, 1]),
(0b1110, [0, 1, 1, 0]),
(0b1111, [1, 0, 1, 1]),
];
for (packed, tail) in table {
let mut state = 0u8;
if packed & 0b0001 != 0 {
state |= 1; }
if packed & 0b0010 != 0 {
state |= 2; }
if packed & 0b0100 != 0 {
state |= 4; }
if packed & 0b1000 != 0 {
state |= 8; }
let mut s = state;
for (step_i, &want) in tail.iter().enumerate() {
let u = feedback(s);
assert_eq!(u8::from(u), want, "state {packed:04b} tail step {step_i}");
let (next, _) = step(s, u);
s = next;
}
assert_eq!(s, 0, "state {packed:04b} did not terminate");
}
}
#[test]
fn puncture_patterns_match_table_1_61() {
assert_eq!(puncture_pattern(0).unwrap(), [0xFF, 0x00, 0x00, 0x00]); assert_eq!(puncture_pattern(3).unwrap(), [0xFF, 0xA8, 0x00, 0x00]); assert_eq!(puncture_pattern(5).unwrap(), [0xFF, 0xEA, 0x00, 0x00]); assert_eq!(puncture_pattern(8).unwrap(), [0xFF, 0xFF, 0x00, 0x00]); assert_eq!(puncture_pattern(9).unwrap(), [0xFF, 0xFF, 0x80, 0x00]); assert_eq!(puncture_pattern(16).unwrap(), [0xFF, 0xFF, 0xFF, 0x00]); assert_eq!(puncture_pattern(17).unwrap(), [0xFF, 0xFF, 0xFF, 0x80]); assert_eq!(puncture_pattern(24).unwrap(), [0xFF, 0xFF, 0xFF, 0xFF]); }
#[test]
fn srcpc_roundtrip_all_rates() {
for rate in [0u8, 1, 3, 8, 12, 17, 24] {
for terminated in [false, true] {
let info = prand_bits(97, 0xC0FFEE ^ u32::from(rate));
let coded = srcpc_encode(&info, rate, terminated).unwrap();
assert_eq!(
coded.len(),
srcpc_coded_len(info.len(), rate, terminated).unwrap()
);
if rate == 0 {
let systematic: Vec<bool> = coded
.iter()
.copied()
.take(if terminated {
info.len() + 4
} else {
info.len()
})
.collect();
assert_eq!(&systematic[..info.len()], &info[..]);
}
let decoded = srcpc_decode(&coded, info.len(), rate, terminated).unwrap();
assert_eq!(decoded, info, "rate {rate} terminated {terminated}");
}
}
}
#[test]
fn srcpc_corrects_errors() {
let info = prand_bits(120, 0xDEAD);
let mut coded = srcpc_encode(&info, 8, true).unwrap();
for &pos in &[10usize, 77, 150, 220] {
coded[pos] = !coded[pos];
}
let decoded = srcpc_decode(&coded, info.len(), 8, true).unwrap();
assert_eq!(decoded, info);
}
#[test]
fn header_fec_roundtrip_all_classes() {
for l in [1usize, 2, 3, 4, 5, 7, 8, 12, 13, 16, 17, 30] {
let info = prand_bits(l, 0xBEEF ^ l as u32);
let parity = header_fec_encode(&info).unwrap();
assert_eq!(
parity.len(),
HeaderFec::for_len(l).unwrap().parity_bits(l).unwrap(),
"len {l}"
);
let decoded = header_fec_decode(&info, &parity).unwrap();
assert_eq!(decoded, info, "len {l}");
}
}
#[test]
fn header_fec_corrects_errors() {
let info = prand_bits(12, 0x1234);
let parity = header_fec_encode(&info).unwrap();
let mut rx_info = info.clone();
let mut rx_parity = parity.clone();
rx_info[3] = !rx_info[3];
rx_info[9] = !rx_info[9];
rx_parity[5] = !rx_parity[5];
assert_eq!(header_fec_decode(&rx_info, &rx_parity).unwrap(), info);
let info = prand_bits(2, 0x9);
let parity = header_fec_encode(&info).unwrap();
let mut rx_info = info.clone();
rx_info[0] = !rx_info[0];
assert_eq!(header_fec_decode(&rx_info, &parity).unwrap(), info);
let info = prand_bits(6, 0x77);
let parity = header_fec_encode(&info).unwrap();
let mut rx_parity = parity.clone();
rx_parity[0] = !rx_parity[0];
rx_parity[6] = !rx_parity[6];
assert_eq!(header_fec_decode(&info, &rx_parity).unwrap(), info);
}
}