const POLYNOMIAL: u32 = 0xEDB8_8320;
const TABLE: [u32; 256] = {
let mut table = [0u32; 256];
let mut i = 0;
while i < 256 {
let mut crc = i as u32;
let mut bit = 0;
while bit < 8 {
crc = if crc & 1 != 0 {
POLYNOMIAL ^ (crc >> 1)
} else {
crc >> 1
};
bit += 1;
}
table[i] = crc;
i += 1;
}
table
};
pub(crate) struct Crc32(u32);
impl Crc32 {
pub(crate) fn new() -> Self {
Self(0xFFFF_FFFF)
}
pub(crate) fn update(&mut self, bytes: &[u8]) {
for &byte in bytes {
let index = ((self.0 ^ byte as u32) & 0xFF) as usize;
self.0 = TABLE[index] ^ (self.0 >> 8);
}
}
pub(crate) fn finish(self) -> u32 {
self.0 ^ 0xFFFF_FFFF
}
}
impl Default for Crc32 {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_standard_check_vector_matches() {
let mut crc = Crc32::new();
crc.update(b"123456789");
assert_eq!(crc.finish(), 0xCBF4_3926);
}
#[test]
fn splitting_the_input_does_not_change_the_result() {
let mut whole = Crc32::new();
whole.update(b"123456789");
let mut split = Crc32::new();
split.update(b"1234");
split.update(b"");
split.update(b"56789");
assert_eq!(whole.finish(), split.finish());
}
#[test]
fn every_single_bit_flip_is_detected() {
let clean: Vec<u8> = (0u8..64).collect();
let mut baseline = Crc32::new();
baseline.update(&clean);
let baseline = baseline.finish();
for byte in 0..clean.len() {
for bit in 0..8 {
let mut damaged = clean.clone();
damaged[byte] ^= 1 << bit;
let mut crc = Crc32::new();
crc.update(&damaged);
assert_ne!(
crc.finish(),
baseline,
"flipping bit {bit} of byte {byte} went unnoticed"
);
}
}
}
#[test]
fn zeroed_bytes_do_not_hash_to_zero() {
assert_eq!(
Crc32::new().finish(),
0,
"the empty input is 0 by definition"
);
let mut crc = Crc32::new();
crc.update(&[0u8; 4]);
assert_ne!(crc.finish(), 0, "four zero bytes must not hash to zero");
}
}