#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrcPoly {
Crc1,
Crc2,
Crc3,
Crc4,
Crc5,
Crc6,
Crc7,
Crc8,
Crc9,
Crc10,
Crc11,
Crc12,
Crc13,
Crc14,
Crc15,
Crc16,
Crc24,
Crc32,
}
impl CrcPoly {
pub const fn width(self) -> u32 {
match self {
CrcPoly::Crc1 => 1,
CrcPoly::Crc2 => 2,
CrcPoly::Crc3 => 3,
CrcPoly::Crc4 => 4,
CrcPoly::Crc5 => 5,
CrcPoly::Crc6 => 6,
CrcPoly::Crc7 => 7,
CrcPoly::Crc8 => 8,
CrcPoly::Crc9 => 9,
CrcPoly::Crc10 => 10,
CrcPoly::Crc11 => 11,
CrcPoly::Crc12 => 12,
CrcPoly::Crc13 => 13,
CrcPoly::Crc14 => 14,
CrcPoly::Crc15 => 15,
CrcPoly::Crc16 => 16,
CrcPoly::Crc24 => 24,
CrcPoly::Crc32 => 32,
}
}
pub const fn generator(self) -> u64 {
match self {
CrcPoly::Crc1 => bits(&[0]),
CrcPoly::Crc2 => bits(&[1, 0]),
CrcPoly::Crc3 => bits(&[1, 0]),
CrcPoly::Crc4 => bits(&[3, 2, 0]),
CrcPoly::Crc5 => bits(&[4, 2, 0]),
CrcPoly::Crc6 => bits(&[5, 4, 2, 1, 0]),
CrcPoly::Crc7 => bits(&[3, 1, 0]),
CrcPoly::Crc8 => bits(&[4, 3, 2, 0]),
CrcPoly::Crc9 => bits(&[4, 3, 2, 1, 0]),
CrcPoly::Crc10 => bits(&[9, 5, 4, 1, 0]),
CrcPoly::Crc11 => bits(&[10, 9, 5, 1, 0]),
CrcPoly::Crc12 => bits(&[11, 3, 2, 1, 0]),
CrcPoly::Crc13 => bits(&[12, 11, 8, 7, 4, 2, 0]),
CrcPoly::Crc14 => bits(&[13, 10, 5, 3, 1, 0]),
CrcPoly::Crc15 => bits(&[14, 13, 10, 8, 5, 2, 1, 0]),
CrcPoly::Crc16 => bits(&[15, 2, 0]),
CrcPoly::Crc24 => bits(&[23, 6, 5, 1, 0]),
CrcPoly::Crc32 => bits(&[26, 23, 22, 16, 12, 11, 10, 8, 7, 5, 4, 2, 1, 0]),
}
}
const fn mask(self) -> u64 {
let k = self.width();
if k >= 64 {
u64::MAX
} else {
(1u64 << k) - 1
}
}
}
const fn bits(exponents: &[u32]) -> u64 {
let mut acc = 0u64;
let mut i = 0;
while i < exponents.len() {
acc |= 1u64 << exponents[i];
i += 1;
}
acc
}
pub fn crc_bits(poly: CrcPoly, message_bits: &[bool]) -> u64 {
let k = poly.width();
let gen = poly.generator();
let mask = poly.mask();
let top = 1u64 << (k - 1);
let mut reg: u64 = 0;
for &bit in message_bits {
let high = (reg & top) != 0;
reg = (reg << 1) & mask;
if high {
reg ^= gen;
}
if bit {
reg ^= 1; }
}
for _ in 0..k {
let high = (reg & top) != 0;
reg = (reg << 1) & mask;
if high {
reg ^= gen;
}
}
(!reg) & mask
}
pub fn crc_bytes(poly: CrcPoly, message: &[u8]) -> u64 {
let k = poly.width();
let gen = poly.generator();
let mask = poly.mask();
let top = 1u64 << (k - 1);
let mut reg: u64 = 0;
for &byte in message {
for i in (0..8).rev() {
let bit = (byte >> i) & 1 != 0;
let high = (reg & top) != 0;
reg = (reg << 1) & mask;
if high {
reg ^= gen;
}
if bit {
reg ^= 1;
}
}
}
for _ in 0..k {
let high = (reg & top) != 0;
reg = (reg << 1) & mask;
if high {
reg ^= gen;
}
}
(!reg) & mask
}
pub fn stream_mux_config_crc(config_bits: &[bool]) -> u8 {
crc_bits(CrcPoly::Crc8, config_bits) as u8
}
#[cfg(test)]
mod tests {
use super::*;
fn reference_remainder(poly: CrcPoly, message_bits: &[bool]) -> u64 {
let k = poly.width();
let full_gen = poly.generator() | (1u64 << k); let mut dividend: Vec<bool> = message_bits.to_vec();
dividend.extend(std::iter::repeat(false).take(k as usize));
let mut reg: u64 = 0;
let topbit = 1u64 << k;
for &bit in ÷nd {
reg = (reg << 1) | (bit as u64);
if reg & topbit != 0 {
reg ^= full_gen;
}
}
reg & ((1u64 << k) - 1)
}
fn to_bits(bytes: &[u8]) -> Vec<bool> {
let mut v = Vec::with_capacity(bytes.len() * 8);
for &b in bytes {
for i in (0..8).rev() {
v.push((b >> i) & 1 != 0);
}
}
v
}
#[test]
fn generator_masks_match_spec_exponents() {
assert_eq!(CrcPoly::Crc8.generator(), 0b0001_1101); assert_eq!(CrcPoly::Crc16.generator(), (1 << 15) | (1 << 2) | 1);
assert_eq!(CrcPoly::Crc4.generator(), 0b1101); for p in [
CrcPoly::Crc4,
CrcPoly::Crc5,
CrcPoly::Crc6,
CrcPoly::Crc7,
CrcPoly::Crc8,
CrcPoly::Crc9,
CrcPoly::Crc10,
CrcPoly::Crc11,
CrcPoly::Crc12,
CrcPoly::Crc13,
CrcPoly::Crc14,
CrcPoly::Crc15,
CrcPoly::Crc16,
CrcPoly::Crc24,
CrcPoly::Crc32,
] {
assert!(p.generator() & 1 == 1, "{p:?} missing x⁰ term");
assert!(p.generator() <= p.mask(), "{p:?} generator exceeds width");
}
}
#[test]
fn crc_bits_matches_reference_long_division() {
let messages: [&[u8]; 5] = [
&[],
&[0x00],
&[0xFF],
&[0x12, 0x34, 0x56, 0x78],
&[0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03],
];
for p in [
CrcPoly::Crc4,
CrcPoly::Crc8,
CrcPoly::Crc12,
CrcPoly::Crc16,
CrcPoly::Crc24,
CrcPoly::Crc32,
] {
let mask = p.mask();
for m in messages {
let bits = to_bits(m);
let got = crc_bits(p, &bits);
let expect = (!reference_remainder(p, &bits)) & mask;
assert_eq!(got, expect, "poly {p:?} message {m:x?}");
}
}
}
#[test]
fn crc_bytes_agrees_with_crc_bits() {
let m: &[u8] = &[0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0xFF];
for p in [CrcPoly::Crc8, CrcPoly::Crc16, CrcPoly::Crc32] {
assert_eq!(crc_bytes(p, m), crc_bits(p, &to_bits(m)));
}
}
#[test]
fn inversion_is_present() {
for p in [CrcPoly::Crc4, CrcPoly::Crc8, CrcPoly::Crc16] {
assert_eq!(crc_bits(p, &[]), p.mask());
}
}
#[test]
fn appending_crc_makes_codeword_divisible_modulo_inversion() {
let m: &[u8] = &[0x53, 0x91, 0x2C];
for p in [CrcPoly::Crc8, CrcPoly::Crc16] {
let mut bits = to_bits(m);
let on_wire = crc_bits(p, &bits);
let r = (!on_wire) & p.mask(); for i in (0..p.width()).rev() {
bits.push((r >> i) & 1 != 0);
}
assert_eq!(reference_remainder(p, &bits), 0, "poly {p:?}");
}
}
#[test]
fn stream_mux_config_crc_is_crc8() {
let bits = to_bits(&[0x00, 0x10, 0x07, 0x00]);
assert_eq!(
stream_mux_config_crc(&bits) as u64,
crc_bits(CrcPoly::Crc8, &bits)
);
}
}