use serde::Deserialize;
use serde::Serialize;
use crate::numerical::finite_field::gf256_add;
use crate::numerical::finite_field::gf256_div;
use crate::numerical::finite_field::gf256_inv;
use crate::numerical::finite_field::gf256_mul;
use crate::numerical::finite_field::gf256_pow;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PolyGF256(pub Vec<u8>);
impl PolyGF256 {
#[must_use]
pub const fn new(coeffs: Vec<u8>) -> Self {
Self(coeffs)
}
#[must_use]
pub const fn degree(&self) -> usize {
if self.0.is_empty() {
0
} else {
self.0.len() - 1
}
}
#[must_use]
pub fn eval(
&self,
x: u8,
) -> u8 {
self.0
.iter()
.rfold(0, |acc, &coeff| gf256_add(gf256_mul(acc, x), coeff))
}
#[must_use]
pub fn poly_add(
&self,
other: &Self,
) -> Self {
let mut result = vec![0; self.0.len().max(other.0.len())];
let result_len = result.len();
for i in 0..self.0.len() {
result[i + result_len - self.0.len()] = self.0[i];
}
for i in 0..other.0.len() {
result[i + result_len - other.0.len()] ^= other.0[i];
}
Self(result)
}
#[must_use]
pub fn poly_sub(
&self,
other: &Self,
) -> Self {
self.poly_add(other)
}
#[must_use]
pub fn poly_mul(
&self,
other: &Self,
) -> Self {
let mut result = vec![0; self.degree() + other.degree() + 1];
for i in 0..=self.degree() {
for j in 0..=other.degree() {
result[i + j] ^= gf256_mul(self.0[i], other.0[j]);
}
}
Self(result)
}
pub fn poly_div(
&self,
divisor: &Self,
) -> Result<(Self, Self), String> {
if divisor.0.is_empty() {
return Err("Division by zero \
polynomial"
.to_string());
}
let mut rem = self.0.clone();
let mut quot = vec![0; self.degree() + 1];
let divisor_lead_inv = gf256_inv(divisor.0[0])?;
while rem.len() >= divisor.0.len() {
let lead_coeff = rem[0];
let q_coeff = gf256_mul(lead_coeff, divisor_lead_inv);
let deg_diff = rem.len() - divisor.0.len();
quot[deg_diff] = q_coeff;
for (i, var) in rem.iter_mut().enumerate().take(divisor.0.len()) {
*var ^= gf256_mul(divisor.0[i], q_coeff);
}
rem.remove(0);
}
Ok((Self(quot), Self(rem)))
}
#[must_use]
pub fn derivative(&self) -> Self {
let mut deriv = vec![0; self.degree()];
for i in 1..=self.degree() {
if i % 2 != 0 {
deriv[i - 1] = self.0[i];
}
}
Self(deriv)
}
#[must_use]
pub fn scale(
&self,
c: u8,
) -> Self {
Self(self.0.iter().map(|&coeff| gf256_mul(coeff, c)).collect())
}
#[must_use]
pub fn normalize(&self) -> Self {
let start = self.0.iter().position(|&x| x != 0).unwrap_or(self.0.len());
Self(self.0[start..].to_vec())
}
}
pub(crate) fn rs_generator_poly(n_parity: usize) -> Result<Vec<u8>, String> {
if n_parity == 0 {
return Err("Number of parity symbols \
must be positive"
.to_string());
}
let mut g = vec![1u8];
for i in 0..n_parity {
let root = gf256_pow(2, i as u64);
let factor = vec![1u8, root];
g = poly_mul_gf256(&g, &factor);
}
Ok(g)
}
fn poly_mul_gf256(
p1: &[u8],
p2: &[u8],
) -> Vec<u8> {
if p1.is_empty() || p2.is_empty() {
return vec![];
}
let mut result = vec![0; p1.len() + p2.len() - 1];
for i in 0..p1.len() {
for j in 0..p2.len() {
result[i + j] ^= gf256_mul(p1[i], p2[j]);
}
}
result
}
fn poly_div_gf256(
mut dividend: Vec<u8>,
divisor: &[u8],
) -> Result<Vec<u8>, String> {
if divisor.is_empty() {
return Err("Divisor cannot \
be empty"
.to_string());
}
let divisor_len = divisor.len();
let lead_divisor = divisor[0];
let lead_divisor_inv = gf256_inv(lead_divisor)?;
while dividend.len() >= divisor_len {
let lead_dividend = dividend[0];
if lead_dividend == 0 {
dividend.remove(0);
continue;
}
let coeff = gf256_mul(lead_dividend, lead_divisor_inv);
for i in 0..divisor_len {
let term = gf256_mul(coeff, divisor[i]);
dividend[i] ^= term;
}
dividend.remove(0);
}
while dividend.len() < divisor_len - 1 {
dividend.insert(0, 0);
}
Ok(dividend)
}
fn poly_eval_gf256(
poly: &[u8],
x: u8,
) -> u8 {
let mut y = 0u8;
for &coeff in poly {
y = gf256_mul(y, x) ^ coeff;
}
y
}
pub fn reed_solomon_encode(
message: &[u8],
n_parity: usize,
) -> Result<Vec<u8>, String> {
if message.len() + n_parity > 255 {
return Err("Message + parity length \
cannot exceed 255"
.to_string());
}
if n_parity == 0 {
return Ok(message.to_vec());
}
let gen_poly = rs_generator_poly(n_parity)?;
let mut message_poly = message.to_vec();
message_poly.extend(vec![0; n_parity]);
let remainder = poly_div_gf256(message_poly, &gen_poly)?;
let mut codeword = message.to_vec();
codeword.extend(&remainder);
Ok(codeword)
}
pub fn reed_solomon_decode(
codeword: &mut [u8],
n_parity: usize,
) -> Result<(), String> {
let syndromes = calculate_syndromes(codeword, n_parity);
if syndromes.iter().all(|&s| s == 0) {
return Ok(());
}
let sigma = berlekamp_massey(&syndromes);
let error_locations = chien_search_extended(&sigma, codeword.len())?;
if error_locations.is_empty() {
return Err("Failed to find \
error locations."
.to_string());
}
let mut omega = poly_mul_gf256(&syndromes, &sigma);
if omega.len() > n_parity {
omega = omega[omega.len() - n_parity..].to_vec();
}
let error_magnitudes =
forney_algorithm_extended(&omega, &sigma, &error_locations, codeword.len())?;
for (i, &loc) in error_locations.iter().enumerate() {
codeword[loc] ^= error_magnitudes[i];
}
Ok(())
}
fn berlekamp_massey(syndromes: &[u8]) -> Vec<u8> {
let mut sigma = vec![1u8];
let mut b = vec![1u8];
let mut l = 0;
for i in 0..syndromes.len() {
let mut delta = syndromes[i];
for j in 1..=l {
if j < sigma.len() {
delta ^= gf256_mul(sigma[j], syndromes[i - j]);
}
}
b.insert(0, 0);
if delta != 0 {
let t = sigma.clone();
let scaled_b: Vec<u8> = b.iter().map(|&c| gf256_mul(c, delta)).collect();
sigma = poly_add_gf256(&sigma, &scaled_b);
if 2 * l <= i {
l = i + 1 - l;
let inv_delta = gf256_inv(delta).unwrap();
b = t.iter().map(|&c| gf256_mul(c, inv_delta)).collect();
}
}
}
sigma
}
fn poly_eval_gf256_le(
poly: &[u8],
x: u8,
) -> u8 {
let mut result = 0;
for &coeff in poly.iter().rev() {
result = gf256_mul(result, x) ^ coeff;
}
result
}
fn poly_add_gf256(
p1: &[u8],
p2: &[u8],
) -> Vec<u8> {
let mut result = vec![0u8; p1.len().max(p2.len())];
for (i, &c) in p1.iter().enumerate() {
result[i] ^= c;
}
for (i, &c) in p2.iter().enumerate() {
result[i] ^= c;
}
result
}
fn chien_search_extended(
sigma: &[u8],
codeword_len: usize,
) -> Result<Vec<usize>, String> {
let mut error_locs = Vec::new();
let degree = sigma.iter().rposition(|&x| x != 0).unwrap_or(0);
if degree == 0 {
return Ok(Vec::new());
}
for i in 0..codeword_len {
let power = codeword_len - 1 - i;
let x_inv = gf256_pow(2, power as u64);
let x = gf256_inv(x_inv).unwrap_or(0);
if poly_eval_gf256_le(sigma, x) == 0 {
error_locs.push(i);
}
}
if error_locs.len() != degree {
return Err(format!(
"Uncorrectable errors: \
found {} roots, but \
expected {}",
error_locs.len(),
degree
));
}
Ok(error_locs)
}
fn forney_algorithm_extended(
omega: &[u8],
sigma: &[u8],
error_locs: &[usize],
codeword_len: usize,
) -> Result<Vec<u8>, String> {
let sigma_prime = poly_derivative_gf256(sigma);
let mut magnitudes = Vec::new();
for &loc in error_locs {
let power = codeword_len - 1 - loc;
let x_inv = gf256_pow(2, power as u64); let x = gf256_inv(x_inv).unwrap_or(0);
let omega_val = poly_eval_gf256_le(omega, x);
let sigma_prime_val = poly_eval_gf256_le(&sigma_prime, x);
if sigma_prime_val == 0 {
return Err("Division by zero in \
Forney"
.to_string());
}
let magnitude = gf256_div(omega_val, sigma_prime_val)?;
magnitudes.push(magnitude);
}
Ok(magnitudes)
}
fn poly_derivative_gf256(poly: &[u8]) -> Vec<u8> {
let mut result = Vec::new();
for (i, &coeff) in poly.iter().enumerate().skip(1) {
if i % 2 == 1 {
result.push(coeff);
} else {
result.push(0);
}
}
if result.is_empty() {
result.push(0);
}
result
}
#[must_use]
pub fn reed_solomon_check(
codeword: &[u8],
n_parity: usize,
) -> bool {
let syndromes = calculate_syndromes(codeword, n_parity);
syndromes.iter().all(|&s| s == 0)
}
#[must_use]
pub fn calculate_syndromes(
codeword: &[u8],
n_parity: usize,
) -> Vec<u8> {
let mut syndromes = Vec::with_capacity(n_parity);
for i in 0..n_parity {
let alpha_i = gf256_pow(2, i as u64);
syndromes.push(poly_eval_gf256(codeword, alpha_i));
}
syndromes
}
pub fn chien_search(sigma: &PolyGF256) -> Result<Vec<u8>, String> {
let mut error_locs = Vec::new();
for i in 0..255u8 {
let alpha_inv = gf256_inv(gf256_pow(2, u64::from(i)))?;
if sigma.eval(alpha_inv) == 0 {
error_locs.push(i);
}
}
Ok(error_locs)
}
pub fn forney_algorithm(
omega: &PolyGF256,
sigma: &PolyGF256,
error_locs: &[u8],
) -> Result<Vec<u8>, String> {
let sigma_prime = sigma.derivative();
let mut magnitudes = Vec::new();
for &loc in error_locs {
let x_inv = gf256_inv(gf256_pow(2, u64::from(loc)))?;
let omega_val = omega.eval(x_inv);
let sigma_prime_val = sigma_prime.eval(x_inv);
let magnitude = gf256_div(gf256_mul(omega_val, x_inv), sigma_prime_val)?;
magnitudes.push(magnitude);
}
Ok(magnitudes)
}
#[must_use]
pub fn hamming_distance_numerical(
a: &[u8],
b: &[u8],
) -> Option<usize> {
if a.len() != b.len() {
return None;
}
Some(a.iter().zip(b.iter()).filter(|&(&x, &y)| x != y).count())
}
#[must_use]
pub fn hamming_weight_numerical(data: &[u8]) -> usize {
data.iter().filter(|&&x| x != 0).count()
}
#[must_use]
pub fn hamming_encode_numerical(data: &[u8]) -> Option<Vec<u8>> {
if data.len() != 4 {
return None;
}
let d3 = data[0];
let d5 = data[1];
let d6 = data[2];
let d7 = data[3];
let p1 = d3 ^ d5 ^ d7;
let p2 = d3 ^ d6 ^ d7;
let p4 = d5 ^ d6 ^ d7;
Some(vec![p1, p2, d3, p4, d5, d6, d7])
}
pub fn hamming_decode_numerical(codeword: &[u8]) -> Result<(Vec<u8>, Option<usize>), String> {
if codeword.len() != 7 {
return Err("Codeword length \
must be 7"
.to_string());
}
let p1_in = codeword[0];
let p2_in = codeword[1];
let d3_in = codeword[2];
let p4_in = codeword[3];
let d5_in = codeword[4];
let d6_in = codeword[5];
let d7_in = codeword[6];
let p1_calc = d3_in ^ d5_in ^ d7_in;
let p2_calc = d3_in ^ d6_in ^ d7_in;
let p4_calc = d5_in ^ d6_in ^ d7_in;
let c1 = p1_in ^ p1_calc;
let c2 = p2_in ^ p2_calc;
let c4 = p4_in ^ p4_calc;
let error_pos = (c4 << 2) | (c2 << 1) | c1;
let mut corrected_codeword = codeword.to_vec();
let error_index = if error_pos != 0 {
let index = error_pos as usize - 1;
if index < corrected_codeword.len() {
corrected_codeword[index] ^= 1;
}
Some(error_pos as usize)
} else {
None
};
let corrected_data = vec![
corrected_codeword[2],
corrected_codeword[4],
corrected_codeword[5],
corrected_codeword[6],
];
Ok((corrected_data, error_index))
}
#[must_use]
pub fn hamming_check_numerical(codeword: &[u8]) -> bool {
if codeword.len() != 7 {
return false;
}
let p1_in = codeword[0];
let p2_in = codeword[1];
let d3_in = codeword[2];
let p4_in = codeword[3];
let d5_in = codeword[4];
let d6_in = codeword[5];
let d7_in = codeword[6];
let p1_calc = d3_in ^ d5_in ^ d7_in;
let p2_calc = d3_in ^ d6_in ^ d7_in;
let p4_calc = d5_in ^ d6_in ^ d7_in;
p1_in == p1_calc && p2_in == p2_calc && p4_in == p4_calc
}
#[must_use]
pub fn bch_encode(
data: &[u8],
t: usize,
) -> Vec<u8> {
let n_parity = 2 * t;
let mut codeword = data.to_vec();
for i in 0..n_parity {
let mut parity = 0u8;
for (j, &bit) in data.iter().enumerate() {
if ((j + 1) >> i) & 1 == 1 {
parity ^= bit;
}
}
codeword.push(parity);
}
codeword
}
pub fn bch_decode(
codeword: &[u8],
t: usize,
) -> Result<Vec<u8>, String> {
let n_parity = 2 * t;
if codeword.len() < n_parity {
return Err("Codeword too \
short"
.to_string());
}
let data_len = codeword.len() - n_parity;
let data = &codeword[..data_len];
let parity_bits = &codeword[data_len..];
let mut syndrome = 0usize;
for (i, &parity_bit) in parity_bits.iter().enumerate() {
let mut expected_parity = 0u8;
for (j, &bit) in data.iter().enumerate() {
if ((j + 1) >> i) & 1 == 1 {
expected_parity ^= bit;
}
}
if expected_parity != parity_bit {
syndrome |= 1 << i;
}
}
if syndrome == 0 {
return Ok(data.to_vec());
}
if syndrome > 0 && syndrome <= data_len {
let mut corrected = data.to_vec();
corrected[syndrome - 1] ^= 1;
return Ok(corrected);
}
Err("Unable to correct errors".to_string())
}
const CRC32_POLYNOMIAL: u32 = 0xEDB8_8320;
const CRC16_POLYNOMIAL: u16 = 0xA001;
const CRC8_POLYNOMIAL: u8 = 0x07;
#[must_use]
pub fn crc32_compute_numerical(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFF_FFFF;
for byte in data {
crc ^= u32::from(*byte);
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ CRC32_POLYNOMIAL;
} else {
crc >>= 1;
}
}
}
!crc
}
#[must_use]
pub fn crc32_verify_numerical(
data: &[u8],
expected_crc: u32,
) -> bool {
crc32_compute_numerical(data) == expected_crc
}
#[must_use]
pub fn crc32_update_numerical(
crc: u32,
data: &[u8],
) -> u32 {
let mut crc = crc;
for byte in data {
crc ^= u32::from(*byte);
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ CRC32_POLYNOMIAL;
} else {
crc >>= 1;
}
}
}
crc
}
#[must_use]
pub const fn crc32_finalize_numerical(crc: u32) -> u32 {
!crc
}
#[must_use]
pub fn crc16_compute(data: &[u8]) -> u16 {
let mut crc: u16 = 0x0000;
for byte in data {
crc ^= u16::from(*byte);
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ CRC16_POLYNOMIAL;
} else {
crc >>= 1;
}
}
}
crc
}
#[must_use]
pub fn crc8_compute(data: &[u8]) -> u8 {
let mut crc: u8 = 0;
for byte in data {
crc ^= *byte;
for _ in 0..8 {
if crc & 0x80 != 0 {
crc = (crc << 1) ^ CRC8_POLYNOMIAL;
} else {
crc <<= 1;
}
}
}
crc
}
#[must_use]
pub fn interleave(
data: &[u8],
depth: usize,
) -> Vec<u8> {
if depth == 0 || data.is_empty() {
return data.to_vec();
}
let n = data.len();
let rows = n.div_ceil(depth);
let mut result = Vec::with_capacity(n);
for col in 0..depth {
for row in 0..rows {
let idx = row * depth + col;
if idx < n {
result.push(data[idx]);
}
}
}
result
}
#[must_use]
pub fn deinterleave(
data: &[u8],
depth: usize,
) -> Vec<u8> {
if depth == 0 || data.is_empty() {
return data.to_vec();
}
let n = data.len();
let rows = n.div_ceil(depth);
let full_cols = n % depth;
let full_cols = if full_cols == 0 {
depth
} else {
full_cols
};
let mut result = vec![0u8; n];
let mut idx = 0;
for col in 0..depth {
let col_len = if col < full_cols {
rows
} else {
rows - 1
};
for row in 0..col_len {
let orig_idx = row * depth + col;
if orig_idx < n {
result[orig_idx] = data[idx];
idx += 1;
}
}
}
result
}
#[must_use]
pub fn convolutional_encode(data: &[u8]) -> Vec<u8> {
let mut state: u8 = 0;
let mut output = Vec::with_capacity(data.len() * 2);
for &bit in data {
state = (state >> 1) | ((bit & 1) << 2);
let g1 = (state & 1) ^ ((state >> 2) & 1);
let g2 = (state & 1) ^ ((state >> 1) & 1) ^ ((state >> 2) & 1);
output.push(g1);
output.push(g2);
}
for _ in 0..2 {
state >>= 1;
let g1 = (state & 1) ^ ((state >> 2) & 1);
let g2 = (state & 1) ^ ((state >> 1) & 1) ^ ((state >> 2) & 1);
output.push(g1);
output.push(g2);
}
output
}
#[must_use]
pub fn minimum_distance(codewords: &[Vec<u8>]) -> Option<usize> {
if codewords.len() < 2 {
return None;
}
let mut min_dist: Option<usize> = None;
for i in 0..codewords.len() {
for j in (i + 1)..codewords.len() {
if let Some(dist) = hamming_distance_numerical(&codewords[i], &codewords[j]) {
min_dist = Some(min_dist.map_or(dist, |m| m.min(dist)));
}
}
}
min_dist
}
#[must_use]
pub fn code_rate(
k: usize,
n: usize,
) -> f64 {
if n == 0 {
0.0
} else {
k as f64 / n as f64
}
}
#[must_use]
pub const fn error_correction_capability(min_distance: usize) -> usize {
if min_distance == 0 {
0
} else {
(min_distance - 1) / 2
}
}
#[must_use]
pub const fn error_detection_capability(min_distance: usize) -> usize {
if min_distance == 0 {
0
} else {
min_distance - 1
}
}