macro_rules! crc_family {
($algo:ident, $engine:ident, $int:ty, $width:expr, $reflect:ident, $table:ident, $doc_algo:literal, $doc_eng:literal) => {
const fn $reflect(mut v: $int) -> $int {
let mut r: $int = 0;
let mut i = 0;
while i < $width {
r = (r << 1) | (v & 1);
v >>= 1;
i += 1;
}
r
}
const fn $table(poly: $int, reflected: bool) -> [$int; 256] {
let mut table = [0 as $int; 256];
let mut i = 0usize;
while i < 256 {
let mut c: $int;
if reflected {
let rpoly = $reflect(poly);
c = i as $int;
let mut k = 0;
while k < 8 {
c = if c & 1 != 0 { (c >> 1) ^ rpoly } else { c >> 1 };
k += 1;
}
} else {
c = (i as $int) << ($width - 8);
let msb: $int = 1 << ($width - 1);
let mut k = 0;
while k < 8 {
c = if c & msb != 0 { (c << 1) ^ poly } else { c << 1 };
k += 1;
}
}
table[i] = c;
i += 1;
}
table
}
#[doc = $doc_algo]
pub struct $algo {
/// Generator polynomial (Rocksoft normal form, MSB-first).
pub poly: $int,
pub init: $int,
pub refin: bool,
pub refout: bool,
pub xorout: $int,
pub check: $int,
table: [$int; 256],
}
impl $algo {
pub const fn new(poly: $int, init: $int, refin: bool, refout: bool, xorout: $int, check: $int) -> Self {
Self {
poly,
init,
refin,
refout,
xorout,
check,
table: $table(poly, refin),
}
}
}
#[doc = $doc_eng]
pub struct $engine<'a> {
alg: &'a $algo,
value: $int,
}
impl<'a> $engine<'a> {
pub fn new(alg: &'a $algo) -> Self {
let value = if alg.refin { $reflect(alg.init) } else { alg.init };
Self { alg, value }
}
pub fn update(&mut self, data: &[u8]) {
if self.alg.refin {
for &b in data {
let idx = ((self.value ^ b as $int) & 0xff) as usize;
self.value = (self.value >> 8) ^ self.alg.table[idx];
}
} else {
for &b in data {
let idx = (((self.value >> ($width - 8)) ^ b as $int) & 0xff) as usize;
self.value = (self.value << 8) ^ self.alg.table[idx];
}
}
}
pub fn finalize(self) -> $int {
let v = if self.alg.refin != self.alg.refout {
$reflect(self.value)
} else {
self.value
};
v ^ self.alg.xorout
}
pub fn checksum(alg: &$algo, data: &[u8]) -> $int {
let mut c = $engine::new(alg);
c.update(data);
c.finalize()
}
}
};
}
crc_family!(
Crc16Algorithm,
Crc16,
u16,
16,
reflect16,
crc16_table,
"A CRC-16 variant (16-bit generator, Rocksoft parameters).",
"A running CRC-16 checksum over a chosen [`Crc16Algorithm`]."
);
crc_family!(
Crc32Algorithm,
Crc32,
u32,
32,
reflect32,
crc32_table,
"A CRC-32 variant (32-bit generator, Rocksoft parameters).",
"A running CRC-32 checksum over a chosen [`Crc32Algorithm`]."
);
pub const CRC32_ISO_HDLC: Crc32Algorithm =
Crc32Algorithm::new(0x04C1_1DB7, 0xFFFF_FFFF, true, true, 0xFFFF_FFFF, 0xCBF4_3926);
pub const CRC32_ISCSI: Crc32Algorithm =
Crc32Algorithm::new(0x1EDC_6F41, 0xFFFF_FFFF, true, true, 0xFFFF_FFFF, 0xE306_9283);
pub const CRC32_BZIP2: Crc32Algorithm =
Crc32Algorithm::new(0x04C1_1DB7, 0xFFFF_FFFF, false, false, 0xFFFF_FFFF, 0xFC89_1918);
pub const CRC32_MPEG_2: Crc32Algorithm =
Crc32Algorithm::new(0x04C1_1DB7, 0xFFFF_FFFF, false, false, 0x0000_0000, 0x0376_E6E7);
pub const CRC32_CKSUM: Crc32Algorithm =
Crc32Algorithm::new(0x04C1_1DB7, 0x0000_0000, false, false, 0xFFFF_FFFF, 0x765E_7680);
pub const CRC16_ARC: Crc16Algorithm = Crc16Algorithm::new(0x8005, 0x0000, true, true, 0x0000, 0xBB3D);
pub const CRC16_MODBUS: Crc16Algorithm = Crc16Algorithm::new(0x8005, 0xFFFF, true, true, 0x0000, 0x4B37);
pub const CRC16_USB: Crc16Algorithm = Crc16Algorithm::new(0x8005, 0xFFFF, true, true, 0xFFFF, 0xB4C8);
pub const CRC16_CCITT_FALSE: Crc16Algorithm = Crc16Algorithm::new(0x1021, 0xFFFF, false, false, 0x0000, 0x29B1);
pub const CRC16_XMODEM: Crc16Algorithm = Crc16Algorithm::new(0x1021, 0x0000, false, false, 0x0000, 0x31C3);
pub const CRC16_KERMIT: Crc16Algorithm = Crc16Algorithm::new(0x1021, 0x0000, true, true, 0x0000, 0x2189);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn named_variants_self_check() {
macro_rules! check32 {
($($alg:ident),+ $(,)?) => {$(
assert_eq!(Crc32::checksum(&$alg, b"123456789"), $alg.check, stringify!($alg));
)+};
}
macro_rules! check16 {
($($alg:ident),+ $(,)?) => {$(
assert_eq!(Crc16::checksum(&$alg, b"123456789"), $alg.check, stringify!($alg));
)+};
}
check32!(CRC32_ISO_HDLC, CRC32_ISCSI, CRC32_BZIP2, CRC32_MPEG_2, CRC32_CKSUM);
check16!(
CRC16_ARC,
CRC16_MODBUS,
CRC16_USB,
CRC16_CCITT_FALSE,
CRC16_XMODEM,
CRC16_KERMIT
);
}
#[test]
fn streaming_matches_one_shot() {
let msg = b"123456789";
let mut c = Crc32::new(&CRC32_ISO_HDLC);
c.update(&msg[..4]);
c.update(&msg[4..]);
assert_eq!(c.finalize(), Crc32::checksum(&CRC32_ISO_HDLC, msg));
let mut c = Crc16::new(&CRC16_CCITT_FALSE);
for &b in msg {
c.update(&[b]);
}
assert_eq!(c.finalize(), Crc16::checksum(&CRC16_CCITT_FALSE, msg));
}
}