1use crc32fast::Hasher;
27use std::io::{self, Write};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub enum ChecksumType {
32 #[default]
34 CRC32C,
35 None,
37}
38
39pub struct Checksum;
41
42impl Checksum {
43 pub fn compute(checksum_type: ChecksumType, data: &[u8]) -> u32 {
57 match checksum_type {
58 ChecksumType::CRC32C => {
59 let mut hasher = Hasher::new();
60 hasher.update(data);
61 hasher.finalize()
62 }
63 ChecksumType::None => 0,
64 }
65 }
66
67 pub fn verify(
83 checksum_type: ChecksumType,
84 data: &[u8],
85 expected: u32,
86 ) -> Result<(), ChecksumError> {
87 if checksum_type == ChecksumType::None {
88 return Ok(());
89 }
90
91 let actual = Self::compute(checksum_type, data);
92 if actual != expected {
93 return Err(ChecksumError::Mismatch {
94 expected,
95 actual,
96 data_len: data.len(),
97 });
98 }
99
100 Ok(())
101 }
102
103 pub fn builder(checksum_type: ChecksumType) -> ChecksumBuilder {
113 ChecksumBuilder::new(checksum_type)
114 }
115
116 pub fn encode_with_checksum(checksum_type: ChecksumType, data: &[u8]) -> Vec<u8> {
125 let checksum = Self::compute(checksum_type, data);
126 let mut encoded = Vec::with_capacity(4 + data.len() + 4);
127
128 encoded.extend_from_slice(&(data.len() as u32).to_le_bytes());
130
131 encoded.extend_from_slice(data);
133
134 encoded.extend_from_slice(&checksum.to_le_bytes());
136
137 encoded
138 }
139
140 pub fn decode_with_checksum(
151 checksum_type: ChecksumType,
152 encoded: &[u8],
153 ) -> Result<Vec<u8>, ChecksumError> {
154 if encoded.len() < 8 {
155 return Err(ChecksumError::InvalidFormat("Data too short".to_string()));
156 }
157
158 let data_len =
160 u32::from_le_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]) as usize;
161
162 if encoded.len() != 4 + data_len + 4 {
163 return Err(ChecksumError::InvalidFormat(format!(
164 "Expected {} bytes, got {}",
165 4 + data_len + 4,
166 encoded.len()
167 )));
168 }
169
170 let data = &encoded[4..4 + data_len];
172 let expected_checksum = u32::from_le_bytes([
173 encoded[4 + data_len],
174 encoded[4 + data_len + 1],
175 encoded[4 + data_len + 2],
176 encoded[4 + data_len + 3],
177 ]);
178
179 Self::verify(checksum_type, data, expected_checksum)?;
181
182 Ok(data.to_vec())
183 }
184}
185
186pub struct ChecksumBuilder {
188 hasher: Option<Hasher>,
189}
190
191impl ChecksumBuilder {
192 pub fn new(checksum_type: ChecksumType) -> Self {
194 let hasher = match checksum_type {
195 ChecksumType::CRC32C => Some(Hasher::new()),
196 ChecksumType::None => None,
197 };
198
199 Self { hasher }
200 }
201
202 pub fn update(&mut self, data: &[u8]) {
204 if let Some(hasher) = &mut self.hasher {
205 hasher.update(data);
206 }
207 }
208
209 pub fn finalize(self) -> u32 {
211 match self.hasher {
212 Some(hasher) => hasher.finalize(),
213 None => 0,
214 }
215 }
216}
217
218impl Write for ChecksumBuilder {
219 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
220 self.update(buf);
221 Ok(buf.len())
222 }
223
224 fn flush(&mut self) -> io::Result<()> {
225 Ok(())
226 }
227}
228
229#[derive(Debug, thiserror::Error)]
231pub enum ChecksumError {
232 #[error(
233 "Checksum mismatch: expected {expected:#010x}, got {actual:#010x} (data_len={data_len})"
234 )]
235 Mismatch {
236 expected: u32,
237 actual: u32,
238 data_len: usize,
239 },
240
241 #[error("Invalid checksum format: {0}")]
242 InvalidFormat(String),
243
244 #[error("IO error: {0}")]
245 Io(#[from] std::io::Error),
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_checksum_basic() {
254 let data = b"Hello, MoteDB!";
255 let checksum = Checksum::compute(ChecksumType::CRC32C, data);
256
257 assert!(Checksum::verify(ChecksumType::CRC32C, data, checksum).is_ok());
259
260 assert!(Checksum::verify(ChecksumType::CRC32C, data, checksum + 1).is_err());
262
263 let corrupted = b"Hello, MoteDB?";
265 assert!(Checksum::verify(ChecksumType::CRC32C, corrupted, checksum).is_err());
266 }
267
268 #[test]
269 fn test_checksum_none() {
270 let data = b"Hello, MoteDB!";
271 let checksum = Checksum::compute(ChecksumType::None, data);
272 assert_eq!(checksum, 0);
273
274 assert!(Checksum::verify(ChecksumType::None, data, 12345).is_ok());
276 }
277
278 #[test]
279 fn test_checksum_builder() {
280 let data1 = b"Hello, ";
281 let data2 = b"MoteDB!";
282
283 let mut builder = Checksum::builder(ChecksumType::CRC32C);
285 builder.update(data1);
286 builder.update(data2);
287 let checksum1 = builder.finalize();
288
289 let checksum2 = Checksum::compute(ChecksumType::CRC32C, b"Hello, MoteDB!");
291
292 assert_eq!(checksum1, checksum2);
294 }
295
296 #[test]
297 fn test_checksum_encode_decode() {
298 let data = b"Hello, MoteDB! This is a test message.";
299
300 let encoded = Checksum::encode_with_checksum(ChecksumType::CRC32C, data);
302
303 let decoded = Checksum::decode_with_checksum(ChecksumType::CRC32C, &encoded).unwrap();
305
306 assert_eq!(data, decoded.as_slice());
307 }
308
309 #[test]
310 fn test_checksum_decode_corrupted() {
311 let data = b"Hello, MoteDB!";
312 let mut encoded = Checksum::encode_with_checksum(ChecksumType::CRC32C, data);
313
314 encoded[10] ^= 0xFF;
316
317 let result = Checksum::decode_with_checksum(ChecksumType::CRC32C, &encoded);
319 assert!(result.is_err());
320 assert!(matches!(
321 result.unwrap_err(),
322 ChecksumError::Mismatch { .. }
323 ));
324 }
325
326 #[test]
327 fn test_checksum_decode_invalid_format() {
328 let short_data = b"abc";
330 let result = Checksum::decode_with_checksum(ChecksumType::CRC32C, short_data);
331 assert!(result.is_err());
332
333 let mut invalid = vec![0u8; 20];
335 invalid[0] = 100; let result = Checksum::decode_with_checksum(ChecksumType::CRC32C, &invalid);
337 assert!(result.is_err());
338 }
339
340 #[test]
341 fn test_checksum_deterministic() {
342 let data = b"Deterministic test";
343
344 let checksum1 = Checksum::compute(ChecksumType::CRC32C, data);
346 let checksum2 = Checksum::compute(ChecksumType::CRC32C, data);
347 let checksum3 = Checksum::compute(ChecksumType::CRC32C, data);
348
349 assert_eq!(checksum1, checksum2);
350 assert_eq!(checksum2, checksum3);
351 }
352
353 #[test]
354 fn test_checksum_empty_data() {
355 let data = b"";
356 let checksum = Checksum::compute(ChecksumType::CRC32C, data);
357
358 assert_eq!(checksum, 0);
360 assert!(Checksum::verify(ChecksumType::CRC32C, data, checksum).is_ok());
361 }
362
363 #[test]
364 fn test_checksum_builder_write_trait() {
365 use std::io::Write;
366
367 let mut builder = Checksum::builder(ChecksumType::CRC32C);
368
369 builder.write_all(b"Hello, ").unwrap();
371 builder.write_all(b"MoteDB!").unwrap();
372 builder.flush().unwrap();
373
374 let checksum = builder.finalize();
375
376 let expected = Checksum::compute(ChecksumType::CRC32C, b"Hello, MoteDB!");
378 assert_eq!(checksum, expected);
379 }
380}