const fn gen_crc_table() -> [[u32; 256]; 16] {
let mut table: [[u32; 256]; 16] = [[0; 256]; 16];
let poly = 0xEDB88320;
let mut i = 0;
while i < 256 {
let mut crc = i as u32;
let mut j = 0;
while j < 8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
j += 1;
}
table[0][i] = crc;
i += 1;
}
i = 1;
while i < 16 {
let mut j = 0;
while j < 256 {
table[i][j] = (table[i - 1][j] >> 8) ^ table[0][(table[i - 1][j] & 0xFF) as usize];
j += 1;
}
i += 1;
}
table
}
static CRC_TABLE: [[u32; 256]; 16] = gen_crc_table();
#[inline]
pub(crate) fn crc32_byte(crc: u32, byte: u8) -> u32 {
(crc >> 8) ^ CRC_TABLE[0][((crc ^ u32::from(byte)) & 0xFF) as usize]
}
pub fn crc32(data: &[u8]) -> u32 {
!crc32_fold(data, !0)
}
#[derive(Debug, Clone)]
pub struct Crc32 {
state: u32,
}
impl Crc32 {
#[inline]
pub fn new() -> Self {
Crc32 { state: !0 }
}
#[inline]
pub fn update(&mut self, data: &[u8]) {
self.state = crc32_fold(data, self.state);
}
#[inline]
pub fn checksum(&self) -> u32 {
!self.state
}
}
impl Default for Crc32 {
fn default() -> Self {
Self::new()
}
}
#[inline(always)]
fn crc32_slice16(data: &[u8], crc: u32) -> u32 {
let w0 = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) ^ crc;
CRC_TABLE[0x0][data[0xf] as usize]
^ CRC_TABLE[0x1][data[0xe] as usize]
^ CRC_TABLE[0x2][data[0xd] as usize]
^ CRC_TABLE[0x3][data[0xc] as usize]
^ CRC_TABLE[0x4][data[0xb] as usize]
^ CRC_TABLE[0x5][data[0xa] as usize]
^ CRC_TABLE[0x6][data[0x9] as usize]
^ CRC_TABLE[0x7][data[0x8] as usize]
^ CRC_TABLE[0x8][data[0x7] as usize]
^ CRC_TABLE[0x9][data[0x6] as usize]
^ CRC_TABLE[0xa][data[0x5] as usize]
^ CRC_TABLE[0xb][data[0x4] as usize]
^ CRC_TABLE[0xc][(w0 >> 24) as usize]
^ CRC_TABLE[0xd][((w0 >> 16) & 0xFF) as usize]
^ CRC_TABLE[0xe][((w0 >> 8) & 0xFF) as usize]
^ CRC_TABLE[0xf][(w0 & 0xFF) as usize]
}
#[inline]
fn crc32_fold(data: &[u8], state: u32) -> u32 {
let mut chunks32 = data.chunks_exact(32);
let mut crc = chunks32.by_ref().fold(state, |crc, data| {
let crc = crc32_slice16(data, crc);
crc32_slice16(&data[16..], crc)
});
crc = chunks32
.remainder()
.iter()
.fold(crc, |crc, &x| crc32_byte(crc, x));
crc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crc() {
let table = gen_crc_table();
assert_eq!(table[0][0], 0x0000_0000);
assert_eq!(table[0][1], 0x77073096);
assert_eq!(table[0][2], 0xee0e612c);
assert_eq!(table[1][1], 0x191B3141);
assert_eq!(table[1][2], 0x32366282);
let abc = b"EU4txt\nchecksum=\"ced5411e2d4a5ec724595c2c4f1b7347\"";
assert_eq!(crc32(abc), 1702863696);
}
fn reference_crc32(data: &[u8]) -> u32 {
let mut crc = !0u32;
for &b in data {
crc = (crc >> 8) ^ CRC_TABLE[0][((crc ^ u32::from(b)) & 0xFF) as usize];
}
!crc
}
#[test]
fn test_crc_sizes() {
let data: Vec<u8> = (0u8..=255).cycle().take(65536).collect();
for &size in &[
0, 1, 4, 15, 16, 17, 31, 32, 33, 64, 256, 1024, 4096, 16384, 65536,
] {
let slice = &data[..size];
assert_eq!(
crc32(slice),
reference_crc32(slice),
"mismatch at size {size}"
);
}
}
#[test]
fn test_crc32_accumulator_streaming() {
let data: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
let full = crc32(&data);
let mut streamed = Crc32::new();
streamed.update(&data[..2048]);
streamed.update(&data[2048..]);
assert_eq!(full, streamed.checksum());
}
}