use md5::{Digest, Md5};
use crate::types::{Par2File, SliceChecksum};
pub struct SliceHasher {
md5: Md5,
crc: crc32fast::Hasher,
slice_size: u64,
len: u64,
}
impl SliceHasher {
pub fn new(slice_size: u64) -> Self {
Self {
md5: Md5::new(),
crc: crc32fast::Hasher::new(),
slice_size,
len: 0,
}
}
pub fn update(&mut self, data: &[u8]) {
self.md5.update(data);
self.crc.update(data);
self.len += data.len() as u64;
}
pub fn len(&self) -> u64 {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn finish(mut self) -> SliceChecksum {
const ZEROS: [u8; 4096] = [0; 4096];
let mut pad = self.slice_size.saturating_sub(self.len);
while pad > 0 {
let n = pad.min(ZEROS.len() as u64) as usize;
self.md5.update(&ZEROS[..n]);
self.crc.update(&ZEROS[..n]);
pad -= n as u64;
}
SliceChecksum {
md5: self.md5.finalize().into(),
crc32: self.crc.finalize(),
}
}
pub fn checksum(data: &[u8], slice_size: u64) -> SliceChecksum {
let mut h = Self::new(slice_size);
h.update(data);
h.finish()
}
}
impl Par2File {
pub fn slice_matches(&self, local_index: usize, data: &[u8], slice_size: u64) -> bool {
let Some(expected) = self.slices.get(local_index) else {
return false;
};
let offset = local_index as u64 * slice_size;
let expected_len = self.size.saturating_sub(offset).min(slice_size);
if data.len() as u64 != expected_len {
return false;
}
SliceHasher::checksum(data, slice_size) == *expected
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn padding_matches_explicit_zeroes() {
let data = vec![0xABu8; 10_000];
let mut padded = data.clone();
padded.resize(16_384, 0);
let a = SliceHasher::checksum(&data, 16_384);
let b = SliceHasher::checksum(&padded, 16_384);
assert_eq!(a, b);
assert_eq!(a.crc32, crc32fast::hash(&padded));
let md5: [u8; 16] = Md5::digest(&padded).into();
assert_eq!(a.md5, md5);
}
#[test]
fn incremental_matches_one_shot() {
let data: Vec<u8> = (0..5000u32).map(|i| (i * 7) as u8).collect();
let mut h = SliceHasher::new(8192);
for chunk in data.chunks(333) {
h.update(chunk);
}
assert_eq!(h.len(), 5000);
assert_eq!(h.finish(), SliceHasher::checksum(&data, 8192));
}
}