const POLYNOMIAL: u32 = 0xEDB8_8320;
const CRC32_TABLE: [u32; 256] = generate_table();
const fn generate_table() -> [u32; 256] {
let mut table = [0u32; 256];
let mut i = 0;
while i < 256 {
let mut crc = i as u32;
let mut j = 0;
while j < 8 {
if crc & 1 == 1 {
crc = (crc >> 1) ^ POLYNOMIAL;
} else {
crc >>= 1;
}
j += 1;
}
table[i] = crc;
i += 1;
}
table
}
pub fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFF; for &byte in data {
let index = ((crc ^ u32::from(byte)) & 0xFF) as usize;
crc = (crc >> 8) ^ CRC32_TABLE[index];
}
crc ^ 0xFFFF_FFFF }
#[derive(Debug, Clone)]
pub struct Crc32 {
state: u32,
}
impl Crc32 {
#[must_use]
pub fn new() -> Self {
Self { state: 0xFFFF_FFFF }
}
pub fn update(&mut self, data: &[u8]) {
for &byte in data {
let index = ((self.state ^ u32::from(byte)) & 0xFF) as usize;
self.state = (self.state >> 8) ^ CRC32_TABLE[index];
}
}
#[must_use]
pub fn finalize(self) -> u32 {
self.state ^ 0xFFFF_FFFF
}
}
impl Default for Crc32 {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crc32_empty() {
assert_eq!(crc32(b""), 0x0000_0000);
}
#[test]
fn test_crc32_known_vectors() {
assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
assert_eq!(
crc32(b"The quick brown fox jumps over the lazy dog"),
0x414F_A339
);
assert_eq!(crc32(b"a"), 0xE8B7_BE43);
}
#[test]
fn test_incremental_matches_oneshot() {
let data = b"hello world this is a test";
let mut hasher = Crc32::new();
hasher.update(data);
let incremental = hasher.finalize();
let oneshot = crc32(data);
assert_eq!(incremental, oneshot);
}
#[test]
fn test_chunking_invariant() {
let data = b"The quick brown fox jumps over the lazy dog";
for split in 0..data.len() {
let mut hasher = Crc32::new();
hasher.update(&data[..split]);
hasher.update(&data[split..]);
assert_eq!(hasher.finalize(), crc32(data));
}
}
#[test]
fn test_multiple_chunks() {
let mut hasher = Crc32::new();
hasher.update(b"hello ");
hasher.update(b"world ");
hasher.update(b"from ");
hasher.update(b"rust");
assert_eq!(hasher.finalize(), crc32(b"hello world from rust"));
}
#[cfg(test)]
#[test]
fn proptest_incremental_matches_oneshot() {
use proptest::prelude::*;
proptest!(|(data: Vec<u8>)| {
let mut hasher = Crc32::new();
hasher.update(&data);
prop_assert_eq!(hasher.finalize(), crc32(&data));
});
}
#[cfg(test)]
#[test]
fn proptest_chunking_invariant() {
use proptest::prelude::*;
proptest!(|(data: Vec<u8>, split: usize)| {
if data.is_empty() {
return Ok(());
}
let split = split % data.len();
let mut hasher = Crc32::new();
hasher.update(&data[..split]);
hasher.update(&data[split..]);
prop_assert_eq!(hasher.finalize(), crc32(&data));
});
}
}