use super::gf::Gf256;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BchError {
#[error("design t={0} is too large for GF(2^8) BCH (parity would exceed the block)")]
DesignTooLarge(usize),
#[error("shortened length n={0} is out of range 1..=255 or leaves no room for parity")]
BadLength(usize),
#[error("codeword has {0} residual errors after decoding (uncorrectable)")]
Uncorrectable(usize),
}
#[derive(Debug, Clone)]
pub struct Bch {
gf: Gf256,
n: usize,
k: usize,
t: usize,
gen_poly: Vec<u8>,
}
impl Bch {
pub fn new(t: usize) -> Result<Self, BchError> {
Self::shortened(255, t)
}
pub fn shortened(n: usize, t: usize) -> Result<Self, BchError> {
if n == 0 || n > 255 {
return Err(BchError::BadLength(n));
}
let gf = Gf256::new();
let gen_poly = build_generator(&gf, t)?;
let parity_bits = gen_poly.len() - 1;
if parity_bits >= n {
return Err(BchError::BadLength(n));
}
let k = n - parity_bits;
Ok(Self {
gf,
n,
k,
t,
gen_poly,
})
}
pub fn n(&self) -> usize {
self.n
}
pub fn k(&self) -> usize {
self.k
}
pub fn t(&self) -> usize {
self.t
}
pub fn parity_bits(&self) -> usize {
self.gen_poly.len() - 1
}
pub fn encode(&self, message: &[u8]) -> Vec<u8> {
assert_eq!(message.len(), self.k, "BCH message must be exactly k bits");
let parity_bits = self.parity_bits();
let mut reg = vec![0u8; parity_bits];
for &bit in message {
let feedback = bit ^ reg[0];
for i in 0..parity_bits - 1 {
reg[i] = reg[i + 1] ^ (self.gen_poly[i + 1] & feedback);
}
reg[parity_bits - 1] = self.gen_poly[parity_bits] & feedback;
}
let mut codeword = Vec::with_capacity(self.n);
codeword.extend_from_slice(message);
codeword.extend_from_slice(®);
codeword
}
pub fn decode(&self, received: &[u8]) -> Result<Vec<u8>, BchError> {
assert_eq!(received.len(), self.n, "BCH word must be exactly n bits");
let gf = &self.gf;
let shift = 255 - self.n;
let two_t = 2 * self.t;
let mut syndromes = vec![0u8; two_t + 1];
let mut has_error = false;
for (j, syn) in syndromes.iter_mut().enumerate().take(two_t + 1).skip(1) {
let mut acc = 0u8;
for (p, &bit) in received.iter().enumerate() {
if bit != 0 {
let degree = self.n - 1 - p + shift;
acc = gf.add(acc, gf.pow(gf.exp_of(j), degree));
}
}
*syn = acc;
if acc != 0 {
has_error = true;
}
}
if !has_error {
return Ok(received[..self.k].to_vec());
}
let sigma = berlekamp_massey(gf, &syndromes, self.t);
let mut corrected = received.to_vec();
let mut n_found = 0usize;
for d in 0..255usize {
let x = gf.exp_of((255 - (d % 255)) % 255); let mut val = 0u8;
for (i, &c) in sigma.iter().enumerate() {
if c != 0 {
val = gf.add(val, gf.mul(c, gf.pow(x, i)));
}
}
if val == 0 {
if d >= shift && d <= self.n - 1 + shift {
let p = self.n - 1 + shift - d;
if p < self.n {
corrected[p] ^= 1;
n_found += 1;
}
}
}
}
let residual = self.residual_errors(&corrected);
if residual != 0 || n_found > self.t {
return Err(BchError::Uncorrectable(residual.max(n_found)));
}
Ok(corrected[..self.k].to_vec())
}
fn residual_errors(&self, word: &[u8]) -> usize {
let gf = &self.gf;
let shift = 255 - self.n;
let mut count = 0;
for j in 1..=2 * self.t {
let mut acc = 0u8;
for (p, &bit) in word.iter().enumerate() {
if bit != 0 {
let degree = self.n - 1 - p + shift;
acc = gf.add(acc, gf.pow(gf.exp_of(j), degree));
}
}
if acc != 0 {
count += 1;
}
}
count
}
}
fn build_generator(gf: &Gf256, t: usize) -> Result<Vec<u8>, BchError> {
if t == 0 {
return Err(BchError::DesignTooLarge(0));
}
let mut covered = vec![false; 2 * t + 1];
let mut g_lo = vec![1u8];
for i in 1..=2 * t {
if covered[i] {
continue;
}
let mut roots = Vec::new();
let mut e = i % 255;
loop {
if e >= 1 && e <= 2 * t {
covered[e] = true;
}
roots.push(e);
e = (e * 2) % 255;
if e == i % 255 {
break;
}
}
let mut min_poly = vec![1u8];
for &r in &roots {
let alpha_r = gf.exp_of(r);
min_poly = poly_mul_linear(gf, &min_poly, alpha_r);
}
g_lo = poly_mul(gf, &g_lo, &min_poly);
}
if g_lo.len() > 255 {
return Err(BchError::DesignTooLarge(t));
}
let mut g: Vec<u8> = g_lo.iter().rev().map(|&c| c & 1).collect();
while g.len() > 1 && g[0] == 0 {
g.remove(0);
}
Ok(g)
}
fn poly_mul_linear(gf: &Gf256, p: &[u8], alpha: u8) -> Vec<u8> {
let mut out = vec![0u8; p.len() + 1];
for (i, &c) in p.iter().enumerate() {
out[i + 1] = gf.add(out[i + 1], c);
out[i] = gf.add(out[i], gf.mul(c, alpha));
}
out
}
fn poly_mul(gf: &Gf256, a: &[u8], b: &[u8]) -> Vec<u8> {
let mut out = vec![0u8; a.len() + b.len() - 1];
for (i, &ai) in a.iter().enumerate() {
if ai == 0 {
continue;
}
for (j, &bj) in b.iter().enumerate() {
if bj != 0 {
out[i + j] = gf.add(out[i + j], gf.mul(ai, bj));
}
}
}
out
}
fn berlekamp_massey(gf: &Gf256, s: &[u8], t: usize) -> Vec<u8> {
let mut sigma = vec![1u8]; let mut b = vec![1u8]; let mut l = 0usize; let mut m = 1usize;
for n in 1..=2 * t {
let mut delta = s[n];
for i in 1..=l {
if i < sigma.len() {
delta = gf.add(delta, gf.mul(sigma[i], s[n - i]));
}
}
if delta == 0 {
m += 1;
} else if 2 * l < n {
let t_sigma = sigma.clone();
let coef = delta; apply_correction(gf, &mut sigma, &b, coef, m);
l = n - l;
b = t_sigma;
let inv = gf.inv(delta);
for c in b.iter_mut() {
*c = gf.mul(*c, inv);
}
m = 1;
} else {
apply_correction(gf, &mut sigma, &b, delta, m);
m += 1;
}
}
sigma
}
fn apply_correction(gf: &Gf256, sigma: &mut Vec<u8>, b: &[u8], coef: u8, shift: usize) {
let needed = b.len() + shift;
if sigma.len() < needed {
sigma.resize(needed, 0);
}
for (i, &bi) in b.iter().enumerate() {
if bi != 0 {
sigma[i + shift] = gf.add(sigma[i + shift], gf.mul(coef, bi));
}
}
}