1use std::collections::HashMap;
9
10pub fn bfs_fnv1a_32(data: &[u8]) -> u32 {
16 let mut h: u64 = 14_695_981_039_346_656_037;
17 for &b in data {
18 h ^= b as u64;
19 h = h.wrapping_mul(1_099_511_628_211);
20 }
21 h as u32
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct FragmentId {
31 pub block_cid: String,
33 pub index: u32,
35}
36
37#[derive(Debug, Clone)]
39pub struct BfsFragment {
40 pub id: FragmentId,
42 pub data: Vec<u8>,
44 pub total_fragments: u32,
46 pub checksum: u32,
48}
49
50impl BfsFragment {
51 pub fn new(
53 block_cid: impl Into<String>,
54 index: u32,
55 data: Vec<u8>,
56 total_fragments: u32,
57 ) -> Self {
58 let checksum = bfs_fnv1a_32(&data);
59 BfsFragment {
60 id: FragmentId {
61 block_cid: block_cid.into(),
62 index,
63 },
64 data,
65 total_fragments,
66 checksum,
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq)]
73pub enum FragmentSetState {
74 Incomplete {
76 received: u32,
78 total: u32,
80 },
81 Complete,
83 Corrupted {
85 bad_indices: Vec<u32>,
87 },
88}
89
90#[derive(Debug, Clone)]
92pub struct FragmentSet {
93 pub block_cid: String,
95 pub total_fragments: u32,
97 pub fragments: HashMap<u32, BfsFragment>,
99 pub created_at: u64,
101 pub last_updated: u64,
103}
104
105impl FragmentSet {
106 fn new(block_cid: String, total_fragments: u32, now: u64) -> Self {
107 FragmentSet {
108 block_cid,
109 total_fragments,
110 fragments: HashMap::new(),
111 created_at: now,
112 last_updated: now,
113 }
114 }
115
116 fn is_complete(&self) -> bool {
118 self.fragments.len() as u32 == self.total_fragments
119 }
120
121 fn state(&self) -> FragmentSetState {
123 if self.is_complete() {
124 let bad: Vec<u32> = self
126 .fragments
127 .iter()
128 .filter(|(_, f)| bfs_fnv1a_32(&f.data) != f.checksum)
129 .map(|(idx, _)| *idx)
130 .collect();
131 if bad.is_empty() {
132 FragmentSetState::Complete
133 } else {
134 let mut sorted = bad;
135 sorted.sort_unstable();
136 FragmentSetState::Corrupted {
137 bad_indices: sorted,
138 }
139 }
140 } else {
141 FragmentSetState::Incomplete {
142 received: self.fragments.len() as u32,
143 total: self.total_fragments,
144 }
145 }
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct AssembledBlock {
152 pub cid: String,
154 pub data: Vec<u8>,
156 pub fragment_count: u32,
158 pub assembled_at: u64,
160}
161
162#[derive(Debug, Clone, PartialEq)]
168pub enum FragmentError {
169 DuplicateFragment {
171 block_cid: String,
173 index: u32,
175 },
176 IndexOutOfRange {
178 index: u32,
180 total: u32,
182 },
183 BlockNotFound(String),
185 AssemblyIncomplete {
187 received: u32,
189 total: u32,
191 },
192 ChecksumMismatch {
194 index: u32,
196 },
197 StoreFull,
199}
200
201impl std::fmt::Display for FragmentError {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 match self {
204 FragmentError::DuplicateFragment { block_cid, index } => {
205 write!(f, "duplicate fragment {index} for block {block_cid}")
206 }
207 FragmentError::IndexOutOfRange { index, total } => {
208 write!(f, "fragment index {index} out of range (total={total})")
209 }
210 FragmentError::BlockNotFound(cid) => {
211 write!(f, "block not found: {cid}")
212 }
213 FragmentError::AssemblyIncomplete { received, total } => {
214 write!(f, "assembly incomplete: {received}/{total} fragments")
215 }
216 FragmentError::ChecksumMismatch { index } => {
217 write!(f, "checksum mismatch for fragment {index}")
218 }
219 FragmentError::StoreFull => write!(f, "store is full"),
220 }
221 }
222}
223
224impl std::error::Error for FragmentError {}
225
226#[derive(Debug, Clone)]
232pub struct FragmentStats {
233 pub pending_sets: usize,
235 pub assembled_blocks: usize,
237 pub total_fragments_stored: usize,
239 pub avg_completion_rate: f64,
241}
242
243#[derive(Debug)]
253pub struct BlockFragmentStore {
254 sets: HashMap<String, FragmentSet>,
256 assembled: HashMap<String, AssembledBlock>,
258 max_pending: usize,
260 max_assembled: usize,
262}
263
264impl BlockFragmentStore {
265 pub fn new(max_pending: usize, max_assembled: usize) -> Self {
274 BlockFragmentStore {
275 sets: HashMap::new(),
276 assembled: HashMap::new(),
277 max_pending,
278 max_assembled,
279 }
280 }
281
282 pub fn store_fragment(
294 &mut self,
295 fragment: BfsFragment,
296 now: u64,
297 ) -> Result<FragmentSetState, FragmentError> {
298 let block_cid = fragment.id.block_cid.clone();
299 let index = fragment.id.index;
300 let total = fragment.total_fragments;
301
302 if total == 0 || index >= total {
304 return Err(FragmentError::IndexOutOfRange { index, total });
305 }
306
307 if self.assembled.contains_key(&block_cid) {
309 return Ok(FragmentSetState::Complete);
311 }
312
313 if !self.sets.contains_key(&block_cid) {
315 if self.sets.len() >= self.max_pending {
317 self.evict_oldest_pending();
318 if self.sets.len() >= self.max_pending {
320 return Err(FragmentError::StoreFull);
321 }
322 }
323 self.sets.insert(
324 block_cid.clone(),
325 FragmentSet::new(block_cid.clone(), total, now),
326 );
327 }
328
329 let set = self
330 .sets
331 .get_mut(&block_cid)
332 .ok_or_else(|| FragmentError::BlockNotFound(block_cid.clone()))?;
333
334 if set.fragments.contains_key(&index) {
336 return Err(FragmentError::DuplicateFragment { block_cid, index });
337 }
338
339 set.fragments.insert(index, fragment);
340 set.last_updated = now;
341
342 if set.is_complete() {
344 match self.assemble_internal(&block_cid, now) {
346 Ok(block) => {
347 if self.assembled.len() >= self.max_assembled {
349 self.evict_oldest_assembled();
350 }
351 self.assembled.insert(block_cid.clone(), block);
352 self.sets.remove(&block_cid);
353 Ok(FragmentSetState::Complete)
354 }
355 Err(FragmentError::ChecksumMismatch { index: bad_idx }) => {
356 Ok(FragmentSetState::Corrupted {
358 bad_indices: vec![bad_idx],
359 })
360 }
361 Err(e) => Err(e),
362 }
363 } else {
364 let state = self
365 .sets
366 .get(&block_cid)
367 .map(|s| s.state())
368 .unwrap_or(FragmentSetState::Incomplete { received: 0, total });
369 Ok(state)
370 }
371 }
372
373 pub fn get_state(&self, block_cid: &str) -> Option<FragmentSetState> {
382 if self.assembled.contains_key(block_cid) {
383 return Some(FragmentSetState::Complete);
384 }
385 self.sets.get(block_cid).map(|s| s.state())
386 }
387
388 pub fn missing_indices(&self, block_cid: &str) -> Vec<u32> {
392 if self.assembled.contains_key(block_cid) {
393 return Vec::new();
394 }
395 let set = match self.sets.get(block_cid) {
396 Some(s) => s,
397 None => return Vec::new(),
398 };
399 (0..set.total_fragments)
400 .filter(|i| !set.fragments.contains_key(i))
401 .collect()
402 }
403
404 pub fn assemble(&mut self, block_cid: &str, now: u64) -> Result<AssembledBlock, FragmentError> {
413 if let Some(block) = self.assembled.get(block_cid) {
414 return Ok(block.clone());
415 }
416
417 let assembled = self.assemble_internal(block_cid, now)?;
418
419 if self.assembled.len() >= self.max_assembled {
420 self.evict_oldest_assembled();
421 }
422 let cid = block_cid.to_owned();
423 self.assembled.insert(cid.clone(), assembled.clone());
424 self.sets.remove(&cid);
425 Ok(assembled)
426 }
427
428 fn assemble_internal(
430 &self,
431 block_cid: &str,
432 now: u64,
433 ) -> Result<AssembledBlock, FragmentError> {
434 let set = self
435 .sets
436 .get(block_cid)
437 .ok_or_else(|| FragmentError::BlockNotFound(block_cid.to_owned()))?;
438
439 if !set.is_complete() {
440 return Err(FragmentError::AssemblyIncomplete {
441 received: set.fragments.len() as u32,
442 total: set.total_fragments,
443 });
444 }
445
446 let mut indices: Vec<u32> = (0..set.total_fragments).collect();
448 indices.sort_unstable();
449
450 let mut data: Vec<u8> = Vec::new();
451 for idx in &indices {
452 let fragment = set
453 .fragments
454 .get(idx)
455 .ok_or_else(|| FragmentError::BlockNotFound(block_cid.to_owned()))?;
456 if bfs_fnv1a_32(&fragment.data) != fragment.checksum {
457 return Err(FragmentError::ChecksumMismatch { index: *idx });
458 }
459 data.extend_from_slice(&fragment.data);
460 }
461
462 Ok(AssembledBlock {
463 cid: block_cid.to_owned(),
464 data,
465 fragment_count: set.total_fragments,
466 assembled_at: now,
467 })
468 }
469
470 pub fn get_assembled(&self, block_cid: &str) -> Option<&[u8]> {
477 self.assembled.get(block_cid).map(|b| b.data.as_slice())
478 }
479
480 pub fn get_assembled_block(&self, block_cid: &str) -> Option<&AssembledBlock> {
482 self.assembled.get(block_cid)
483 }
484
485 pub fn verify_fragment(fragment: &BfsFragment) -> bool {
491 bfs_fnv1a_32(&fragment.data) == fragment.checksum
492 }
493
494 pub fn verify_set(set: &FragmentSet) -> Vec<u32> {
496 let mut bad: Vec<u32> = set
497 .fragments
498 .iter()
499 .filter(|(_, f)| bfs_fnv1a_32(&f.data) != f.checksum)
500 .map(|(idx, _)| *idx)
501 .collect();
502 bad.sort_unstable();
503 bad
504 }
505
506 pub fn evict_stale_pending(&mut self, max_age_ms: u64, now: u64) -> usize {
514 let cutoff = now.saturating_sub(max_age_ms);
515 let before = self.sets.len();
516 self.sets.retain(|_, set| set.created_at >= cutoff);
517 before - self.sets.len()
518 }
519
520 pub fn evict_assembled(&mut self, cid: &str) -> bool {
523 self.assembled.remove(cid).is_some()
524 }
525
526 fn evict_oldest_pending(&mut self) {
528 let oldest = self
529 .sets
530 .iter()
531 .min_by_key(|(_, s)| s.created_at)
532 .map(|(k, _)| k.clone());
533 if let Some(key) = oldest {
534 self.sets.remove(&key);
535 }
536 }
537
538 fn evict_oldest_assembled(&mut self) {
540 let oldest = self
541 .assembled
542 .iter()
543 .min_by_key(|(_, b)| b.assembled_at)
544 .map(|(k, _)| k.clone());
545 if let Some(key) = oldest {
546 self.assembled.remove(&key);
547 }
548 }
549
550 pub fn pending_count(&self) -> usize {
556 self.sets.len()
557 }
558
559 pub fn assembled_count(&self) -> usize {
561 self.assembled.len()
562 }
563
564 pub fn stats(&self) -> FragmentStats {
570 let pending_sets = self.sets.len();
571 let assembled_blocks = self.assembled.len();
572
573 let total_fragments_stored: usize = self.sets.values().map(|s| s.fragments.len()).sum();
574
575 let avg_completion_rate = if pending_sets == 0 {
576 0.0
577 } else {
578 let sum: f64 = self
579 .sets
580 .values()
581 .map(|s| {
582 if s.total_fragments == 0 {
583 0.0
584 } else {
585 s.fragments.len() as f64 / s.total_fragments as f64
586 }
587 })
588 .sum();
589 sum / pending_sets as f64
590 };
591
592 FragmentStats {
593 pending_sets,
594 assembled_blocks,
595 total_fragments_stored,
596 avg_completion_rate,
597 }
598 }
599
600 pub fn pending_cids(&self) -> Vec<&str> {
606 self.sets.keys().map(String::as_str).collect()
607 }
608
609 pub fn assembled_cids(&self) -> Vec<&str> {
611 self.assembled.keys().map(String::as_str).collect()
612 }
613
614 pub fn clear_pending(&mut self) -> usize {
617 let count = self.sets.len();
618 self.sets.clear();
619 count
620 }
621
622 pub fn clear_assembled(&mut self) -> usize {
625 let count = self.assembled.len();
626 self.assembled.clear();
627 count
628 }
629
630 pub fn pending_capacity_remaining(&self) -> usize {
632 self.max_pending.saturating_sub(self.sets.len())
633 }
634}
635
636#[cfg(test)]
641mod tests {
642 use crate::block_fragment_store::{
643 bfs_fnv1a_32, BfsFragment, BlockFragmentStore, FragmentError, FragmentId, FragmentSet,
644 FragmentSetState,
645 };
646
647 fn make_fragment(cid: &str, index: u32, data: &[u8], total: u32) -> BfsFragment {
652 BfsFragment::new(cid, index, data.to_vec(), total)
653 }
654
655 fn make_fragment_tampered(cid: &str, index: u32, data: &[u8], total: u32) -> BfsFragment {
656 let mut f = make_fragment(cid, index, data, total);
657 f.checksum = f.checksum.wrapping_add(1); f
659 }
660
661 fn store_with_defaults() -> BlockFragmentStore {
662 BlockFragmentStore::new(16, 16)
663 }
664
665 #[test]
670 fn test_fnv1a_empty() {
671 let h = bfs_fnv1a_32(&[]);
673 let expected = 14_695_981_039_346_656_037_u64 as u32;
674 assert_eq!(h, expected);
675 }
676
677 #[test]
678 fn test_fnv1a_known_value() {
679 let h = bfs_fnv1a_32(b"abc");
681 assert_eq!(h, 0x0541_574b_u32);
682 }
683
684 #[test]
685 fn test_fnv1a_different_inputs_differ() {
686 assert_ne!(bfs_fnv1a_32(b"hello"), bfs_fnv1a_32(b"world"));
687 }
688
689 #[test]
694 fn test_fragment_new_computes_checksum() {
695 let data = b"test data";
696 let f = BfsFragment::new("cid1", 0, data.to_vec(), 3);
697 assert_eq!(f.checksum, bfs_fnv1a_32(data));
698 }
699
700 #[test]
701 fn test_fragment_id_equality() {
702 let a = FragmentId {
703 block_cid: "x".into(),
704 index: 1,
705 };
706 let b = FragmentId {
707 block_cid: "x".into(),
708 index: 1,
709 };
710 assert_eq!(a, b);
711 }
712
713 #[test]
718 fn test_store_single_fragment_incomplete() {
719 let mut store = store_with_defaults();
720 let f = make_fragment("cid1", 0, b"part0", 3);
721 let state = store.store_fragment(f, 1000).expect("store ok");
722 assert_eq!(
723 state,
724 FragmentSetState::Incomplete {
725 received: 1,
726 total: 3
727 }
728 );
729 }
730
731 #[test]
732 fn test_store_all_fragments_completes() {
733 let mut store = store_with_defaults();
734 store
735 .store_fragment(make_fragment("cid1", 0, b"aa", 2), 100)
736 .expect("ok");
737 let state = store
738 .store_fragment(make_fragment("cid1", 1, b"bb", 2), 200)
739 .expect("ok");
740 assert_eq!(state, FragmentSetState::Complete);
741 }
742
743 #[test]
744 fn test_assembled_block_data_correct() {
745 let mut store = store_with_defaults();
746 store
747 .store_fragment(make_fragment("cid1", 0, b"hello", 2), 1)
748 .expect("ok");
749 store
750 .store_fragment(make_fragment("cid1", 1, b"world", 2), 2)
751 .expect("ok");
752 let data = store.get_assembled("cid1").expect("assembled");
753 assert_eq!(data, b"helloworld");
754 }
755
756 #[test]
757 fn test_fragments_ordered_by_index() {
758 let mut store = store_with_defaults();
759 store
761 .store_fragment(make_fragment("cid2", 2, b"C", 3), 1)
762 .expect("ok");
763 store
764 .store_fragment(make_fragment("cid2", 0, b"A", 3), 2)
765 .expect("ok");
766 store
767 .store_fragment(make_fragment("cid2", 1, b"B", 3), 3)
768 .expect("ok");
769 let data = store.get_assembled("cid2").expect("assembled");
770 assert_eq!(data, b"ABC");
771 }
772
773 #[test]
778 fn test_duplicate_fragment_error() {
779 let mut store = store_with_defaults();
780 store
781 .store_fragment(make_fragment("cid1", 0, b"a", 2), 1)
782 .expect("ok");
783 let err = store
784 .store_fragment(make_fragment("cid1", 0, b"a", 2), 2)
785 .expect_err("dup");
786 assert_eq!(
787 err,
788 FragmentError::DuplicateFragment {
789 block_cid: "cid1".into(),
790 index: 0
791 }
792 );
793 }
794
795 #[test]
796 fn test_index_out_of_range_error() {
797 let mut store = store_with_defaults();
798 let f = make_fragment("cid1", 5, b"x", 3);
799 let err = store.store_fragment(f, 1).expect_err("oob");
800 assert_eq!(err, FragmentError::IndexOutOfRange { index: 5, total: 3 });
801 }
802
803 #[test]
804 fn test_total_fragments_zero_error() {
805 let mut store = store_with_defaults();
806 let f = BfsFragment {
807 id: FragmentId {
808 block_cid: "cid1".into(),
809 index: 0,
810 },
811 data: vec![1],
812 total_fragments: 0,
813 checksum: 0,
814 };
815 let err = store.store_fragment(f, 1).expect_err("zero total");
816 assert_eq!(err, FragmentError::IndexOutOfRange { index: 0, total: 0 });
817 }
818
819 #[test]
820 fn test_block_not_found_for_state() {
821 let store = store_with_defaults();
822 assert!(store.get_state("nonexistent").is_none());
823 }
824
825 #[test]
826 fn test_assemble_not_found_error() {
827 let mut store = store_with_defaults();
828 let err = store.assemble("ghost", 0).expect_err("not found");
829 assert_eq!(err, FragmentError::BlockNotFound("ghost".into()));
830 }
831
832 #[test]
833 fn test_assemble_incomplete_error() {
834 let mut store = store_with_defaults();
835 store
836 .store_fragment(make_fragment("cid1", 0, b"a", 3), 1)
837 .expect("ok");
838 let err = store.assemble("cid1", 2).expect_err("incomplete");
839 assert_eq!(
840 err,
841 FragmentError::AssemblyIncomplete {
842 received: 1,
843 total: 3
844 }
845 );
846 }
847
848 #[test]
853 fn test_verify_fragment_valid() {
854 let f = make_fragment("cid", 0, b"data", 1);
855 assert!(BlockFragmentStore::verify_fragment(&f));
856 }
857
858 #[test]
859 fn test_verify_fragment_invalid() {
860 let f = make_fragment_tampered("cid", 0, b"data", 1);
861 assert!(!BlockFragmentStore::verify_fragment(&f));
862 }
863
864 #[test]
865 fn test_verify_set_all_valid() {
866 let mut set = FragmentSet::new("cid".into(), 2, 0);
867 set.fragments.insert(0, make_fragment("cid", 0, b"a", 2));
868 set.fragments.insert(1, make_fragment("cid", 1, b"b", 2));
869 assert!(BlockFragmentStore::verify_set(&set).is_empty());
870 }
871
872 #[test]
873 fn test_verify_set_one_bad() {
874 let mut set = FragmentSet::new("cid".into(), 2, 0);
875 set.fragments.insert(0, make_fragment("cid", 0, b"a", 2));
876 set.fragments
877 .insert(1, make_fragment_tampered("cid", 1, b"b", 2));
878 let bad = BlockFragmentStore::verify_set(&set);
879 assert_eq!(bad, vec![1u32]);
880 }
881
882 #[test]
883 fn test_corrupted_state_on_bad_checksum() {
884 let mut store = store_with_defaults();
885 store
886 .store_fragment(make_fragment("cid1", 0, b"good", 2), 1)
887 .expect("ok");
888 let state = store
889 .store_fragment(make_fragment_tampered("cid1", 1, b"bad", 2), 2)
890 .expect("stored despite tamper");
891 match state {
893 FragmentSetState::Corrupted { bad_indices } => {
894 assert!(bad_indices.contains(&1));
895 }
896 FragmentSetState::Incomplete { .. } => {}
899 FragmentSetState::Complete => panic!("should not be complete with bad checksum"),
900 }
901 }
902
903 #[test]
904 fn test_assemble_detects_checksum_mismatch() {
905 let mut store = BlockFragmentStore::new(4, 4);
906 store
912 .store_fragment(make_fragment("cid1", 0, b"ok", 2), 1)
913 .expect("ok");
914 let tampered = make_fragment_tampered("cid1", 1, b"bad", 2);
916 let result = store.store_fragment(tampered, 2);
919 match result {
920 Ok(FragmentSetState::Corrupted { .. }) => {} Ok(FragmentSetState::Incomplete { .. }) => {} Ok(FragmentSetState::Complete) => panic!("should not complete with bad checksum"),
923 Err(FragmentError::ChecksumMismatch { .. }) => {} Err(e) => panic!("unexpected error: {e}"),
925 }
926 }
927
928 #[test]
933 fn test_missing_indices_all_missing() {
934 let mut store = store_with_defaults();
935 store
936 .store_fragment(make_fragment("cid1", 0, b"a", 4), 1)
937 .expect("ok");
938 let missing = store.missing_indices("cid1");
939 assert_eq!(missing, vec![1u32, 2, 3]);
940 }
941
942 #[test]
943 fn test_missing_indices_none_when_assembled() {
944 let mut store = store_with_defaults();
945 store
946 .store_fragment(make_fragment("cid1", 0, b"a", 1), 1)
947 .expect("ok");
948 let missing = store.missing_indices("cid1");
949 assert!(missing.is_empty());
950 }
951
952 #[test]
953 fn test_missing_indices_unknown_cid() {
954 let store = store_with_defaults();
955 assert!(store.missing_indices("ghost").is_empty());
956 }
957
958 #[test]
963 fn test_get_state_after_assembly() {
964 let mut store = store_with_defaults();
965 store
966 .store_fragment(make_fragment("cid1", 0, b"x", 1), 1)
967 .expect("ok");
968 assert_eq!(store.get_state("cid1"), Some(FragmentSetState::Complete));
969 }
970
971 #[test]
972 fn test_get_state_incomplete() {
973 let mut store = store_with_defaults();
974 store
975 .store_fragment(make_fragment("cid1", 0, b"x", 3), 1)
976 .expect("ok");
977 let state = store.get_state("cid1").expect("exists");
978 assert_eq!(
979 state,
980 FragmentSetState::Incomplete {
981 received: 1,
982 total: 3
983 }
984 );
985 }
986
987 #[test]
992 fn test_evict_stale_pending_removes_old() {
993 let mut store = store_with_defaults();
994 store
995 .store_fragment(make_fragment("old_cid", 0, b"a", 2), 0)
996 .expect("ok");
997 store
998 .store_fragment(make_fragment("new_cid", 0, b"b", 2), 5000)
999 .expect("ok");
1000 let removed = store.evict_stale_pending(2000, 6000);
1002 assert_eq!(removed, 1);
1003 assert!(store.get_state("old_cid").is_none());
1004 assert!(store.get_state("new_cid").is_some());
1005 }
1006
1007 #[test]
1008 fn test_evict_stale_pending_no_removal_when_fresh() {
1009 let mut store = store_with_defaults();
1010 store
1011 .store_fragment(make_fragment("cid1", 0, b"a", 2), 5000)
1012 .expect("ok");
1013 let removed = store.evict_stale_pending(10000, 6000);
1014 assert_eq!(removed, 0);
1015 }
1016
1017 #[test]
1022 fn test_evict_assembled_removes_block() {
1023 let mut store = store_with_defaults();
1024 store
1025 .store_fragment(make_fragment("cid1", 0, b"x", 1), 1)
1026 .expect("ok");
1027 assert!(store.get_assembled("cid1").is_some());
1028 let removed = store.evict_assembled("cid1");
1029 assert!(removed);
1030 assert!(store.get_assembled("cid1").is_none());
1031 }
1032
1033 #[test]
1034 fn test_evict_assembled_missing_returns_false() {
1035 let mut store = store_with_defaults();
1036 assert!(!store.evict_assembled("ghost"));
1037 }
1038
1039 #[test]
1044 fn test_pending_count_increments() {
1045 let mut store = store_with_defaults();
1046 store
1047 .store_fragment(make_fragment("a", 0, b"x", 2), 1)
1048 .expect("ok");
1049 store
1050 .store_fragment(make_fragment("b", 0, b"y", 2), 2)
1051 .expect("ok");
1052 assert_eq!(store.pending_count(), 2);
1053 }
1054
1055 #[test]
1056 fn test_assembled_count_increments() {
1057 let mut store = store_with_defaults();
1058 store
1059 .store_fragment(make_fragment("a", 0, b"x", 1), 1)
1060 .expect("ok");
1061 store
1062 .store_fragment(make_fragment("b", 0, b"y", 1), 2)
1063 .expect("ok");
1064 assert_eq!(store.assembled_count(), 2);
1065 }
1066
1067 #[test]
1068 fn test_max_pending_evicts_oldest() {
1069 let mut store = BlockFragmentStore::new(2, 16);
1070 store
1071 .store_fragment(make_fragment("a", 0, b"x", 2), 1)
1072 .expect("ok");
1073 store
1074 .store_fragment(make_fragment("b", 0, b"y", 2), 2)
1075 .expect("ok");
1076 store
1078 .store_fragment(make_fragment("c", 0, b"z", 2), 3)
1079 .expect("ok");
1080 assert_eq!(store.pending_count(), 2);
1081 assert!(store.get_state("a").is_none(), "oldest should be evicted");
1082 }
1083
1084 #[test]
1085 fn test_store_full_error_when_max_pending_zero() {
1086 let mut store = BlockFragmentStore::new(0, 16);
1087 let err = store
1088 .store_fragment(make_fragment("cid1", 0, b"x", 2), 1)
1089 .expect_err("store full");
1090 assert_eq!(err, FragmentError::StoreFull);
1091 }
1092
1093 #[test]
1098 fn test_stats_empty_store() {
1099 let store = store_with_defaults();
1100 let s = store.stats();
1101 assert_eq!(s.pending_sets, 0);
1102 assert_eq!(s.assembled_blocks, 0);
1103 assert_eq!(s.total_fragments_stored, 0);
1104 assert_eq!(s.avg_completion_rate, 0.0);
1105 }
1106
1107 #[test]
1108 fn test_stats_with_pending() {
1109 let mut store = store_with_defaults();
1110 store
1111 .store_fragment(make_fragment("cid1", 0, b"a", 4), 1)
1112 .expect("ok");
1113 store
1114 .store_fragment(make_fragment("cid1", 1, b"b", 4), 2)
1115 .expect("ok");
1116 let s = store.stats();
1117 assert_eq!(s.pending_sets, 1);
1118 assert_eq!(s.total_fragments_stored, 2);
1119 let expected_rate = 2.0 / 4.0;
1120 assert!((s.avg_completion_rate - expected_rate).abs() < 1e-9);
1121 }
1122
1123 #[test]
1124 fn test_stats_assembled_counted() {
1125 let mut store = store_with_defaults();
1126 store
1127 .store_fragment(make_fragment("cid1", 0, b"z", 1), 1)
1128 .expect("ok");
1129 let s = store.stats();
1130 assert_eq!(s.assembled_blocks, 1);
1131 assert_eq!(s.pending_sets, 0);
1132 }
1133
1134 #[test]
1139 fn test_multiple_blocks_independent() {
1140 let mut store = store_with_defaults();
1141 for i in 0u32..5 {
1142 let cid = format!("block-{i}");
1143 for j in 0u32..3 {
1144 let data = format!("cid{i}-frag{j}");
1145 store
1146 .store_fragment(
1147 make_fragment(&cid, j, data.as_bytes(), 3),
1148 (i * 10 + j) as u64,
1149 )
1150 .expect("ok");
1151 }
1152 }
1153 assert_eq!(store.assembled_count(), 5);
1154 assert_eq!(store.pending_count(), 0);
1155 }
1156
1157 #[test]
1162 fn test_explicit_assemble_after_all_received() {
1163 let mut store = store_with_defaults();
1164 store
1165 .store_fragment(make_fragment("cid1", 0, b"X", 2), 1)
1166 .expect("ok");
1167 store
1168 .store_fragment(make_fragment("cid1", 1, b"Y", 2), 2)
1169 .expect("ok");
1170 let block = store.assemble("cid1", 99).expect("assembled");
1172 assert_eq!(block.data, b"XY");
1173 }
1174
1175 #[test]
1176 fn test_explicit_assemble_stores_to_cache() {
1177 let mut store = store_with_defaults();
1178 store
1179 .store_fragment(make_fragment("cid1", 0, b"P", 2), 1)
1180 .expect("ok");
1181 store
1182 .store_fragment(make_fragment("cid1", 1, b"Q", 2), 2)
1183 .expect("ok");
1184 let block = store.assemble("cid1", 10).expect("ok");
1185 assert_eq!(block.fragment_count, 2);
1186 assert_eq!(store.get_assembled("cid1"), Some(b"PQ".as_slice()));
1188 }
1189
1190 #[test]
1195 fn test_clear_pending() {
1196 let mut store = store_with_defaults();
1197 store
1198 .store_fragment(make_fragment("a", 0, b"x", 2), 1)
1199 .expect("ok");
1200 store
1201 .store_fragment(make_fragment("b", 0, b"y", 2), 2)
1202 .expect("ok");
1203 let dropped = store.clear_pending();
1204 assert_eq!(dropped, 2);
1205 assert_eq!(store.pending_count(), 0);
1206 }
1207
1208 #[test]
1209 fn test_clear_assembled() {
1210 let mut store = store_with_defaults();
1211 store
1212 .store_fragment(make_fragment("a", 0, b"x", 1), 1)
1213 .expect("ok");
1214 store
1215 .store_fragment(make_fragment("b", 0, b"y", 1), 2)
1216 .expect("ok");
1217 let dropped = store.clear_assembled();
1218 assert_eq!(dropped, 2);
1219 assert_eq!(store.assembled_count(), 0);
1220 }
1221
1222 #[test]
1227 fn test_pending_capacity_remaining() {
1228 let mut store = BlockFragmentStore::new(5, 16);
1229 assert_eq!(store.pending_capacity_remaining(), 5);
1230 store
1231 .store_fragment(make_fragment("a", 0, b"x", 2), 1)
1232 .expect("ok");
1233 assert_eq!(store.pending_capacity_remaining(), 4);
1234 }
1235
1236 #[test]
1241 fn test_assembled_block_fragment_count() {
1242 let mut store = store_with_defaults();
1243 for i in 0u32..4 {
1244 store
1245 .store_fragment(make_fragment("c", i, &[i as u8], 4), i as u64)
1246 .expect("ok");
1247 }
1248 let block = store.get_assembled_block("c").expect("assembled");
1249 assert_eq!(block.fragment_count, 4);
1250 }
1251
1252 #[test]
1253 fn test_assembled_block_cid_matches() {
1254 let mut store = store_with_defaults();
1255 store
1256 .store_fragment(make_fragment("myCid", 0, b"z", 1), 1)
1257 .expect("ok");
1258 let block = store.get_assembled_block("myCid").expect("assembled");
1259 assert_eq!(block.cid, "myCid");
1260 }
1261
1262 #[test]
1267 fn test_pending_cids_listed() {
1268 let mut store = store_with_defaults();
1269 store
1270 .store_fragment(make_fragment("p1", 0, b"a", 2), 1)
1271 .expect("ok");
1272 store
1273 .store_fragment(make_fragment("p2", 0, b"b", 2), 2)
1274 .expect("ok");
1275 let mut cids = store.pending_cids();
1276 cids.sort();
1277 assert_eq!(cids, vec!["p1", "p2"]);
1278 }
1279
1280 #[test]
1281 fn test_assembled_cids_listed() {
1282 let mut store = store_with_defaults();
1283 store
1284 .store_fragment(make_fragment("done1", 0, b"x", 1), 1)
1285 .expect("ok");
1286 store
1287 .store_fragment(make_fragment("done2", 0, b"y", 1), 2)
1288 .expect("ok");
1289 let mut cids = store.assembled_cids();
1290 cids.sort();
1291 assert_eq!(cids, vec!["done1", "done2"]);
1292 }
1293
1294 #[test]
1299 fn test_single_fragment_block() {
1300 let mut store = store_with_defaults();
1301 let data = b"singletons are valid";
1302 store
1303 .store_fragment(make_fragment("single", 0, data, 1), 1)
1304 .expect("ok");
1305 assert_eq!(store.get_assembled("single"), Some(data.as_slice()));
1306 }
1307
1308 #[test]
1313 fn test_large_fragment_count() {
1314 let mut store = BlockFragmentStore::new(4, 4);
1315 let total = 256u32;
1316 for i in 0..total {
1317 let data = vec![i as u8; 64];
1318 store
1319 .store_fragment(make_fragment("big", i, &data, total), i as u64)
1320 .expect("ok");
1321 }
1322 let assembled = store.get_assembled("big").expect("assembled");
1323 assert_eq!(assembled.len(), 256 * 64);
1324 for i in 0..total as usize {
1326 assert_eq!(assembled[i * 64], i as u8);
1327 }
1328 }
1329
1330 #[test]
1335 fn test_assemble_idempotent() {
1336 let mut store = store_with_defaults();
1337 store
1338 .store_fragment(make_fragment("idem", 0, b"A", 1), 1)
1339 .expect("ok");
1340 let b1 = store.assemble("idem", 2).expect("first");
1341 let b2 = store.assemble("idem", 3).expect("second");
1342 assert_eq!(b1.data, b2.data);
1343 }
1344
1345 #[test]
1350 fn test_store_fragment_for_assembled_block_returns_complete() {
1351 let mut store = store_with_defaults();
1352 store
1353 .store_fragment(make_fragment("done", 0, b"X", 1), 1)
1354 .expect("ok");
1355 let state = store
1357 .store_fragment(make_fragment("done", 0, b"Y", 1), 2)
1358 .expect("ok");
1359 assert_eq!(state, FragmentSetState::Complete);
1360 }
1361
1362 #[test]
1367 fn test_avg_completion_rate_two_sets() {
1368 let mut store = store_with_defaults();
1369 store
1371 .store_fragment(make_fragment("s1", 0, b"a", 4), 1)
1372 .expect("ok");
1373 store
1375 .store_fragment(make_fragment("s2", 0, b"a", 4), 2)
1376 .expect("ok");
1377 store
1378 .store_fragment(make_fragment("s2", 1, b"b", 4), 3)
1379 .expect("ok");
1380 store
1381 .store_fragment(make_fragment("s2", 2, b"c", 4), 4)
1382 .expect("ok");
1383 let s = store.stats();
1384 let expected = (0.25 + 0.75) / 2.0;
1385 assert!((s.avg_completion_rate - expected).abs() < 1e-9);
1386 }
1387
1388 #[test]
1393 fn test_error_display_duplicate() {
1394 let e = FragmentError::DuplicateFragment {
1395 block_cid: "c".into(),
1396 index: 2,
1397 };
1398 let s = e.to_string();
1399 assert!(s.contains("duplicate"), "msg: {s}");
1400 }
1401
1402 #[test]
1403 fn test_error_display_store_full() {
1404 let e = FragmentError::StoreFull;
1405 let s = e.to_string();
1406 assert!(s.contains("full"), "msg: {s}");
1407 }
1408
1409 #[test]
1414 fn test_max_assembled_evicts_oldest() {
1415 let mut store = BlockFragmentStore::new(16, 2);
1416 store
1418 .store_fragment(make_fragment("a", 0, b"A", 1), 1)
1419 .expect("ok");
1420 store
1421 .store_fragment(make_fragment("b", 0, b"B", 1), 2)
1422 .expect("ok");
1423 assert_eq!(store.assembled_count(), 2);
1425 store
1427 .store_fragment(make_fragment("c", 0, b"C", 1), 3)
1428 .expect("ok");
1429 assert_eq!(store.assembled_count(), 2);
1430 assert!(
1431 store.get_assembled("a").is_none(),
1432 "oldest assembled evicted"
1433 );
1434 assert!(store.get_assembled("b").is_some());
1435 assert!(store.get_assembled("c").is_some());
1436 }
1437
1438 #[test]
1443 fn test_verify_set_multiple_bad_sorted() {
1444 let mut set = FragmentSet::new("cid".into(), 4, 0);
1445 set.fragments.insert(0, make_fragment("cid", 0, b"ok", 4));
1446 set.fragments
1447 .insert(1, make_fragment_tampered("cid", 1, b"bad1", 4));
1448 set.fragments.insert(2, make_fragment("cid", 2, b"ok2", 4));
1449 set.fragments
1450 .insert(3, make_fragment_tampered("cid", 3, b"bad3", 4));
1451 let bad = BlockFragmentStore::verify_set(&set);
1452 assert_eq!(bad, vec![1u32, 3]);
1453 }
1454}