1use std::collections::HashMap;
8
9pub fn fnv1a_64(data: &[u8]) -> u64 {
15 const OFFSET: u64 = 14_695_981_039_346_656_037;
16 const PRIME: u64 = 1_099_511_628_211;
17 let mut hash = OFFSET;
18 for &b in data {
19 hash ^= b as u64;
20 hash = hash.wrapping_mul(PRIME);
21 }
22 hash
23}
24
25pub fn djb2(data: &[u8]) -> u32 {
27 let mut hash: u64 = 5381;
28 for &b in data {
29 hash = hash.wrapping_mul(33).wrapping_add(b as u64);
30 }
31 (hash & 0xFFFF_FFFF) as u32
32}
33
34pub fn murmur3_32(data: &[u8]) -> u32 {
36 const C1: u32 = 0xcc9e_2d51;
37 const C2: u32 = 0x1b87_3593;
38 let len = data.len();
39 let mut h: u32 = 0;
40
41 let nblocks = len / 4;
42 for i in 0..nblocks {
43 let idx = i * 4;
44 let k = u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
45 let k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
46 h ^= k;
47 h = h.rotate_left(13);
48 h = h.wrapping_mul(5).wrapping_add(0xe654_6b64);
49 }
50
51 let tail_start = nblocks * 4;
53 let tail = &data[tail_start..];
54 let mut k: u32 = 0;
55 match tail.len() {
56 3 => {
57 k ^= (tail[2] as u32) << 16;
58 k ^= (tail[1] as u32) << 8;
59 k ^= tail[0] as u32;
60 k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
61 h ^= k;
62 }
63 2 => {
64 k ^= (tail[1] as u32) << 8;
65 k ^= tail[0] as u32;
66 k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
67 h ^= k;
68 }
69 1 => {
70 k ^= tail[0] as u32;
71 k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
72 h ^= k;
73 }
74 _ => {}
75 }
76
77 h ^= len as u32;
78 h ^= h >> 16;
80 h = h.wrapping_mul(0x85eb_ca6b);
81 h ^= h >> 13;
82 h = h.wrapping_mul(0xc2b2_ae35);
83 h ^= h >> 16;
84 h
85}
86
87pub fn adler32(data: &[u8]) -> u32 {
89 const MOD: u32 = 65521;
90 let mut s1: u32 = 1;
91 let mut s2: u32 = 0;
92 for &b in data {
93 s1 = (s1 + b as u32) % MOD;
94 s2 = (s2 + s1) % MOD;
95 }
96 (s2 << 16) | s1
97}
98
99fn crc32_table() -> [u32; 256] {
101 let mut table = [0u32; 256];
102 for i in 0u32..256 {
103 let mut crc = i;
104 for _ in 0..8 {
105 if crc & 1 != 0 {
106 crc = (crc >> 1) ^ 0xEDB8_8320;
107 } else {
108 crc >>= 1;
109 }
110 }
111 table[i as usize] = crc;
112 }
113 table
114}
115
116pub fn crc32_iso(data: &[u8]) -> u32 {
118 let table = crc32_table();
119 let mut crc: u32 = 0xFFFF_FFFF;
120 for &b in data {
121 let idx = ((crc ^ b as u32) & 0xFF) as usize;
122 crc = (crc >> 8) ^ table[idx];
123 }
124 crc ^ 0xFFFF_FFFF
125}
126
127pub fn xxhash64_simple(data: &[u8]) -> u64 {
129 const PRIME1: u64 = 11_400_714_785_074_694_791;
130 const PRIME2: u64 = 14_029_467_366_897_019_727;
131 const PRIME3: u64 = 1_609_587_929_392_839_161;
132 const PRIME4: u64 = 9_650_029_242_287_828_579;
133 const PRIME5: u64 = 2_870_177_450_012_600_261;
134
135 let len = data.len();
136 let mut pos = 0usize;
137 let mut h64: u64;
138
139 if len >= 32 {
140 let mut v1 = 0u64.wrapping_add(PRIME1).wrapping_add(PRIME2);
141 let mut v2 = 0u64.wrapping_add(PRIME2);
142 let mut v3 = 0u64;
143 let mut v4 = 0u64.wrapping_sub(PRIME1);
144
145 while pos + 32 <= len {
146 let lane = |off: usize| {
147 u64::from_le_bytes([
148 data[pos + off],
149 data[pos + off + 1],
150 data[pos + off + 2],
151 data[pos + off + 3],
152 data[pos + off + 4],
153 data[pos + off + 5],
154 data[pos + off + 6],
155 data[pos + off + 7],
156 ])
157 };
158 v1 = v1
159 .wrapping_add(lane(0).wrapping_mul(PRIME2))
160 .rotate_left(31)
161 .wrapping_mul(PRIME1);
162 v2 = v2
163 .wrapping_add(lane(8).wrapping_mul(PRIME2))
164 .rotate_left(31)
165 .wrapping_mul(PRIME1);
166 v3 = v3
167 .wrapping_add(lane(16).wrapping_mul(PRIME2))
168 .rotate_left(31)
169 .wrapping_mul(PRIME1);
170 v4 = v4
171 .wrapping_add(lane(24).wrapping_mul(PRIME2))
172 .rotate_left(31)
173 .wrapping_mul(PRIME1);
174 pos += 32;
175 }
176
177 h64 = v1
178 .rotate_left(1)
179 .wrapping_add(v2.rotate_left(7))
180 .wrapping_add(v3.rotate_left(12))
181 .wrapping_add(v4.rotate_left(18));
182
183 let merge = |acc: u64, v: u64| -> u64 {
184 let v = v.wrapping_mul(PRIME2).rotate_left(31).wrapping_mul(PRIME1);
185 acc.wrapping_mul(PRIME1)
186 .wrapping_add(v)
187 .wrapping_mul(PRIME4)
188 .wrapping_add(PRIME3)
189 };
190 h64 = merge(h64, v1);
191 h64 = merge(h64, v2);
192 h64 = merge(h64, v3);
193 h64 = merge(h64, v4);
194 } else {
195 h64 = 0u64.wrapping_add(PRIME5);
196 }
197
198 h64 = h64.wrapping_add(len as u64);
199
200 while pos + 8 <= len {
202 let lane = u64::from_le_bytes([
203 data[pos],
204 data[pos + 1],
205 data[pos + 2],
206 data[pos + 3],
207 data[pos + 4],
208 data[pos + 5],
209 data[pos + 6],
210 data[pos + 7],
211 ]);
212 let k1 = lane
213 .wrapping_mul(PRIME2)
214 .rotate_left(31)
215 .wrapping_mul(PRIME1);
216 h64 ^= k1;
217 h64 = h64
218 .rotate_left(27)
219 .wrapping_mul(PRIME1)
220 .wrapping_add(PRIME4);
221 pos += 8;
222 }
223
224 if pos + 4 <= len {
226 let lane = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
227 h64 ^= (lane as u64).wrapping_mul(PRIME1);
228 h64 = h64
229 .rotate_left(23)
230 .wrapping_mul(PRIME2)
231 .wrapping_add(PRIME3);
232 pos += 4;
233 }
234
235 while pos < len {
237 h64 ^= (data[pos] as u64).wrapping_mul(PRIME5);
238 h64 = h64.rotate_left(11).wrapping_mul(PRIME1);
239 pos += 1;
240 }
241
242 h64 ^= h64 >> 33;
244 h64 = h64.wrapping_mul(PRIME2);
245 h64 ^= h64 >> 29;
246 h64 = h64.wrapping_mul(PRIME1);
247 h64 ^= h64 >> 32;
248 h64
249}
250
251pub fn blake3_256_simple(data: &[u8]) -> [u8; 32] {
253 const SEEDS: [u64; 4] = [
254 0x0000_0000_0000_0000,
255 0x1234_5678_90AB_CDEF,
256 0xFEDC_BA98_7654_3210,
257 0xDEAD_BEEF_CAFE_BABE,
258 ];
259 const PRIME: u64 = 1_099_511_628_211;
260 const OFFSET: u64 = 14_695_981_039_346_656_037;
261
262 let mut result = [0u8; 32];
263 for (i, &seed) in SEEDS.iter().enumerate() {
264 let mut hash = OFFSET ^ seed;
265 for &b in data {
266 hash ^= b as u64;
267 hash = hash.wrapping_mul(PRIME);
268 }
269 let bytes = hash.to_le_bytes();
270 result[i * 8..(i + 1) * 8].copy_from_slice(&bytes);
271 }
272 result
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
281pub enum ChecksumAlgorithm {
282 Fnv1a64,
284 Djb2,
286 Murmur3_32,
288 Adler32,
290 Crc32,
292 Xxhash64,
294 Blake3_256,
296}
297
298impl ChecksumAlgorithm {
299 pub fn name(&self) -> &'static str {
301 match self {
302 Self::Fnv1a64 => "fnv1a64",
303 Self::Djb2 => "djb2",
304 Self::Murmur3_32 => "murmur3_32",
305 Self::Adler32 => "adler32",
306 Self::Crc32 => "crc32",
307 Self::Xxhash64 => "xxhash64",
308 Self::Blake3_256 => "blake3_256",
309 }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct Checksum {
316 pub algorithm: ChecksumAlgorithm,
318 pub value: Vec<u8>,
320 pub hex: String,
322}
323
324impl Checksum {
325 pub fn new(algorithm: ChecksumAlgorithm, value: Vec<u8>) -> Self {
327 let hex = value.iter().map(|b| format!("{b:02x}")).collect();
328 Self {
329 algorithm,
330 value,
331 hex,
332 }
333 }
334}
335
336#[derive(Debug, Clone)]
338pub struct ChecksumRecord {
339 pub object_id: String,
341 pub checksum: Checksum,
343 pub computed_at: u64,
345 pub verified_at: Option<u64>,
347 pub size_bytes: u64,
349}
350
351#[derive(Debug, Clone)]
353pub struct CeVerificationResult {
354 pub object_id: String,
356 pub expected: Checksum,
358 pub actual: Checksum,
360 pub matches: bool,
362 pub verified_at: u64,
364}
365
366#[derive(Debug, Clone)]
368pub struct ChecksumStats {
369 pub total_records: usize,
371 pub verified_count: usize,
373 pub corruption_rate: f64,
375 pub algorithm: String,
377}
378
379#[derive(Debug, Clone)]
388pub struct StorageChecksumEngine {
389 records: HashMap<String, ChecksumRecord>,
390 pub algorithm: ChecksumAlgorithm,
392 pub verify_on_read: bool,
394}
395
396impl StorageChecksumEngine {
397 pub fn new(algorithm: ChecksumAlgorithm) -> Self {
399 Self {
400 records: HashMap::new(),
401 algorithm,
402 verify_on_read: false,
403 }
404 }
405
406 pub fn compute(&self, data: &[u8]) -> Checksum {
410 Self::compute_with_algorithm(self.algorithm, data)
411 }
412
413 pub fn compute_with_algorithm(algorithm: ChecksumAlgorithm, data: &[u8]) -> Checksum {
415 let value: Vec<u8> = match algorithm {
416 ChecksumAlgorithm::Fnv1a64 => fnv1a_64(data).to_le_bytes().to_vec(),
417 ChecksumAlgorithm::Djb2 => djb2(data).to_le_bytes().to_vec(),
418 ChecksumAlgorithm::Murmur3_32 => murmur3_32(data).to_le_bytes().to_vec(),
419 ChecksumAlgorithm::Adler32 => adler32(data).to_le_bytes().to_vec(),
420 ChecksumAlgorithm::Crc32 => crc32_iso(data).to_le_bytes().to_vec(),
421 ChecksumAlgorithm::Xxhash64 => xxhash64_simple(data).to_le_bytes().to_vec(),
422 ChecksumAlgorithm::Blake3_256 => blake3_256_simple(data).to_vec(),
423 };
424 Checksum::new(algorithm, value)
425 }
426
427 pub fn compute_for(&mut self, object_id: String, data: &[u8], now: u64) -> ChecksumRecord {
429 let checksum = self.compute(data);
430 let record = ChecksumRecord {
431 object_id: object_id.clone(),
432 checksum,
433 computed_at: now,
434 verified_at: None,
435 size_bytes: data.len() as u64,
436 };
437 self.records.insert(object_id, record.clone());
438 record
439 }
440
441 pub fn verify(
448 &mut self,
449 object_id: &str,
450 data: &[u8],
451 now: u64,
452 ) -> Option<CeVerificationResult> {
453 let record = self.records.get_mut(object_id)?;
454 let actual = Self::compute_with_algorithm(record.checksum.algorithm, data);
455 let matches = actual.value == record.checksum.value;
456 if matches {
457 record.verified_at = Some(now);
458 }
459 Some(CeVerificationResult {
460 object_id: object_id.to_owned(),
461 expected: record.checksum.clone(),
462 actual,
463 matches,
464 verified_at: now,
465 })
466 }
467
468 pub fn record(&self, object_id: &str) -> Option<&ChecksumRecord> {
470 self.records.get(object_id)
471 }
472
473 pub fn remove(&mut self, object_id: &str) -> bool {
475 self.records.remove(object_id).is_some()
476 }
477
478 pub fn verify_all(
482 &mut self,
483 data_fn: impl Fn(&str) -> Option<Vec<u8>>,
484 now: u64,
485 ) -> Vec<CeVerificationResult> {
486 let ids: Vec<String> = self.records.keys().cloned().collect();
487 let mut results = Vec::with_capacity(ids.len());
488 for id in ids {
489 if let Some(data) = data_fn(&id) {
490 if let Some(result) = self.verify(&id, &data, now) {
491 results.push(result);
492 }
493 }
494 }
495 results
496 }
497
498 pub fn batch_compute(&mut self, items: &[(&str, &[u8])], now: u64) -> Vec<ChecksumRecord> {
500 items
501 .iter()
502 .map(|&(id, data)| self.compute_for(id.to_owned(), data, now))
503 .collect()
504 }
505
506 pub fn corruption_count(results: &[CeVerificationResult]) -> usize {
510 results.iter().filter(|r| !r.matches).count()
511 }
512
513 pub fn object_count(&self) -> usize {
515 self.records.len()
516 }
517
518 pub fn stats(&self, results: &[CeVerificationResult]) -> ChecksumStats {
520 let total_records = self.records.len();
521 let verified_count = self
522 .records
523 .values()
524 .filter(|r| r.verified_at.is_some())
525 .count();
526 let corrupted = Self::corruption_count(results);
527 let corruption_rate = if results.is_empty() {
528 0.0
529 } else {
530 corrupted as f64 / results.len() as f64
531 };
532 ChecksumStats {
533 total_records,
534 verified_count,
535 corruption_rate,
536 algorithm: self.algorithm.name().to_owned(),
537 }
538 }
539}
540
541#[cfg(test)]
546mod tests {
547 use crate::checksum_engine::{
548 adler32, blake3_256_simple, crc32_iso, djb2, fnv1a_64, murmur3_32, xxhash64_simple,
549 CeVerificationResult, ChecksumAlgorithm, ChecksumRecord, StorageChecksumEngine,
550 };
551
552 #[test]
555 fn test_fnv1a64_empty() {
556 assert_eq!(fnv1a_64(b""), 14_695_981_039_346_656_037);
558 }
559
560 #[test]
561 fn test_fnv1a64_known_value() {
562 let h = fnv1a_64(b"hello");
564 assert_ne!(h, 0);
565 assert_eq!(fnv1a_64(b"hello"), h);
567 }
568
569 #[test]
570 fn test_fnv1a64_different_inputs_differ() {
571 assert_ne!(fnv1a_64(b"foo"), fnv1a_64(b"bar"));
572 }
573
574 #[test]
575 fn test_djb2_empty() {
576 let h = djb2(b"");
577 assert_eq!(h, (5381u64 & 0xFFFF_FFFF) as u32);
578 }
579
580 #[test]
581 fn test_djb2_hello() {
582 let h = djb2(b"hello");
583 assert_ne!(h, 0);
584 assert_eq!(djb2(b"hello"), h);
585 }
586
587 #[test]
588 fn test_djb2_different() {
589 assert_ne!(djb2(b"abc"), djb2(b"xyz"));
590 }
591
592 #[test]
593 fn test_murmur3_32_empty() {
594 let h = murmur3_32(b"");
596 assert_eq!(h, murmur3_32(b""));
597 }
598
599 #[test]
600 fn test_murmur3_32_4bytes() {
601 let h = murmur3_32(b"test");
602 assert_ne!(h, 0);
603 assert_eq!(murmur3_32(b"test"), h);
604 }
605
606 #[test]
607 fn test_murmur3_32_partial_tail() {
608 let h = murmur3_32(b"abcde");
610 assert_eq!(murmur3_32(b"abcde"), h);
611 }
612
613 #[test]
614 fn test_adler32_empty() {
615 assert_eq!(adler32(b""), 1);
617 }
618
619 #[test]
620 fn test_adler32_abc() {
621 assert_eq!(adler32(b"abc"), 0x024d_0127);
623 }
624
625 #[test]
626 fn test_adler32_deterministic() {
627 let h = adler32(b"hello world");
628 assert_eq!(adler32(b"hello world"), h);
629 }
630
631 #[test]
632 fn test_crc32_empty() {
633 assert_eq!(crc32_iso(b""), 0x0000_0000);
635 }
636
637 #[test]
638 fn test_crc32_known_value() {
639 assert_eq!(crc32_iso(b"123456789"), 0xCBF4_3926);
641 }
642
643 #[test]
644 fn test_crc32_deterministic() {
645 assert_eq!(crc32_iso(b"hello"), crc32_iso(b"hello"));
646 }
647
648 #[test]
649 fn test_xxhash64_empty() {
650 let h = xxhash64_simple(b"");
651 assert_eq!(xxhash64_simple(b""), h);
652 }
653
654 #[test]
655 fn test_xxhash64_hello() {
656 let h = xxhash64_simple(b"hello");
657 assert_ne!(h, 0);
658 assert_eq!(xxhash64_simple(b"hello"), h);
659 }
660
661 #[test]
662 fn test_xxhash64_large_input() {
663 let data: Vec<u8> = (0u8..64).collect();
665 let h = xxhash64_simple(&data);
666 assert_eq!(xxhash64_simple(&data), h);
667 }
668
669 #[test]
670 fn test_blake3_256_empty() {
671 let h = blake3_256_simple(b"");
672 assert_eq!(h.len(), 32);
673 assert_eq!(blake3_256_simple(b""), h);
674 }
675
676 #[test]
677 fn test_blake3_256_hello() {
678 let h = blake3_256_simple(b"hello");
679 assert_eq!(h.len(), 32);
680 assert_ne!(h, [0u8; 32]);
681 }
682
683 #[test]
684 fn test_blake3_256_different_inputs() {
685 assert_ne!(blake3_256_simple(b"a"), blake3_256_simple(b"b"));
686 }
687
688 #[test]
691 fn test_checksum_hex_encoding() {
692 let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
693 let cs = engine.compute(b"hello");
694 assert_eq!(cs.hex.len(), 16); assert!(cs.hex.chars().all(|c| c.is_ascii_hexdigit()));
696 }
697
698 #[test]
699 fn test_checksum_hex_lowercase() {
700 let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
701 let cs = engine.compute(b"123456789");
702 assert!(cs.hex.chars().all(|c| !c.is_ascii_uppercase()));
703 }
704
705 #[test]
706 fn test_checksum_blake3_hex_length() {
707 let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Blake3_256);
708 let cs = engine.compute(b"data");
709 assert_eq!(cs.hex.len(), 64); }
711
712 #[test]
715 fn test_compute_for_stores_record() {
716 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
717 let rec = engine.compute_for("obj1".to_owned(), b"data", 1000);
718 assert_eq!(rec.object_id, "obj1");
719 assert_eq!(rec.computed_at, 1000);
720 assert_eq!(rec.size_bytes, 4);
721 assert!(rec.verified_at.is_none());
722 assert_eq!(engine.object_count(), 1);
723 }
724
725 #[test]
726 fn test_record_retrieval() {
727 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Adler32);
728 engine.compute_for("x".to_owned(), b"hello", 42);
729 let rec = engine.record("x");
730 assert!(rec.is_some());
731 assert_eq!(rec.map(|r| r.object_id.as_str()), Some("x"));
732 }
733
734 #[test]
735 fn test_record_missing_returns_none() {
736 let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Djb2);
737 assert!(engine.record("nonexistent").is_none());
738 }
739
740 #[test]
741 fn test_remove_existing() {
742 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
743 engine.compute_for("a".to_owned(), b"x", 0);
744 assert!(engine.remove("a"));
745 assert_eq!(engine.object_count(), 0);
746 }
747
748 #[test]
749 fn test_remove_nonexistent() {
750 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
751 assert!(!engine.remove("ghost"));
752 }
753
754 #[test]
757 fn test_verify_matching() {
758 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
759 engine.compute_for("doc".to_owned(), b"content", 100);
760 let result = engine
761 .verify("doc", b"content", 200)
762 .expect("result expected");
763 assert!(result.matches);
764 assert_eq!(result.verified_at, 200);
765 assert_eq!(engine.record("doc").and_then(|r| r.verified_at), Some(200));
767 }
768
769 #[test]
770 fn test_verify_mismatch() {
771 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
772 engine.compute_for("doc".to_owned(), b"original", 100);
773 let result = engine.verify("doc", b"corrupted", 200).expect("result");
774 assert!(!result.matches);
775 assert!(engine.record("doc").and_then(|r| r.verified_at).is_none());
777 }
778
779 #[test]
780 fn test_verify_missing_object() {
781 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
782 assert!(engine.verify("missing", b"data", 0).is_none());
783 }
784
785 #[test]
786 fn test_verify_updates_verified_at() {
787 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Xxhash64);
788 engine.compute_for("item".to_owned(), b"payload", 50);
789 let r = engine.verify("item", b"payload", 999).expect("ok");
790 assert!(r.matches);
791 assert_eq!(engine.record("item").and_then(|r| r.verified_at), Some(999));
792 }
793
794 #[test]
797 fn test_batch_compute() {
798 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Murmur3_32);
799 let items: Vec<(&str, &[u8])> = vec![("a", b"alpha"), ("b", b"beta"), ("c", b"gamma")];
800 let records: Vec<ChecksumRecord> = engine.batch_compute(&items, 777);
801 assert_eq!(records.len(), 3);
802 assert_eq!(engine.object_count(), 3);
803 }
804
805 #[test]
806 fn test_batch_compute_all_stored() {
807 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Adler32);
808 let items: Vec<(&str, &[u8])> = vec![("x", b"1"), ("y", b"2")];
809 engine.batch_compute(&items, 10);
810 assert!(engine.record("x").is_some());
811 assert!(engine.record("y").is_some());
812 }
813
814 #[test]
817 fn test_verify_all_clean() {
818 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
819 engine.compute_for("p".to_owned(), b"ping", 1);
820 engine.compute_for("q".to_owned(), b"pong", 1);
821 let results = engine.verify_all(
822 |id| {
823 if id == "p" {
824 Some(b"ping".to_vec())
825 } else {
826 Some(b"pong".to_vec())
827 }
828 },
829 2,
830 );
831 assert_eq!(results.len(), 2);
832 assert!(results.iter().all(|r| r.matches));
833 }
834
835 #[test]
836 fn test_verify_all_with_corruption() {
837 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
838 engine.compute_for("good".to_owned(), b"ok", 1);
839 engine.compute_for("bad".to_owned(), b"original", 1);
840 let results = engine.verify_all(
841 |id| {
842 if id == "good" {
843 Some(b"ok".to_vec())
844 } else {
845 Some(b"corrupted".to_vec())
846 }
847 },
848 2,
849 );
850 let corrupt = StorageChecksumEngine::corruption_count(&results);
851 assert_eq!(corrupt, 1);
852 }
853
854 #[test]
855 fn test_verify_all_data_fn_returns_none() {
856 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
857 engine.compute_for("present".to_owned(), b"data", 0);
858 engine.compute_for("absent".to_owned(), b"x", 0);
859 let results = engine.verify_all(
860 |id| {
861 if id == "present" {
862 Some(b"data".to_vec())
863 } else {
864 None
865 }
866 },
867 5,
868 );
869 assert_eq!(results.len(), 1);
871 }
872
873 #[test]
876 fn test_corruption_count_zero() {
877 let results: Vec<CeVerificationResult> = vec![];
878 assert_eq!(StorageChecksumEngine::corruption_count(&results), 0);
879 }
880
881 #[test]
882 fn test_stats_empty() {
883 let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Blake3_256);
884 let stats = engine.stats(&[]);
885 assert_eq!(stats.total_records, 0);
886 assert_eq!(stats.verified_count, 0);
887 assert_eq!(stats.corruption_rate, 0.0);
888 assert_eq!(stats.algorithm, "blake3_256");
889 }
890
891 #[test]
892 fn test_stats_after_verification() {
893 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
894 engine.compute_for("a".to_owned(), b"hello", 1);
895 engine.compute_for("b".to_owned(), b"world", 1);
896 let results = engine.verify_all(
897 |id| {
898 if id == "a" {
899 Some(b"hello".to_vec())
900 } else {
901 Some(b"corrupted".to_vec())
902 }
903 },
904 2,
905 );
906 let stats = engine.stats(&results);
907 assert_eq!(stats.total_records, 2);
908 assert_eq!(stats.verified_count, 1); assert!((stats.corruption_rate - 0.5).abs() < f64::EPSILON);
910 }
911
912 #[test]
913 fn test_algorithm_name() {
914 assert_eq!(ChecksumAlgorithm::Fnv1a64.name(), "fnv1a64");
915 assert_eq!(ChecksumAlgorithm::Djb2.name(), "djb2");
916 assert_eq!(ChecksumAlgorithm::Blake3_256.name(), "blake3_256");
917 assert_eq!(ChecksumAlgorithm::Crc32.name(), "crc32");
918 }
919
920 #[test]
921 fn test_all_algorithms_produce_nonzero_for_nonempty() {
922 let data = b"checksum test data";
923 let algos = [
924 ChecksumAlgorithm::Fnv1a64,
925 ChecksumAlgorithm::Djb2,
926 ChecksumAlgorithm::Murmur3_32,
927 ChecksumAlgorithm::Adler32,
928 ChecksumAlgorithm::Crc32,
929 ChecksumAlgorithm::Xxhash64,
930 ChecksumAlgorithm::Blake3_256,
931 ];
932 for algo in algos {
933 let cs = StorageChecksumEngine::compute_with_algorithm(algo, data);
934 assert!(
935 cs.value.iter().any(|&b| b != 0),
936 "Algorithm {:?} returned all-zero for non-empty input",
937 algo
938 );
939 }
940 }
941
942 #[test]
943 fn test_overwrite_record_on_recompute() {
944 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
945 engine.compute_for("obj".to_owned(), b"v1", 10);
946 engine.compute_for("obj".to_owned(), b"v2", 20);
947 assert_eq!(engine.object_count(), 1);
949 let rec = engine.record("obj").expect("present");
950 assert_eq!(rec.computed_at, 20);
951 assert_eq!(rec.size_bytes, 2);
952 }
953
954 #[test]
955 fn test_verify_on_read_flag() {
956 let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
957 engine.verify_on_read = true;
958 assert!(engine.verify_on_read);
959 }
960
961 #[test]
962 fn test_compute_with_algorithm_static() {
963 let cs = StorageChecksumEngine::compute_with_algorithm(ChecksumAlgorithm::Adler32, b"abc");
964 assert_eq!(cs.algorithm, ChecksumAlgorithm::Adler32);
965 let expected = adler32(b"abc").to_le_bytes().to_vec();
967 assert_eq!(cs.value, expected);
968 }
969}