#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrcPoly {
Crc8,
#[cfg(test)]
Crc4,
#[cfg(test)]
Crc5,
#[cfg(test)]
Crc6,
#[cfg(test)]
Crc7,
#[cfg(test)]
Crc9,
#[cfg(test)]
Crc10,
#[cfg(test)]
Crc11,
#[cfg(test)]
Crc12,
#[cfg(test)]
Crc13,
#[cfg(test)]
Crc14,
#[cfg(test)]
Crc15,
#[cfg(test)]
Crc16,
#[cfg(test)]
Crc24,
#[cfg(test)]
Crc32,
}
impl CrcPoly {
pub const fn width(self) -> u32 {
match self {
CrcPoly::Crc8 => 8,
#[cfg(test)]
CrcPoly::Crc4 => 4,
#[cfg(test)]
CrcPoly::Crc5 => 5,
#[cfg(test)]
CrcPoly::Crc6 => 6,
#[cfg(test)]
CrcPoly::Crc7 => 7,
#[cfg(test)]
CrcPoly::Crc9 => 9,
#[cfg(test)]
CrcPoly::Crc10 => 10,
#[cfg(test)]
CrcPoly::Crc11 => 11,
#[cfg(test)]
CrcPoly::Crc12 => 12,
#[cfg(test)]
CrcPoly::Crc13 => 13,
#[cfg(test)]
CrcPoly::Crc14 => 14,
#[cfg(test)]
CrcPoly::Crc15 => 15,
#[cfg(test)]
CrcPoly::Crc16 => 16,
#[cfg(test)]
CrcPoly::Crc24 => 24,
#[cfg(test)]
CrcPoly::Crc32 => 32,
}
}
pub const fn generator(self) -> u64 {
match self {
CrcPoly::Crc8 => bits(&[4, 3, 2, 0]),
#[cfg(test)]
CrcPoly::Crc4 => bits(&[3, 2, 0]),
#[cfg(test)]
CrcPoly::Crc5 => bits(&[4, 2, 0]),
#[cfg(test)]
CrcPoly::Crc6 => bits(&[5, 4, 2, 1, 0]),
#[cfg(test)]
CrcPoly::Crc7 => bits(&[3, 1, 0]),
#[cfg(test)]
CrcPoly::Crc9 => bits(&[4, 3, 2, 1, 0]),
#[cfg(test)]
CrcPoly::Crc10 => bits(&[9, 5, 4, 1, 0]),
#[cfg(test)]
CrcPoly::Crc11 => bits(&[10, 9, 5, 1, 0]),
#[cfg(test)]
CrcPoly::Crc12 => bits(&[11, 3, 2, 1, 0]),
#[cfg(test)]
CrcPoly::Crc13 => bits(&[12, 11, 8, 7, 4, 2, 0]),
#[cfg(test)]
CrcPoly::Crc14 => bits(&[13, 10, 5, 3, 1, 0]),
#[cfg(test)]
CrcPoly::Crc15 => bits(&[14, 13, 10, 8, 5, 2, 1, 0]),
#[cfg(test)]
CrcPoly::Crc16 => bits(&[15, 2, 0]),
#[cfg(test)]
CrcPoly::Crc24 => bits(&[23, 6, 5, 1, 0]),
#[cfg(test)]
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 poly_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 ^= poly_gen;
}
if bit {
reg ^= 1; }
}
for _ in 0..k {
let high = (reg & top) != 0;
reg = (reg << 1) & mask;
if high {
reg ^= poly_gen;
}
}
(!reg) & mask
}
#[cfg(test)]
pub fn crc_bytes(poly: CrcPoly, message: &[u8]) -> u64 {
let k = poly.width();
let poly_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 ^= poly_gen;
}
if bit {
reg ^= 1;
}
}
}
for _ in 0..k {
let high = (reg & top) != 0;
reg = (reg << 1) & mask;
if high {
reg ^= poly_gen;
}
}
(!reg) & mask
}
pub fn stream_mux_config_crc(config_bits: &[bool]) -> u8 {
crc_bits(CrcPoly::Crc8, config_bits) as u8
}
pub fn mpeg1_crc16(message_bits: &[bool]) -> u16 {
let mut crc = 0xFFFFu16;
for &bit in message_bits {
mpeg1_crc16_update(&mut crc, bit);
}
crc
}
pub fn mpeg1_crc16_update(crc: &mut u16, bit: bool) {
let inb = u16::from(bit);
let msb = *crc >> 15;
*crc <<= 1;
if (msb ^ inb) != 0 {
*crc ^= 0x8005;
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
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_n(false, 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)
);
}
fn mpeg1_reference(message_bits: &[bool]) -> u16 {
let full_gen = 0x18005u32;
let mut reg = 0xFFFFu32;
for &bit in message_bits {
let inb = u32::from(bit);
let top = (reg >> 15) & 1;
reg = ((reg << 1) & 0xFFFF) ^ if top ^ inb != 0 { full_gen & 0xFFFF } else { 0 };
}
reg as u16
}
#[test]
fn mpeg1_crc16_is_11172_3_not_mpeg4_ep() {
assert_eq!(mpeg1_crc16(&[]), 0xFFFF);
let bits = to_bits(&[0x12, 0x34, 0x56, 0x78]);
let got = mpeg1_crc16(&bits);
assert_eq!(got, mpeg1_reference(&bits));
assert_ne!(got as u64, crc_bits(CrcPoly::Crc16, &bits));
}
}