const STRIDE: usize = 16;
static CRC32_TABLES: [[u32; 256]; STRIDE] = generate_crc32_tables();
const fn generate_crc32_tables() -> [[u32; 256]; STRIDE] {
let mut tables = [[0u32; 256]; STRIDE];
let mut index = 0;
while index < 256 {
let mut value = index as u32;
let mut bit = 0;
while bit < 8 {
value = if value & 1 == 1 {
(value >> 1) ^ 0xEDB8_8320
} else {
value >> 1
};
bit += 1;
}
tables[0][index] = value;
index += 1;
}
let mut lane = 1;
while lane < STRIDE {
let mut index = 0;
while index < 256 {
let previous = tables[lane - 1][index];
tables[lane][index] = (previous >> 8) ^ tables[0][(previous & 0xFF) as usize];
index += 1;
}
lane += 1;
}
tables
}
#[must_use]
pub fn crc32(bytes: &[u8]) -> u32 {
let mut running = Crc32::new();
running.update(bytes);
running.finish()
}
#[derive(Clone, Copy, Debug)]
pub struct Crc32 {
state: u32,
}
impl Default for Crc32 {
fn default() -> Self {
Self::new()
}
}
impl Crc32 {
#[must_use]
pub const fn new() -> Self {
Self { state: 0xFFFF_FFFF }
}
pub fn update(&mut self, bytes: &[u8]) {
self.state = fold(self.state, bytes);
}
#[must_use]
pub const fn finish(self) -> u32 {
!self.state
}
}
fn fold(mut checksum: u32, bytes: &[u8]) -> u32 {
let (blocks, remainder) = bytes.as_chunks::<STRIDE>();
for block in blocks {
checksum ^= u32::from_le_bytes([block[0], block[1], block[2], block[3]]);
checksum = CRC32_TABLES[15][(checksum & 0xFF) as usize]
^ CRC32_TABLES[14][((checksum >> 8) & 0xFF) as usize]
^ CRC32_TABLES[13][((checksum >> 16) & 0xFF) as usize]
^ CRC32_TABLES[12][(checksum >> 24) as usize]
^ CRC32_TABLES[11][block[4] as usize]
^ CRC32_TABLES[10][block[5] as usize]
^ CRC32_TABLES[9][block[6] as usize]
^ CRC32_TABLES[8][block[7] as usize]
^ CRC32_TABLES[7][block[8] as usize]
^ CRC32_TABLES[6][block[9] as usize]
^ CRC32_TABLES[5][block[10] as usize]
^ CRC32_TABLES[4][block[11] as usize]
^ CRC32_TABLES[3][block[12] as usize]
^ CRC32_TABLES[2][block[13] as usize]
^ CRC32_TABLES[1][block[14] as usize]
^ CRC32_TABLES[0][block[15] as usize];
}
for &byte in remainder {
let table_index = ((checksum ^ u32::from(byte)) & 0xFF) as usize;
checksum = (checksum >> 8) ^ CRC32_TABLES[0][table_index];
}
checksum
}
#[cfg(test)]
mod tests {
use super::{Crc32, STRIDE, crc32};
fn crc32_bit_at_a_time(bytes: &[u8]) -> u32 {
let mut checksum = 0xFFFF_FFFFu32;
for &byte in bytes {
checksum ^= u32::from(byte);
for _ in 0..8 {
checksum = if checksum & 1 == 1 {
(checksum >> 1) ^ 0xEDB8_8320
} else {
checksum >> 1
};
}
}
!checksum
}
#[test]
fn empty_input_produces_zero() {
assert_eq!(crc32(&[]), 0);
}
#[test]
fn known_test_vectors_match() {
assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
assert_eq!(
crc32(b"The quick brown fox jumps over the lazy dog"),
0x414F_A339
);
}
#[test]
fn single_byte_matches_reference_value() {
assert_eq!(crc32(&[0]), 0xD202_EF8D);
}
#[test]
fn chunked_updates_agree_with_the_one_shot_at_every_chunk_width() {
let bytes: Vec<u8> = (0..=200u16).map(|index| (index % 251) as u8).collect();
let expected = crc32_bit_at_a_time(&bytes);
assert_eq!(crc32(&bytes), expected);
for chunk_width in 1..=(2 * STRIDE + 3) {
let mut running = Crc32::new();
for chunk in bytes.chunks(chunk_width) {
running.update(chunk);
}
assert_eq!(
running.finish(),
expected,
"folding in {chunk_width}-byte chunks must equal the one-shot checksum"
);
}
let mut running = Crc32::new();
running.update(&[]);
running.update(&bytes[..7]);
running.update(&[]);
running.update(&bytes[7..]);
assert_eq!(running.finish(), expected);
assert_eq!(Crc32::new().finish(), crc32(&[]));
}
#[test]
fn every_length_matches_the_independent_bitwise_definition() {
let data: Vec<u8> = (0..=255u8).cycle().take(STRIDE * 4 + 3).collect();
for length in 0..data.len() {
let sliced = crc32(&data[..length]);
let bitwise = crc32_bit_at_a_time(&data[..length]);
assert_eq!(
sliced, bitwise,
"slice-by-{STRIDE} and the bitwise definition disagree at length {length}"
);
}
}
#[test]
fn high_bytes_and_repeated_patterns_match_the_bitwise_definition() {
for pattern in [
vec![0x00; 129],
vec![0xFF; 129],
vec![0xA5, 0x5A].into_iter().cycle().take(129).collect(),
] {
assert_eq!(crc32(&pattern), crc32_bit_at_a_time(&pattern));
}
}
}