#![allow(dead_code)]
const CRC32_POLY: u32 = 0xEDB8_8320;
const TABLE_SIZE: usize = 256;
#[derive(Debug, Clone)]
pub struct Crc32Table {
table: [u32; TABLE_SIZE],
}
impl Crc32Table {
pub fn new() -> Self {
let mut table = [0u32; TABLE_SIZE];
for i in 0..TABLE_SIZE {
let mut crc = i as u32;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ CRC32_POLY;
} else {
crc >>= 1;
}
}
table[i] = crc;
}
Self { table }
}
pub fn lookup(&self, index: u8) -> u32 {
self.table[index as usize]
}
}
impl Default for Crc32Table {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Crc32Stream {
state: u32,
table: Crc32Table,
bytes_processed: u64,
}
impl Crc32Stream {
pub fn new() -> Self {
Self {
state: 0xFFFF_FFFF,
table: Crc32Table::new(),
bytes_processed: 0,
}
}
pub fn update(&mut self, data: &[u8]) {
for &byte in data {
let index = (self.state ^ u32::from(byte)) & 0xFF;
self.state = (self.state >> 8) ^ self.table.lookup(index as u8);
}
self.bytes_processed += data.len() as u64;
}
pub fn value(&self) -> u32 {
self.state ^ 0xFFFF_FFFF
}
pub fn bytes_processed(&self) -> u64 {
self.bytes_processed
}
pub fn reset(&mut self) {
self.state = 0xFFFF_FFFF;
self.bytes_processed = 0;
}
pub fn compute(data: &[u8]) -> u32 {
let mut stream = Self::new();
stream.update(data);
stream.value()
}
}
impl Default for Crc32Stream {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CrcVerifyResult {
Match,
Mismatch {
expected: u32,
actual: u32,
},
}
impl CrcVerifyResult {
pub fn is_match(&self) -> bool {
matches!(self, Self::Match)
}
}
pub fn verify_crc32(data: &[u8], expected: u32) -> CrcVerifyResult {
let actual = Crc32Stream::compute(data);
if actual == expected {
CrcVerifyResult::Match
} else {
CrcVerifyResult::Mismatch { expected, actual }
}
}
pub fn crc32_combine(crc_a: u32, crc_b: u32, len_b: u64) -> u32 {
let mut result = crc_a;
let table = Crc32Table::new();
for _ in 0..len_b {
let index = (result & 0xFF) as u8;
result = (result >> 8) ^ table.lookup(index);
}
result ^ crc_b
}
#[derive(Debug, Clone)]
pub struct CrcAccumulator {
segments: Vec<(u32, u64)>,
current: Crc32Stream,
}
impl CrcAccumulator {
pub fn new() -> Self {
Self {
segments: Vec::new(),
current: Crc32Stream::new(),
}
}
pub fn feed(&mut self, data: &[u8]) {
self.current.update(data);
}
pub fn finalize_segment(&mut self) {
let crc = self.current.value();
let len = self.current.bytes_processed();
if len > 0 {
self.segments.push((crc, len));
}
self.current.reset();
}
pub fn segment_count(&self) -> usize {
self.segments.len()
}
pub fn segment_crcs(&self) -> Vec<u32> {
self.segments.iter().map(|(crc, _)| *crc).collect()
}
pub fn total_bytes(&self) -> u64 {
self.segments.iter().map(|(_, len)| *len).sum()
}
}
impl Default for CrcAccumulator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crc32_empty() {
let crc = Crc32Stream::compute(b"");
assert_eq!(crc, 0);
}
#[test]
fn test_crc32_known_value() {
let crc = Crc32Stream::compute(b"123456789");
assert_eq!(crc, 0xCBF4_3926);
}
#[test]
fn test_crc32_streaming() {
let mut stream = Crc32Stream::new();
stream.update(b"1234");
stream.update(b"56789");
assert_eq!(stream.value(), 0xCBF4_3926);
assert_eq!(stream.bytes_processed(), 9);
}
#[test]
fn test_crc32_reset() {
let mut stream = Crc32Stream::new();
stream.update(b"hello");
let crc1 = stream.value();
assert_ne!(crc1, 0);
stream.reset();
assert_eq!(stream.bytes_processed(), 0);
stream.update(b"hello");
assert_eq!(stream.value(), crc1);
}
#[test]
fn test_crc32_table() {
let table = Crc32Table::new();
assert_eq!(table.lookup(0), 0);
assert_ne!(table.lookup(1), 0);
}
#[test]
fn test_verify_match() {
let data = b"test data";
let crc = Crc32Stream::compute(data);
let result = verify_crc32(data, crc);
assert!(result.is_match());
}
#[test]
fn test_verify_mismatch() {
let result = verify_crc32(b"test data", 0x12345678);
assert!(!result.is_match());
if let CrcVerifyResult::Mismatch { expected, actual } = result {
assert_eq!(expected, 0x12345678);
assert_ne!(actual, expected);
} else {
panic!("Expected mismatch");
}
}
#[test]
fn test_crc_accumulator_empty() {
let acc = CrcAccumulator::new();
assert_eq!(acc.segment_count(), 0);
assert_eq!(acc.total_bytes(), 0);
}
#[test]
fn test_crc_accumulator_segments() {
let mut acc = CrcAccumulator::new();
acc.feed(b"hello");
acc.finalize_segment();
acc.feed(b"world");
acc.finalize_segment();
assert_eq!(acc.segment_count(), 2);
assert_eq!(acc.total_bytes(), 10);
let crcs = acc.segment_crcs();
assert_eq!(crcs.len(), 2);
assert_eq!(crcs[0], Crc32Stream::compute(b"hello"));
assert_eq!(crcs[1], Crc32Stream::compute(b"world"));
}
#[test]
fn test_crc_accumulator_empty_segment_skipped() {
let mut acc = CrcAccumulator::new();
acc.finalize_segment(); assert_eq!(acc.segment_count(), 0);
}
#[test]
fn test_crc32_single_byte() {
let crc = Crc32Stream::compute(b"A");
assert_ne!(crc, 0);
assert_eq!(crc, Crc32Stream::compute(b"A"));
}
#[test]
fn test_crc32_default() {
let stream = Crc32Stream::default();
assert_eq!(stream.bytes_processed(), 0);
}
#[test]
fn test_crc_verify_result_display() {
let r = CrcVerifyResult::Match;
assert!(r.is_match());
let r2 = CrcVerifyResult::Mismatch {
expected: 1,
actual: 2,
};
assert!(!r2.is_match());
}
#[test]
fn test_crc32_combine_basic() {
let data_a = b"Hello";
let data_b = b"World";
let crc_a = Crc32Stream::compute(data_a);
let crc_b = Crc32Stream::compute(data_b);
let combined = crc32_combine(crc_a, crc_b, data_b.len() as u64);
let _ = combined;
}
}