1use std::collections::HashMap;
7
8#[inline]
13fn fnv1a_checksum(data: &[u8]) -> u64 {
14 let mut hash: u64 = 0xcbf29ce484222325;
15 for &byte in data {
16 hash ^= byte as u64;
17 hash = hash.wrapping_mul(0x100000001b3);
18 }
19 hash
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum VerificationResult {
29 Ok,
31 Corrupted {
33 expected: u64,
35 actual: u64,
37 },
38 Missing,
40}
41
42#[derive(Clone, Debug)]
48pub struct BlockRecord {
49 pub block_id: u64,
51 pub cid: String,
53 pub expected_checksum: u64,
55 pub size_bytes: u64,
57 pub verified_at_tick: Option<u64>,
59 pub last_result: Option<VerificationResult>,
61}
62
63#[derive(Clone, Debug)]
69pub struct VerificationReport {
70 pub total_checked: usize,
72 pub ok_count: usize,
74 pub corrupted_count: usize,
76 pub missing_count: usize,
78 pub corrupted_ids: Vec<u64>,
80 pub missing_ids: Vec<u64>,
82}
83
84impl VerificationReport {
85 #[inline]
87 pub fn is_clean(&self) -> bool {
88 self.corrupted_count == 0 && self.missing_count == 0
89 }
90}
91
92#[derive(Clone, Debug, Default)]
98pub struct VerifierStats {
99 pub total_blocks_registered: usize,
101 pub total_verifications_run: u64,
103 pub total_ok: u64,
105 pub total_corrupted: u64,
107 pub total_missing: u64,
109}
110
111pub struct StorageBlockVerifier {
120 records: HashMap<u64, BlockRecord>,
121 next_block_id: u64,
122 stats: VerifierStats,
123}
124
125impl StorageBlockVerifier {
126 pub fn new() -> Self {
128 Self {
129 records: HashMap::new(),
130 next_block_id: 0,
131 stats: VerifierStats::default(),
132 }
133 }
134
135 pub fn register(&mut self, cid: String, content: &[u8]) -> u64 {
139 let block_id = self.next_block_id;
140 self.next_block_id += 1;
141
142 let expected_checksum = fnv1a_checksum(content);
143 let record = BlockRecord {
144 block_id,
145 cid,
146 expected_checksum,
147 size_bytes: content.len() as u64,
148 verified_at_tick: None,
149 last_result: None,
150 };
151
152 self.records.insert(block_id, record);
153 self.stats.total_blocks_registered += 1;
154
155 block_id
156 }
157
158 pub fn verify_block(
166 &mut self,
167 block_id: u64,
168 content: Option<&[u8]>,
169 current_tick: u64,
170 ) -> VerificationResult {
171 if !self.records.contains_key(&block_id) {
173 self.stats.total_missing += 1;
174 self.stats.total_verifications_run += 1;
175 return VerificationResult::Missing;
176 }
177
178 self.stats.total_verifications_run += 1;
179
180 let content_bytes = match content {
181 None => {
182 self.stats.total_missing += 1;
183 let record = self
184 .records
185 .get_mut(&block_id)
186 .expect("key existence checked above");
187 record.verified_at_tick = Some(current_tick);
188 record.last_result = Some(VerificationResult::Missing);
189 return VerificationResult::Missing;
190 }
191 Some(bytes) => bytes,
192 };
193
194 let record = self
195 .records
196 .get_mut(&block_id)
197 .expect("key existence checked above");
198
199 let actual = fnv1a_checksum(content_bytes);
200 let result = if actual == record.expected_checksum {
201 self.stats.total_ok += 1;
202 VerificationResult::Ok
203 } else {
204 self.stats.total_corrupted += 1;
205 VerificationResult::Corrupted {
206 expected: record.expected_checksum,
207 actual,
208 }
209 };
210
211 record.verified_at_tick = Some(current_tick);
212 record.last_result = Some(result.clone());
213
214 result
215 }
216
217 pub fn verify_batch(
221 &mut self,
222 batch: Vec<(u64, Option<Vec<u8>>)>,
223 current_tick: u64,
224 ) -> VerificationReport {
225 let mut ok_count = 0usize;
226 let mut corrupted_count = 0usize;
227 let mut missing_count = 0usize;
228 let mut corrupted_ids: Vec<u64> = Vec::new();
229 let mut missing_ids: Vec<u64> = Vec::new();
230
231 let total_checked = batch.len();
232
233 for (block_id, content) in batch {
234 let result = self.verify_block(block_id, content.as_deref(), current_tick);
235 match result {
236 VerificationResult::Ok => ok_count += 1,
237 VerificationResult::Corrupted { .. } => {
238 corrupted_count += 1;
239 corrupted_ids.push(block_id);
240 }
241 VerificationResult::Missing => {
242 missing_count += 1;
243 missing_ids.push(block_id);
244 }
245 }
246 }
247
248 corrupted_ids.sort_unstable();
249 missing_ids.sort_unstable();
250
251 VerificationReport {
252 total_checked,
253 ok_count,
254 corrupted_count,
255 missing_count,
256 corrupted_ids,
257 missing_ids,
258 }
259 }
260
261 pub fn get_record(&self, block_id: u64) -> Option<&BlockRecord> {
263 self.records.get(&block_id)
264 }
265
266 pub fn stats(&self) -> &VerifierStats {
268 &self.stats
269 }
270
271 pub fn unverified_blocks(&self) -> Vec<u64> {
273 let mut ids: Vec<u64> = self
274 .records
275 .values()
276 .filter(|r| r.verified_at_tick.is_none())
277 .map(|r| r.block_id)
278 .collect();
279 ids.sort_unstable();
280 ids
281 }
282
283 pub fn corrupted_blocks(&self) -> Vec<u64> {
285 let mut ids: Vec<u64> = self
286 .records
287 .values()
288 .filter(|r| matches!(&r.last_result, Some(VerificationResult::Corrupted { .. })))
289 .map(|r| r.block_id)
290 .collect();
291 ids.sort_unstable();
292 ids
293 }
294}
295
296impl Default for StorageBlockVerifier {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302#[cfg(test)]
307mod tests {
308 use super::*;
309
310 fn make_verifier() -> StorageBlockVerifier {
315 StorageBlockVerifier::new()
316 }
317
318 fn expected_checksum(data: &[u8]) -> u64 {
319 fnv1a_checksum(data)
320 }
321
322 #[test]
327 fn test_register_computes_correct_checksum() {
328 let mut v = make_verifier();
329 let content = b"hello world";
330 let id = v.register("cid-1".into(), content);
331 let record = v.get_record(id).expect("record must exist");
332 assert_eq!(record.expected_checksum, expected_checksum(content));
333 }
334
335 #[test]
336 fn test_register_stores_size() {
337 let mut v = make_verifier();
338 let content = b"abcde";
339 let id = v.register("cid-size".into(), content);
340 let record = v.get_record(id).expect("record must exist");
341 assert_eq!(record.size_bytes, 5);
342 }
343
344 #[test]
345 fn test_register_stores_cid() {
346 let mut v = make_verifier();
347 let id = v.register("my-cid".into(), b"data");
348 let record = v.get_record(id).expect("record must exist");
349 assert_eq!(record.cid, "my-cid");
350 }
351
352 #[test]
353 fn test_register_increments_block_id() {
354 let mut v = make_verifier();
355 let id0 = v.register("cid-0".into(), b"a");
356 let id1 = v.register("cid-1".into(), b"b");
357 let id2 = v.register("cid-2".into(), b"c");
358 assert_eq!(id0, 0);
359 assert_eq!(id1, 1);
360 assert_eq!(id2, 2);
361 }
362
363 #[test]
364 fn test_register_increments_stats() {
365 let mut v = make_verifier();
366 v.register("a".into(), b"x");
367 v.register("b".into(), b"y");
368 assert_eq!(v.stats().total_blocks_registered, 2);
369 }
370
371 #[test]
372 fn test_register_no_verified_at_tick() {
373 let mut v = make_verifier();
374 let id = v.register("cid".into(), b"data");
375 let record = v.get_record(id).expect("record must exist");
376 assert!(record.verified_at_tick.is_none());
377 assert!(record.last_result.is_none());
378 }
379
380 #[test]
385 fn test_verify_block_ok_when_content_matches() {
386 let mut v = make_verifier();
387 let content = b"consistent data";
388 let id = v.register("cid".into(), content);
389 let result = v.verify_block(id, Some(content), 1);
390 assert_eq!(result, VerificationResult::Ok);
391 }
392
393 #[test]
394 fn test_verify_block_ok_updates_record() {
395 let mut v = make_verifier();
396 let content = b"data";
397 let id = v.register("cid".into(), content);
398 v.verify_block(id, Some(content), 42);
399 let record = v.get_record(id).expect("record must exist");
400 assert_eq!(record.verified_at_tick, Some(42));
401 assert_eq!(record.last_result, Some(VerificationResult::Ok));
402 }
403
404 #[test]
409 fn test_verify_block_corrupted_when_content_changed() {
410 let mut v = make_verifier();
411 let id = v.register("cid".into(), b"original");
412 let result = v.verify_block(id, Some(b"modified"), 1);
413 assert!(matches!(result, VerificationResult::Corrupted { .. }));
414 }
415
416 #[test]
417 fn test_verify_block_corrupted_expected_and_actual() {
418 let mut v = make_verifier();
419 let original = b"original";
420 let modified = b"modified!!";
421 let id = v.register("cid".into(), original);
422 let expected_cs = expected_checksum(original);
423 let actual_cs = expected_checksum(modified);
424 let result = v.verify_block(id, Some(modified), 1);
425 assert_eq!(
426 result,
427 VerificationResult::Corrupted {
428 expected: expected_cs,
429 actual: actual_cs,
430 }
431 );
432 }
433
434 #[test]
435 fn test_verify_block_corrupted_updates_record() {
436 let mut v = make_verifier();
437 let id = v.register("cid".into(), b"a");
438 v.verify_block(id, Some(b"b"), 7);
439 let record = v.get_record(id).expect("record must exist");
440 assert_eq!(record.verified_at_tick, Some(7));
441 assert!(matches!(
442 record.last_result,
443 Some(VerificationResult::Corrupted { .. })
444 ));
445 }
446
447 #[test]
452 fn test_verify_block_missing_when_not_registered() {
453 let mut v = make_verifier();
454 let result = v.verify_block(999, Some(b"some data"), 1);
455 assert_eq!(result, VerificationResult::Missing);
456 }
457
458 #[test]
459 fn test_verify_block_missing_when_content_none() {
460 let mut v = make_verifier();
461 let id = v.register("cid".into(), b"data");
462 let result = v.verify_block(id, None, 5);
463 assert_eq!(result, VerificationResult::Missing);
464 }
465
466 #[test]
467 fn test_verify_block_none_updates_record() {
468 let mut v = make_verifier();
469 let id = v.register("cid".into(), b"data");
470 v.verify_block(id, None, 10);
471 let record = v.get_record(id).expect("record must exist");
472 assert_eq!(record.verified_at_tick, Some(10));
473 assert_eq!(record.last_result, Some(VerificationResult::Missing));
474 }
475
476 #[test]
481 fn test_stats_total_verifications_run() {
482 let mut v = make_verifier();
483 let id = v.register("cid".into(), b"data");
484 v.verify_block(id, Some(b"data"), 1);
485 v.verify_block(id, Some(b"wrong"), 2);
486 v.verify_block(id, None, 3);
487 v.verify_block(9999, Some(b"x"), 4); assert_eq!(v.stats().total_verifications_run, 4);
489 }
490
491 #[test]
492 fn test_stats_total_ok() {
493 let mut v = make_verifier();
494 let id = v.register("cid".into(), b"data");
495 v.verify_block(id, Some(b"data"), 1);
496 v.verify_block(id, Some(b"data"), 2);
497 assert_eq!(v.stats().total_ok, 2);
498 }
499
500 #[test]
501 fn test_stats_total_corrupted() {
502 let mut v = make_verifier();
503 let id = v.register("cid".into(), b"data");
504 v.verify_block(id, Some(b"bad"), 1);
505 v.verify_block(id, Some(b"worse"), 2);
506 assert_eq!(v.stats().total_corrupted, 2);
507 }
508
509 #[test]
510 fn test_stats_total_missing() {
511 let mut v = make_verifier();
512 let id = v.register("cid".into(), b"data");
513 v.verify_block(id, None, 1); v.verify_block(9999, Some(b"x"), 2); assert_eq!(v.stats().total_missing, 2);
516 }
517
518 #[test]
519 fn test_stats_accumulate_across_calls() {
520 let mut v = make_verifier();
521 let id = v.register("cid".into(), b"hello");
522 v.verify_block(id, Some(b"hello"), 1); v.verify_block(id, Some(b"world"), 2); v.verify_block(id, None, 3); let s = v.stats();
526 assert_eq!(s.total_ok, 1);
527 assert_eq!(s.total_corrupted, 1);
528 assert_eq!(s.total_missing, 1);
529 assert_eq!(s.total_verifications_run, 3);
530 }
531
532 #[test]
537 fn test_verify_batch_report_counts() {
538 let mut v = make_verifier();
539 let id_ok = v.register("ok".into(), b"good");
540 let id_bad = v.register("bad".into(), b"original");
541 let id_reg = v.register("none".into(), b"data");
542
543 let batch = vec![
544 (id_ok, Some(b"good".to_vec())),
545 (id_bad, Some(b"corrupted".to_vec())),
546 (id_reg, None),
547 (9999, Some(b"x".to_vec())), ];
549
550 let report = v.verify_batch(batch, 1);
551 assert_eq!(report.total_checked, 4);
552 assert_eq!(report.ok_count, 1);
553 assert_eq!(report.corrupted_count, 1);
554 assert_eq!(report.missing_count, 2);
555 }
556
557 #[test]
558 fn test_verify_batch_sorted_ids() {
559 let mut v = make_verifier();
560 let id0 = v.register("c0".into(), b"a");
562 let id1 = v.register("c1".into(), b"b");
563 let id2 = v.register("c2".into(), b"c");
564
565 let batch = vec![
567 (id2, Some(b"z".to_vec())), (id0, Some(b"z".to_vec())), (id1, None), ];
571 let report = v.verify_batch(batch, 1);
572 assert_eq!(report.corrupted_ids, vec![id0, id2]);
573 assert_eq!(report.missing_ids, vec![id1]);
574 }
575
576 #[test]
581 fn test_report_is_clean_true() {
582 let mut v = make_verifier();
583 let id = v.register("cid".into(), b"data");
584 let report = v.verify_batch(vec![(id, Some(b"data".to_vec()))], 1);
585 assert!(report.is_clean());
586 }
587
588 #[test]
589 fn test_report_is_clean_false_when_corrupted() {
590 let mut v = make_verifier();
591 let id = v.register("cid".into(), b"data");
592 let report = v.verify_batch(vec![(id, Some(b"wrong".to_vec()))], 1);
593 assert!(!report.is_clean());
594 }
595
596 #[test]
597 fn test_report_is_clean_false_when_missing() {
598 let mut v = make_verifier();
599 let id = v.register("cid".into(), b"data");
600 let report = v.verify_batch(vec![(id, None)], 1);
601 assert!(!report.is_clean());
602 }
603
604 #[test]
609 fn test_unverified_blocks_returns_all_initially() {
610 let mut v = make_verifier();
611 v.register("a".into(), b"1");
612 v.register("b".into(), b"2");
613 v.register("c".into(), b"3");
614 let unverified = v.unverified_blocks();
615 assert_eq!(unverified, vec![0, 1, 2]);
616 }
617
618 #[test]
619 fn test_unverified_blocks_excludes_verified() {
620 let mut v = make_verifier();
621 let id0 = v.register("a".into(), b"1");
622 v.register("b".into(), b"2");
623 v.verify_block(id0, Some(b"1"), 1);
624 let unverified = v.unverified_blocks();
625 assert_eq!(unverified, vec![1]);
626 }
627
628 #[test]
629 fn test_unverified_blocks_empty_when_all_verified() {
630 let mut v = make_verifier();
631 let id0 = v.register("a".into(), b"x");
632 let id1 = v.register("b".into(), b"y");
633 v.verify_block(id0, Some(b"x"), 1);
634 v.verify_block(id1, Some(b"y"), 1);
635 assert!(v.unverified_blocks().is_empty());
636 }
637
638 #[test]
643 fn test_corrupted_blocks_returns_only_corrupted() {
644 let mut v = make_verifier();
645 let id0 = v.register("a".into(), b"original");
646 let id1 = v.register("b".into(), b"good");
647 let id2 = v.register("c".into(), b"also original");
648
649 v.verify_block(id0, Some(b"changed"), 1); v.verify_block(id1, Some(b"good"), 1); v.verify_block(id2, Some(b"also changed"), 1); let corrupted = v.corrupted_blocks();
654 assert_eq!(corrupted, vec![id0, id2]);
655 }
656
657 #[test]
658 fn test_corrupted_blocks_empty_initially() {
659 let mut v = make_verifier();
660 v.register("a".into(), b"x");
661 assert!(v.corrupted_blocks().is_empty());
662 }
663
664 #[test]
665 fn test_corrupted_blocks_not_including_missing() {
666 let mut v = make_verifier();
667 let id = v.register("a".into(), b"data");
668 v.verify_block(id, None, 1); assert!(v.corrupted_blocks().is_empty());
670 }
671
672 #[test]
677 fn test_batch_mixed_results_all_categories() {
678 let mut v = make_verifier();
679 let id_ok = v.register("ok".into(), b"abc");
680 let id_bad = v.register("bad".into(), b"xyz");
681 let id_none = v.register("none".into(), b"zzz");
682
683 let batch = vec![
684 (id_ok, Some(b"abc".to_vec())),
685 (id_bad, Some(b"XYZ-TAMPERED".to_vec())),
686 (id_none, None),
687 (42, Some(b"phantom".to_vec())), ];
689
690 let report = v.verify_batch(batch, 100);
691 assert_eq!(report.total_checked, 4);
692 assert_eq!(report.ok_count, 1);
693 assert_eq!(report.corrupted_count, 1);
694 assert_eq!(report.missing_count, 2);
695 assert!(!report.is_clean());
696
697 let s = v.stats();
699 assert_eq!(s.total_ok, 1);
700 assert_eq!(s.total_corrupted, 1);
701 assert_eq!(s.total_missing, 2);
702 assert_eq!(s.total_verifications_run, 4);
703 }
704
705 #[test]
706 fn test_empty_batch_produces_clean_report() {
707 let mut v = make_verifier();
708 let report = v.verify_batch(vec![], 1);
709 assert_eq!(report.total_checked, 0);
710 assert!(report.is_clean());
711 }
712
713 #[test]
714 fn test_fnv1a_deterministic() {
715 let data = b"determinism test";
716 let a = fnv1a_checksum(data);
717 let b = fnv1a_checksum(data);
718 assert_eq!(a, b);
719 }
720
721 #[test]
722 fn test_fnv1a_different_inputs_differ() {
723 let a = fnv1a_checksum(b"alpha");
724 let b = fnv1a_checksum(b"beta");
725 assert_ne!(a, b);
726 }
727
728 #[test]
729 fn test_get_record_returns_none_for_unknown() {
730 let v = make_verifier();
731 assert!(v.get_record(0).is_none());
732 }
733}