use comnum_core::{Error, Result, ensure};
use comnum_math::gf2::poly_rem;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Crc {
name: &'static str,
width: u32,
poly: u64,
}
impl Crc {
pub fn new(name: &'static str, width: u32, poly: u64) -> Result<Crc> {
ensure((1..=64).contains(&width), || format!("the CRC degree must be 1 to 64, got {width}"))?;
ensure(width == 64 || poly < (1u64 << width), || {
format!("the generator {poly:#x} exceeds the degree {width}")
})?;
Ok(Crc { name, width, poly })
}
pub fn name(&self) -> &'static str {
self.name
}
pub fn width(&self) -> u32 {
self.width
}
pub fn poly(&self) -> u64 {
self.poly
}
pub fn crc24a() -> Crc {
Crc { name: "CRC24A", width: 24, poly: 0x86_4CFB }
}
pub fn crc24b() -> Crc {
Crc { name: "CRC24B", width: 24, poly: 0x80_0063 }
}
pub fn crc24c() -> Crc {
Crc { name: "CRC24C", width: 24, poly: 0xB2_B117 }
}
pub fn crc16() -> Crc {
Crc { name: "CRC16", width: 16, poly: 0x1021 }
}
pub fn crc11() -> Crc {
Crc { name: "CRC11", width: 11, poly: 0x621 }
}
pub fn crc6() -> Crc {
Crc { name: "CRC6", width: 6, poly: 0x21 }
}
pub fn from_name(name: &str) -> Result<Crc> {
Ok(match name.to_ascii_lowercase().as_str() {
"crc24a" => Crc::crc24a(),
"crc24b" => Crc::crc24b(),
"crc24c" => Crc::crc24c(),
"crc16" => Crc::crc16(),
"crc11" => Crc::crc11(),
"crc6" => Crc::crc6(),
other => {
return Err(Error::Config(format!(
"unknown CRC {other:?}: crc24a, crc24b, crc24c, crc16, crc11 or crc6"
)));
}
})
}
pub fn checksum(&self, bits: &[u8]) -> Vec<u8> {
poly_rem(bits, &self.generator())
}
fn generator(&self) -> Vec<u8> {
(0..self.width).map(|i| ((self.poly >> i) & 1) as u8).chain([1]).collect()
}
pub fn attach(&self, bits: &[u8]) -> Vec<u8> {
let mut out = bits.to_vec();
out.extend(self.checksum(bits));
out
}
pub fn check(&self, word: &[u8]) -> bool {
let w = self.width as usize;
word.len() >= w && self.checksum(&word[..word.len() - w]) == word[word.len() - w..]
}
}
#[cfg(test)]
mod tests {
use super::*;
fn former_checksum(crc: &Crc, bits: &[u8]) -> Vec<u8> {
let top = 1u64 << (crc.width - 1);
let mask = (top << 1).wrapping_sub(1);
let mut reg = 0u64;
for &b in bits {
let fb = ((reg & top) != 0) ^ (b & 1 == 1);
reg = (reg << 1) & mask;
if fb {
reg ^= crc.poly;
}
}
(0..crc.width).rev().map(|i| ((reg >> i) & 1) as u8).collect()
}
#[test]
fn the_checksums_are_those_of_the_former_register() {
let mut rng = comnum_core::rng::Rng::new(9);
let mut crcs = vec![Crc::crc24a(), Crc::crc24b(), Crc::crc24c(), Crc::crc16(), Crc::crc11(), Crc::crc6()];
crcs.extend([Crc::new("one", 1, 1).unwrap(), Crc::new("wide", 64, 0x1B).unwrap()]);
for crc in &crcs {
for len in [0, 1, 12, 300] {
let bits: Vec<u8> = (0..len).map(|_| rng.bit()).collect();
assert_eq!(crc.checksum(&bits), former_checksum(crc, &bits), "{} on {len} bits", crc.name);
}
}
}
#[test]
fn the_remainder_of_a_codeword_is_zero_and_every_single_error_is_detected() {
for crc in [Crc::crc24a(), Crc::crc24b(), Crc::crc24c(), Crc::crc16(), Crc::crc11(), Crc::crc6()] {
let word = crc.attach(&[1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0]);
assert!(crc.checksum(&word).iter().all(|b| *b == 0), "{}", crc.name);
for i in 0..word.len() {
let mut e = word.clone();
e[i] ^= 1;
assert!(!crc.check(&e), "{} misses an error at {i}", crc.name);
}
}
let msg: Vec<u8> = b"123456789".iter().flat_map(|c| (0..8).rev().map(move |i| (c >> i) & 1)).collect();
let v = Crc::crc16().checksum(&msg).iter().fold(0u32, |a, b| (a << 1) | u32::from(*b));
assert_eq!(v, 0x31C3);
for w in [1, 64] {
let c = Crc::new("edge", w, 1).unwrap();
assert!(c.check(&c.attach(&[1, 0, 1])));
}
assert!(Crc::new("bad", 65, 1).is_err() && Crc::new("bad", 4, 16).is_err());
assert_eq!(Crc::from_name("CRC24A").unwrap(), Crc::crc24a());
}
}
#[cfg(test)]
mod literature {
use super::*;
#[test]
fn ts_38_212_sec_5_1_generator_polynomials() {
let exponents = |crc: &Crc| -> Vec<u32> {
let mut e: Vec<u32> = (0..crc.width).filter(|i| (crc.poly >> i) & 1 == 1).collect();
e.push(crc.width);
e.sort_unstable_by(|a, b| b.cmp(a));
e
};
assert_eq!(exponents(&Crc::crc24a()), [24, 23, 18, 17, 14, 11, 10, 7, 6, 5, 4, 3, 1, 0]);
assert_eq!(exponents(&Crc::crc24b()), [24, 23, 6, 5, 1, 0]);
assert_eq!(exponents(&Crc::crc24c()), [24, 23, 21, 20, 17, 15, 13, 12, 8, 4, 2, 1, 0]);
assert_eq!(exponents(&Crc::crc16()), [16, 12, 5, 0]);
assert_eq!(exponents(&Crc::crc11()), [11, 10, 9, 5, 0]);
assert_eq!(exponents(&Crc::crc6()), [6, 5, 0]);
}
}