1use std::collections::{HashMap, VecDeque};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum ChecksumAlgo {
16 Crc32,
18 Adler32,
20 FnvXor64,
22 MultiCheck,
24}
25
26const fn build_crc32_table() -> [u32; 256] {
32 let mut table = [0u32; 256];
33 let mut i = 0usize;
34 while i < 256 {
35 let mut crc = i as u32;
36 let mut j = 0usize;
37 while j < 8 {
38 if crc & 1 != 0 {
39 crc = (crc >> 1) ^ 0xEDB8_8320;
40 } else {
41 crc >>= 1;
42 }
43 j += 1;
44 }
45 table[i] = crc;
46 i += 1;
47 }
48 table
49}
50
51static CRC32_TABLE: [u32; 256] = build_crc32_table();
52
53pub fn compute_crc32(data: &[u8]) -> u32 {
55 let mut crc: u32 = 0xFFFF_FFFF;
56 for &byte in data {
57 let idx = ((crc ^ u32::from(byte)) & 0xFF) as usize;
58 crc = (crc >> 8) ^ CRC32_TABLE[idx];
59 }
60 crc ^ 0xFFFF_FFFF
61}
62
63const ADLER32_MOD: u32 = 65521;
65
66pub fn compute_adler32(data: &[u8]) -> u32 {
72 let mut a: u32 = 1;
73 let mut b: u32 = 0;
74 for &byte in data {
75 a = (a + u32::from(byte)) % ADLER32_MOD;
76 b = (b + a) % ADLER32_MOD;
77 }
78 (b << 16) | a
79}
80
81const FNV1A_64_OFFSET: u64 = 14_695_981_039_346_656_037;
83const FNV1A_64_PRIME: u64 = 1_099_511_628_211;
84
85pub fn compute_fnv_xor64(data: &[u8]) -> u32 {
87 let mut hash = FNV1A_64_OFFSET;
88 for &byte in data {
89 hash ^= u64::from(byte);
90 hash = hash.wrapping_mul(FNV1A_64_PRIME);
91 }
92 let high = (hash >> 32) as u32;
93 let low = (hash & 0xFFFF_FFFF) as u32;
94 high ^ low
95}
96
97pub fn compute_checksum(data: &[u8], algo: &ChecksumAlgo) -> u32 {
103 match algo {
104 ChecksumAlgo::Crc32 => compute_crc32(data),
105 ChecksumAlgo::Adler32 => compute_adler32(data),
106 ChecksumAlgo::FnvXor64 => compute_fnv_xor64(data),
107 ChecksumAlgo::MultiCheck => {
108 compute_crc32(data) ^ compute_adler32(data) ^ compute_fnv_xor64(data)
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
119pub struct BlockChecksum {
120 pub cid: String,
122 pub algo: ChecksumAlgo,
124 pub checksum: u32,
126 pub computed_at: u64,
128 pub block_size: usize,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum AuditResult {
135 Passed {
137 cid: String,
139 checksum: u32,
141 },
142 Failed {
144 cid: String,
146 stored_checksum: u32,
148 computed_checksum: u32,
150 },
151 Missing {
153 cid: String,
155 },
156}
157
158#[derive(Debug, Clone)]
160pub struct RepairRecord {
161 pub cid: String,
163 pub detected_at: u64,
165 pub repaired_at: Option<u64>,
167 pub source: String,
169 pub success: bool,
171}
172
173#[derive(Debug, Clone)]
175pub struct AuditConfig {
176 pub algo: ChecksumAlgo,
178 pub audit_batch_size: usize,
180 pub max_repair_history: usize,
182 pub auto_repair: bool,
184}
185
186impl Default for AuditConfig {
187 fn default() -> Self {
188 Self {
189 algo: ChecksumAlgo::MultiCheck,
190 audit_batch_size: 100,
191 max_repair_history: 1000,
192 auto_repair: false,
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
199pub struct AuditorStats {
200 pub registered_blocks: usize,
202 pub total_audited: u64,
204 pub total_passed: u64,
206 pub total_failed: u64,
208 pub integrity_rate: f64,
210 pub pending_repairs: usize,
212}
213
214#[derive(Debug)]
223pub struct DataIntegrityAuditor {
224 pub config: AuditConfig,
226 pub checksums: HashMap<String, BlockChecksum>,
228 pub repair_history: VecDeque<RepairRecord>,
230 pub total_audited: u64,
232 pub total_passed: u64,
234 pub total_failed: u64,
236}
237
238impl DataIntegrityAuditor {
239 pub fn new(config: AuditConfig) -> Self {
245 Self {
246 config,
247 checksums: HashMap::new(),
248 repair_history: VecDeque::new(),
249 total_audited: 0,
250 total_passed: 0,
251 total_failed: 0,
252 }
253 }
254
255 pub fn compute_checksum(data: &[u8], algo: &ChecksumAlgo) -> u32 {
261 compute_checksum(data, algo)
262 }
263
264 pub fn compute_crc32(data: &[u8]) -> u32 {
266 compute_crc32(data)
267 }
268
269 pub fn compute_adler32(data: &[u8]) -> u32 {
271 compute_adler32(data)
272 }
273
274 pub fn compute_fnv_xor64(data: &[u8]) -> u32 {
276 compute_fnv_xor64(data)
277 }
278
279 pub fn register_block(&mut self, cid: String, data: &[u8], now: u64) {
290 let checksum = compute_checksum(data, &self.config.algo);
291 let record = BlockChecksum {
292 cid: cid.clone(),
293 algo: self.config.algo,
294 checksum,
295 computed_at: now,
296 block_size: data.len(),
297 };
298 self.checksums.insert(cid, record);
299 }
300
301 pub fn audit_block(&mut self, cid: &str, data: &[u8]) -> AuditResult {
311 self.total_audited += 1;
312
313 let stored = match self.checksums.get(cid) {
314 Some(s) => s.clone(),
315 None => {
316 return AuditResult::Missing {
317 cid: cid.to_owned(),
318 };
319 }
320 };
321
322 let computed = compute_checksum(data, &stored.algo);
323
324 if computed == stored.checksum {
325 self.total_passed += 1;
326 AuditResult::Passed {
327 cid: cid.to_owned(),
328 checksum: computed,
329 }
330 } else {
331 self.total_failed += 1;
332 if self.config.auto_repair {
333 self.schedule_repair(cid.to_owned(), 0, "auto".to_owned());
334 }
335 AuditResult::Failed {
336 cid: cid.to_owned(),
337 stored_checksum: stored.checksum,
338 computed_checksum: computed,
339 }
340 }
341 }
342
343 pub fn audit_batch(&mut self, blocks: &[(String, Vec<u8>)]) -> Vec<AuditResult> {
348 let limit = self.config.audit_batch_size.min(blocks.len());
349 let mut results = Vec::with_capacity(limit);
350 for (cid, data) in &blocks[..limit] {
351 let result = self.audit_block(cid, data);
352 results.push(result);
353 }
354 results
355 }
356
357 pub fn schedule_repair(&mut self, cid: String, now: u64, source: String) {
367 let record = RepairRecord {
368 cid,
369 detected_at: now,
370 repaired_at: None,
371 source,
372 success: false,
373 };
374 self.repair_history.push_back(record);
375 while self.repair_history.len() > self.config.max_repair_history {
377 self.repair_history.pop_front();
378 }
379 }
380
381 pub fn mark_repaired(&mut self, cid: &str, now: u64) -> bool {
387 for record in self.repair_history.iter_mut().rev() {
389 if record.cid == cid && record.repaired_at.is_none() {
390 record.repaired_at = Some(now);
391 record.success = true;
392 return true;
393 }
394 }
395 false
396 }
397
398 pub fn integrity_rate(&self) -> f64 {
405 let denom = self.total_audited.max(1) as f64;
406 self.total_passed as f64 / denom
407 }
408
409 pub fn pending_repairs(&self) -> Vec<&RepairRecord> {
411 self.repair_history
412 .iter()
413 .filter(|r| r.repaired_at.is_none())
414 .collect()
415 }
416
417 pub fn auditor_stats(&self) -> AuditorStats {
419 AuditorStats {
420 registered_blocks: self.checksums.len(),
421 total_audited: self.total_audited,
422 total_passed: self.total_passed,
423 total_failed: self.total_failed,
424 integrity_rate: self.integrity_rate(),
425 pending_repairs: self.pending_repairs().len(),
426 }
427 }
428}
429
430#[cfg(test)]
435mod tests {
436 use crate::data_integrity_auditor::{
437 compute_adler32, compute_checksum, compute_crc32, compute_fnv_xor64, AuditConfig,
438 AuditResult, ChecksumAlgo, DataIntegrityAuditor,
439 };
440
441 fn default_auditor() -> DataIntegrityAuditor {
446 DataIntegrityAuditor::new(AuditConfig::default())
447 }
448
449 fn auditor_with_algo(algo: ChecksumAlgo) -> DataIntegrityAuditor {
450 DataIntegrityAuditor::new(AuditConfig {
451 algo,
452 ..AuditConfig::default()
453 })
454 }
455
456 #[test]
461 fn crc32_empty() {
462 assert_eq!(compute_crc32(&[]), 0x0000_0000);
464 }
465
466 #[test]
467 fn crc32_single_byte() {
468 let v = compute_crc32(&[0x61]); assert_ne!(v, 0);
471 }
472
473 #[test]
474 fn crc32_known_vector() {
475 let data = b"123456789";
477 assert_eq!(compute_crc32(data), 0xCBF4_3926);
478 }
479
480 #[test]
481 fn crc32_deterministic() {
482 let data = b"hello world";
483 assert_eq!(compute_crc32(data), compute_crc32(data));
484 }
485
486 #[test]
487 fn crc32_different_inputs_differ() {
488 assert_ne!(compute_crc32(b"abc"), compute_crc32(b"abd"));
489 }
490
491 #[test]
496 fn adler32_empty() {
497 assert_eq!(compute_adler32(&[]), 1);
499 }
500
501 #[test]
502 fn adler32_known_vector() {
503 let data = b"Wikipedia";
505 assert_eq!(compute_adler32(data), 0x11E6_0398);
506 }
507
508 #[test]
509 fn adler32_deterministic() {
510 let data = b"data integrity";
511 assert_eq!(compute_adler32(data), compute_adler32(data));
512 }
513
514 #[test]
515 fn adler32_different_inputs_differ() {
516 assert_ne!(compute_adler32(b"foo"), compute_adler32(b"bar"));
517 }
518
519 #[test]
524 fn fnv_xor64_empty() {
525 let v = compute_fnv_xor64(&[]);
529 assert_eq!(v, 0x4FD0_BFC1);
530 }
531
532 #[test]
533 fn fnv_xor64_deterministic() {
534 let data = b"consistency check";
535 assert_eq!(compute_fnv_xor64(data), compute_fnv_xor64(data));
536 }
537
538 #[test]
539 fn fnv_xor64_different_inputs_differ() {
540 assert_ne!(compute_fnv_xor64(b"alpha"), compute_fnv_xor64(b"beta"));
541 }
542
543 #[test]
548 fn multicheck_combines_three_algos() {
549 let data = b"multi";
550 let expected = compute_crc32(data) ^ compute_adler32(data) ^ compute_fnv_xor64(data);
551 let got = compute_checksum(data, &ChecksumAlgo::MultiCheck);
552 assert_eq!(got, expected);
553 }
554
555 #[test]
556 fn multicheck_deterministic() {
557 let data = b"reproducible";
558 assert_eq!(
559 compute_checksum(data, &ChecksumAlgo::MultiCheck),
560 compute_checksum(data, &ChecksumAlgo::MultiCheck)
561 );
562 }
563
564 #[test]
569 fn dispatch_crc32() {
570 let data = b"dispatch crc32";
571 assert_eq!(
572 compute_checksum(data, &ChecksumAlgo::Crc32),
573 compute_crc32(data)
574 );
575 }
576
577 #[test]
578 fn dispatch_adler32() {
579 let data = b"dispatch adler32";
580 assert_eq!(
581 compute_checksum(data, &ChecksumAlgo::Adler32),
582 compute_adler32(data)
583 );
584 }
585
586 #[test]
587 fn dispatch_fnvxor64() {
588 let data = b"dispatch fnv";
589 assert_eq!(
590 compute_checksum(data, &ChecksumAlgo::FnvXor64),
591 compute_fnv_xor64(data)
592 );
593 }
594
595 #[test]
600 fn default_config_values() {
601 let cfg = AuditConfig::default();
602 assert_eq!(cfg.algo, ChecksumAlgo::MultiCheck);
603 assert_eq!(cfg.audit_batch_size, 100);
604 assert_eq!(cfg.max_repair_history, 1000);
605 assert!(!cfg.auto_repair);
606 }
607
608 #[test]
613 fn register_and_audit_passes() {
614 let mut aud = default_auditor();
615 aud.register_block("cid1".to_owned(), b"hello", 1000);
616 let result = aud.audit_block("cid1", b"hello");
617 assert!(matches!(result, AuditResult::Passed { .. }));
618 }
619
620 #[test]
621 fn audit_detects_corruption() {
622 let mut aud = default_auditor();
623 aud.register_block("cid2".to_owned(), b"original", 1000);
624 let result = aud.audit_block("cid2", b"corrupted");
625 assert!(matches!(result, AuditResult::Failed { .. }));
626 }
627
628 #[test]
629 fn audit_missing_returns_missing() {
630 let mut aud = default_auditor();
631 let result = aud.audit_block("nonexistent", b"data");
632 assert!(matches!(result, AuditResult::Missing { .. }));
633 }
634
635 #[test]
636 fn register_block_overwrites_previous() {
637 let mut aud = default_auditor();
638 aud.register_block("cid3".to_owned(), b"v1", 1000);
639 aud.register_block("cid3".to_owned(), b"v2", 2000);
640 assert!(matches!(
642 aud.audit_block("cid3", b"v2"),
643 AuditResult::Passed { .. }
644 ));
645 assert!(matches!(
646 aud.audit_block("cid3", b"v1"),
647 AuditResult::Failed { .. }
648 ));
649 }
650
651 #[test]
652 fn audit_increments_counters() {
653 let mut aud = default_auditor();
654 aud.register_block("cid4".to_owned(), b"data", 0);
655 aud.audit_block("cid4", b"data");
656 aud.audit_block("cid4", b"bad");
657 assert_eq!(aud.total_audited, 2);
658 assert_eq!(aud.total_passed, 1);
659 assert_eq!(aud.total_failed, 1);
660 }
661
662 #[test]
663 fn missing_audit_increments_total_only() {
664 let mut aud = default_auditor();
665 aud.audit_block("ghost", b"x");
666 assert_eq!(aud.total_audited, 1);
667 assert_eq!(aud.total_passed, 0);
668 assert_eq!(aud.total_failed, 0);
669 }
670
671 #[test]
676 fn audit_batch_respects_batch_size() {
677 let mut aud = DataIntegrityAuditor::new(AuditConfig {
678 audit_batch_size: 3,
679 ..AuditConfig::default()
680 });
681 for i in 0u8..10 {
682 let cid = format!("cid{i}");
683 aud.register_block(cid, &[i], 0);
684 }
685 let blocks: Vec<(String, Vec<u8>)> =
686 (0u8..10).map(|i| (format!("cid{i}"), vec![i])).collect();
687 let results = aud.audit_batch(&blocks);
688 assert_eq!(results.len(), 3);
689 }
690
691 #[test]
692 fn audit_batch_all_pass() {
693 let mut aud = default_auditor();
694 for i in 0u8..5 {
695 aud.register_block(format!("b{i}"), &[i, i + 1], 0);
696 }
697 let blocks: Vec<(String, Vec<u8>)> = (0u8..5)
698 .map(|i| (format!("b{i}"), vec![i, i + 1]))
699 .collect();
700 let results = aud.audit_batch(&blocks);
701 assert!(results
702 .iter()
703 .all(|r| matches!(r, AuditResult::Passed { .. })));
704 }
705
706 #[test]
707 fn audit_batch_empty_input() {
708 let mut aud = default_auditor();
709 let results = aud.audit_batch(&[]);
710 assert!(results.is_empty());
711 }
712
713 #[test]
718 fn schedule_repair_adds_record() {
719 let mut aud = default_auditor();
720 aud.schedule_repair("cid5".to_owned(), 100, "peer-A".to_owned());
721 assert_eq!(aud.pending_repairs().len(), 1);
722 }
723
724 #[test]
725 fn mark_repaired_succeeds() {
726 let mut aud = default_auditor();
727 aud.schedule_repair("cid6".to_owned(), 100, "peer-B".to_owned());
728 let ok = aud.mark_repaired("cid6", 200);
729 assert!(ok);
730 assert_eq!(aud.pending_repairs().len(), 0);
731 }
732
733 #[test]
734 fn mark_repaired_sets_timestamp() {
735 let mut aud = default_auditor();
736 aud.schedule_repair("cid7".to_owned(), 50, "src".to_owned());
737 aud.mark_repaired("cid7", 999);
738 let record = aud
739 .repair_history
740 .iter()
741 .find(|r| r.cid == "cid7")
742 .expect("record must exist");
743 assert_eq!(record.repaired_at, Some(999));
744 assert!(record.success);
745 }
746
747 #[test]
748 fn mark_repaired_unknown_cid_returns_false() {
749 let mut aud = default_auditor();
750 assert!(!aud.mark_repaired("unknown", 0));
751 }
752
753 #[test]
754 fn pending_repairs_filters_completed() {
755 let mut aud = default_auditor();
756 aud.schedule_repair("c1".to_owned(), 1, "s1".to_owned());
757 aud.schedule_repair("c2".to_owned(), 2, "s2".to_owned());
758 aud.mark_repaired("c1", 10);
759 let pending = aud.pending_repairs();
760 assert_eq!(pending.len(), 1);
761 assert_eq!(pending[0].cid, "c2");
762 }
763
764 #[test]
765 fn repair_history_evicts_oldest_when_full() {
766 let mut aud = DataIntegrityAuditor::new(AuditConfig {
767 max_repair_history: 3,
768 ..AuditConfig::default()
769 });
770 for i in 0u64..5 {
771 aud.schedule_repair(format!("c{i}"), i, "src".to_owned());
772 }
773 assert_eq!(aud.repair_history.len(), 3);
775 let cids: Vec<&str> = aud.repair_history.iter().map(|r| r.cid.as_str()).collect();
777 assert!(!cids.contains(&"c0"));
778 assert!(!cids.contains(&"c1"));
779 assert!(cids.contains(&"c4"));
780 }
781
782 #[test]
783 fn mark_repaired_picks_most_recent_pending() {
784 let mut aud = default_auditor();
785 aud.schedule_repair("dup".to_owned(), 1, "s1".to_owned());
787 aud.schedule_repair("dup".to_owned(), 2, "s2".to_owned());
788 aud.mark_repaired("dup", 99);
790 let pending = aud.pending_repairs();
791 assert_eq!(pending.len(), 1);
792 assert_eq!(pending[0].detected_at, 1);
794 }
795
796 #[test]
801 fn integrity_rate_zero_when_nothing_audited() {
802 let aud = default_auditor();
803 assert_eq!(aud.integrity_rate(), 0.0);
805 }
806
807 #[test]
808 fn integrity_rate_all_pass() {
809 let mut aud = default_auditor();
810 aud.register_block("cx".to_owned(), b"x", 0);
811 aud.audit_block("cx", b"x");
812 assert!((aud.integrity_rate() - 1.0).abs() < f64::EPSILON);
813 }
814
815 #[test]
816 fn integrity_rate_mixed() {
817 let mut aud = default_auditor();
818 aud.register_block("cy".to_owned(), b"y", 0);
819 aud.audit_block("cy", b"y"); aud.audit_block("cy", b"z"); let rate = aud.integrity_rate();
822 assert!((rate - 0.5).abs() < f64::EPSILON);
823 }
824
825 #[test]
830 fn auditor_stats_reflects_state() {
831 let mut aud = default_auditor();
832 aud.register_block("s1".to_owned(), b"data", 0);
833 aud.register_block("s2".to_owned(), b"more", 0);
834 aud.audit_block("s1", b"data");
835 aud.schedule_repair("s1".to_owned(), 0, "peer".to_owned());
836
837 let stats = aud.auditor_stats();
838 assert_eq!(stats.registered_blocks, 2);
839 assert_eq!(stats.total_audited, 1);
840 assert_eq!(stats.total_passed, 1);
841 assert_eq!(stats.total_failed, 0);
842 assert!((stats.integrity_rate - 1.0).abs() < f64::EPSILON);
843 assert_eq!(stats.pending_repairs, 1);
844 }
845
846 #[test]
851 fn auto_repair_schedules_on_failure() {
852 let mut aud = DataIntegrityAuditor::new(AuditConfig {
853 auto_repair: true,
854 ..AuditConfig::default()
855 });
856 aud.register_block("ar1".to_owned(), b"good", 0);
857 aud.audit_block("ar1", b"bad");
858 assert_eq!(aud.pending_repairs().len(), 1);
859 }
860
861 #[test]
862 fn auto_repair_does_not_schedule_on_pass() {
863 let mut aud = DataIntegrityAuditor::new(AuditConfig {
864 auto_repair: true,
865 ..AuditConfig::default()
866 });
867 aud.register_block("ar2".to_owned(), b"ok", 0);
868 aud.audit_block("ar2", b"ok");
869 assert_eq!(aud.pending_repairs().len(), 0);
870 }
871
872 #[test]
877 fn crc32_algo_round_trip() {
878 let mut aud = auditor_with_algo(ChecksumAlgo::Crc32);
879 aud.register_block("rc".to_owned(), b"crc check", 0);
880 assert!(matches!(
881 aud.audit_block("rc", b"crc check"),
882 AuditResult::Passed { .. }
883 ));
884 }
885
886 #[test]
887 fn adler32_algo_round_trip() {
888 let mut aud = auditor_with_algo(ChecksumAlgo::Adler32);
889 aud.register_block("ra".to_owned(), b"adler check", 0);
890 assert!(matches!(
891 aud.audit_block("ra", b"adler check"),
892 AuditResult::Passed { .. }
893 ));
894 }
895
896 #[test]
897 fn fnvxor64_algo_round_trip() {
898 let mut aud = auditor_with_algo(ChecksumAlgo::FnvXor64);
899 aud.register_block("rf".to_owned(), b"fnv check", 0);
900 assert!(matches!(
901 aud.audit_block("rf", b"fnv check"),
902 AuditResult::Passed { .. }
903 ));
904 }
905
906 #[test]
907 fn multicheck_algo_round_trip() {
908 let mut aud = auditor_with_algo(ChecksumAlgo::MultiCheck);
909 aud.register_block("rm".to_owned(), b"multi check", 0);
910 assert!(matches!(
911 aud.audit_block("rm", b"multi check"),
912 AuditResult::Passed { .. }
913 ));
914 }
915
916 #[test]
921 fn large_block_passes() {
922 let mut aud = default_auditor();
923 let data: Vec<u8> = (0u8..=255).cycle().take(65536).collect();
924 aud.register_block("large".to_owned(), &data, 0);
925 assert!(matches!(
926 aud.audit_block("large", &data),
927 AuditResult::Passed { .. }
928 ));
929 }
930
931 #[test]
932 fn single_bit_flip_detected() {
933 let mut aud = default_auditor();
934 let mut data = vec![0xABu8; 512];
935 aud.register_block("flip".to_owned(), &data, 0);
936 data[256] ^= 0x01;
938 assert!(matches!(
939 aud.audit_block("flip", &data),
940 AuditResult::Failed { .. }
941 ));
942 }
943
944 #[test]
945 fn zero_length_block_round_trip() {
946 let mut aud = default_auditor();
947 aud.register_block("empty_block".to_owned(), &[], 0);
948 assert!(matches!(
949 aud.audit_block("empty_block", &[]),
950 AuditResult::Passed { .. }
951 ));
952 }
953
954 #[test]
959 fn repair_record_stores_source() {
960 let mut aud = default_auditor();
961 aud.schedule_repair("src_test".to_owned(), 0, "peer-XYZ".to_owned());
962 let pending = aud.pending_repairs();
963 assert_eq!(pending[0].source, "peer-XYZ");
964 }
965
966 #[test]
971 fn failed_result_contains_both_checksums() {
972 let mut aud = default_auditor();
973 aud.register_block("fc".to_owned(), b"original", 0);
974 match aud.audit_block("fc", b"tampered") {
975 AuditResult::Failed {
976 stored_checksum,
977 computed_checksum,
978 ..
979 } => {
980 assert_ne!(stored_checksum, computed_checksum);
981 }
982 other => panic!("expected Failed, got {other:?}"),
983 }
984 }
985
986 #[test]
987 fn passed_result_contains_correct_checksum() {
988 let mut aud = default_auditor();
989 let data = b"pass me";
990 aud.register_block("pm".to_owned(), data, 0);
991 let expected = compute_checksum(data, &ChecksumAlgo::MultiCheck);
992 match aud.audit_block("pm", data) {
993 AuditResult::Passed { checksum, .. } => assert_eq!(checksum, expected),
994 other => panic!("expected Passed, got {other:?}"),
995 }
996 }
997
998 #[test]
1003 fn registered_block_count() {
1004 let mut aud = default_auditor();
1005 for i in 0u8..7 {
1006 aud.register_block(format!("blk{i}"), &[i], 0);
1007 }
1008 assert_eq!(aud.checksums.len(), 7);
1009 }
1010
1011 #[test]
1016 fn block_checksum_records_size() {
1017 let mut aud = default_auditor();
1018 let data = b"size test";
1019 aud.register_block("sz".to_owned(), data, 42);
1020 let rec = aud.checksums.get("sz").expect("record must exist");
1021 assert_eq!(rec.block_size, data.len());
1022 assert_eq!(rec.computed_at, 42);
1023 }
1024}