1use md5::{Digest, Md5};
10
11use crate::types::{Par2File, SliceChecksum};
12
13pub struct SliceHasher {
25 md5: Md5,
26 crc: crc32fast::Hasher,
27 slice_size: u64,
28 len: u64,
29}
30
31impl SliceHasher {
32 pub fn new(slice_size: u64) -> Self {
34 Self {
35 md5: Md5::new(),
36 crc: crc32fast::Hasher::new(),
37 slice_size,
38 len: 0,
39 }
40 }
41
42 pub fn update(&mut self, data: &[u8]) {
47 self.md5.update(data);
48 self.crc.update(data);
49 self.len += data.len() as u64;
50 }
51
52 pub fn len(&self) -> u64 {
54 self.len
55 }
56
57 pub fn is_empty(&self) -> bool {
59 self.len == 0
60 }
61
62 pub fn finish(mut self) -> SliceChecksum {
64 const ZEROS: [u8; 4096] = [0; 4096];
65 let mut pad = self.slice_size.saturating_sub(self.len);
66 while pad > 0 {
67 let n = pad.min(ZEROS.len() as u64) as usize;
68 self.md5.update(&ZEROS[..n]);
69 self.crc.update(&ZEROS[..n]);
70 pad -= n as u64;
71 }
72 SliceChecksum {
73 md5: self.md5.finalize().into(),
74 crc32: self.crc.finalize(),
75 }
76 }
77
78 pub fn checksum(data: &[u8], slice_size: u64) -> SliceChecksum {
80 let mut h = Self::new(slice_size);
81 h.update(data);
82 h.finish()
83 }
84}
85
86impl Par2File {
87 pub fn slice_matches(&self, local_index: usize, data: &[u8], slice_size: u64) -> bool {
94 let Some(expected) = self.slices.get(local_index) else {
95 return false;
96 };
97 let offset = local_index as u64 * slice_size;
98 let expected_len = self.size.saturating_sub(offset).min(slice_size);
99 if data.len() as u64 != expected_len {
100 return false;
101 }
102 SliceHasher::checksum(data, slice_size) == *expected
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn padding_matches_explicit_zeroes() {
112 let data = vec![0xABu8; 10_000];
113 let mut padded = data.clone();
114 padded.resize(16_384, 0);
115 let a = SliceHasher::checksum(&data, 16_384);
116 let b = SliceHasher::checksum(&padded, 16_384);
117 assert_eq!(a, b);
118 assert_eq!(a.crc32, crc32fast::hash(&padded));
119 let md5: [u8; 16] = Md5::digest(&padded).into();
120 assert_eq!(a.md5, md5);
121 }
122
123 #[test]
124 fn incremental_matches_one_shot() {
125 let data: Vec<u8> = (0..5000u32).map(|i| (i * 7) as u8).collect();
126 let mut h = SliceHasher::new(8192);
127 for chunk in data.chunks(333) {
128 h.update(chunk);
129 }
130 assert_eq!(h.len(), 5000);
131 assert_eq!(h.finish(), SliceHasher::checksum(&data, 8192));
132 }
133}