1use std::collections::{HashMap, VecDeque};
8
9pub type PfaMessageId = u64;
15
16pub type PfaPacketFragmentationAssembler = PacketFragmentationAssembler;
18
19#[inline]
25pub fn fnv1a_64(data: &[u8]) -> u64 {
26 let mut h: u64 = 14_695_981_039_346_656_037;
27 for &b in data {
28 h ^= b as u64;
29 h = h.wrapping_mul(1_099_511_628_211);
30 }
31 h
32}
33
34#[inline]
36pub fn xorshift64(state: &mut u64) -> u64 {
37 let mut x = *state;
38 x ^= x << 13;
39 x ^= x >> 7;
40 x ^= x << 17;
41 *state = x;
42 x
43}
44
45#[inline]
47fn fnv1a_32(data: &[u8]) -> u32 {
48 (fnv1a_64(data) & 0xFFFF_FFFF) as u32
49}
50
51#[derive(Debug, Clone)]
57pub struct PfaAssemblerConfig {
58 pub mtu: usize,
60 pub max_fragments: u32,
62 pub reassembly_timeout_secs: u64,
64 pub checksum_enabled: bool,
66}
67
68impl Default for PfaAssemblerConfig {
69 fn default() -> Self {
70 Self {
71 mtu: 1_400,
72 max_fragments: 4_096,
73 reassembly_timeout_secs: 30,
74 checksum_enabled: true,
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
85pub struct PfaFragment {
86 pub msg_id: PfaMessageId,
88 pub fragment_index: u32,
90 pub total_fragments: u32,
92 pub offset: usize,
94 pub data: Vec<u8>,
96 pub checksum: u32,
98}
99
100#[derive(Debug)]
102pub struct PfaReassemblyBuffer {
103 pub msg_id: PfaMessageId,
105 pub received: Vec<Option<PfaFragment>>,
107 pub total_fragments: u32,
109 pub created_at: u64,
111 pub last_updated: u64,
113 received_count: u32,
115}
116
117impl PfaReassemblyBuffer {
118 fn new(msg_id: PfaMessageId, total_fragments: u32, now_ts: u64) -> Self {
119 Self {
120 msg_id,
121 received: vec![None; total_fragments as usize],
122 total_fragments,
123 created_at: now_ts,
124 last_updated: now_ts,
125 received_count: 0,
126 }
127 }
128
129 pub fn received_count(&self) -> u32 {
131 self.received_count
132 }
133
134 pub fn is_complete(&self) -> bool {
136 self.received_count == self.total_fragments
137 }
138}
139
140#[derive(Debug, Clone)]
142pub struct PfaFragmentRecord {
143 pub ts: u64,
145 pub msg_id: PfaMessageId,
147 pub fragment_index: u32,
149 pub is_dup: bool,
151 pub assembled: bool,
153}
154
155#[derive(Debug)]
157pub enum PfaReceiveResult {
158 Buffered,
160 Duplicate,
162 Assembled(Vec<u8>),
164 Error(String),
166}
167
168#[derive(Debug, Clone, Default)]
170pub struct PfaAssemblerStats {
171 pub total_fragmented: u64,
173 pub total_assembled: u64,
175 pub total_dropped: u64,
177 pub avg_fragments: f64,
179}
180
181pub struct PacketFragmentationAssembler {
191 config: PfaAssemblerConfig,
192 buffers: HashMap<PfaMessageId, PfaReassemblyBuffer>,
194 fragment_log: VecDeque<PfaFragmentRecord>,
196 rng_state: u64,
198 total_fragmented: u64,
200 total_assembled: u64,
201 total_dropped: u64,
202 fragment_count_sum: u64,
204}
205
206impl PacketFragmentationAssembler {
207 pub fn new(config: PfaAssemblerConfig) -> Self {
211 let seed = fnv1a_64(&config.mtu.to_le_bytes())
213 ^ fnv1a_64(&config.reassembly_timeout_secs.to_le_bytes())
214 ^ 0xDEAD_BEEF_CAFE_BABE;
215 Self {
216 config,
217 buffers: HashMap::new(),
218 fragment_log: VecDeque::with_capacity(1_000),
219 rng_state: seed,
220 total_fragmented: 0,
221 total_assembled: 0,
222 total_dropped: 0,
223 fragment_count_sum: 0,
224 }
225 }
226
227 pub fn with_defaults() -> Self {
229 Self::new(PfaAssemblerConfig::default())
230 }
231
232 fn next_rand(&mut self) -> u64 {
236 xorshift64(&mut self.rng_state)
237 }
238
239 pub fn generate_msg_id(&mut self) -> PfaMessageId {
241 loop {
242 let id = self.next_rand();
243 if id != 0 && !self.buffers.contains_key(&id) {
244 return id;
245 }
246 }
247 }
248
249 pub fn fragment(
260 &mut self,
261 msg_id: PfaMessageId,
262 data: &[u8],
263 ) -> Result<Vec<PfaFragment>, String> {
264 if data.is_empty() {
265 return Ok(Vec::new());
266 }
267
268 let mtu = self.config.mtu.max(1);
269 let total_fragments = data.len().div_ceil(mtu) as u32;
270
271 if total_fragments > self.config.max_fragments {
272 return Err(format!(
273 "message requires {} fragments but max_fragments is {}",
274 total_fragments, self.config.max_fragments
275 ));
276 }
277
278 let mut fragments = Vec::with_capacity(total_fragments as usize);
279 let mut offset = 0usize;
280
281 for idx in 0..total_fragments {
282 let end = (offset + mtu).min(data.len());
283 let payload = data[offset..end].to_vec();
284 let checksum = if self.config.checksum_enabled {
285 fnv1a_32(&payload)
286 } else {
287 0
288 };
289
290 fragments.push(PfaFragment {
291 msg_id,
292 fragment_index: idx,
293 total_fragments,
294 offset,
295 data: payload,
296 checksum,
297 });
298
299 offset = end;
300 }
301
302 self.total_fragmented += 1;
303 self.fragment_count_sum += total_fragments as u64;
304
305 Ok(fragments)
306 }
307
308 pub fn receive_fragment(&mut self, fragment: PfaFragment, now_ts: u64) -> PfaReceiveResult {
315 if self.config.checksum_enabled && !Self::verify_checksum_static(&fragment) {
317 self.total_dropped += 1;
318 self.push_log(PfaFragmentRecord {
319 ts: now_ts,
320 msg_id: fragment.msg_id,
321 fragment_index: fragment.fragment_index,
322 is_dup: false,
323 assembled: false,
324 });
325 return PfaReceiveResult::Error(format!(
326 "checksum mismatch for msg_id={} fragment_index={}",
327 fragment.msg_id, fragment.fragment_index
328 ));
329 }
330
331 if fragment.total_fragments == 0 {
333 self.total_dropped += 1;
334 return PfaReceiveResult::Error("total_fragments must be > 0".into());
335 }
336 if fragment.fragment_index >= fragment.total_fragments {
337 self.total_dropped += 1;
338 return PfaReceiveResult::Error(format!(
339 "fragment_index {} out of range (total={})",
340 fragment.fragment_index, fragment.total_fragments
341 ));
342 }
343 if fragment.total_fragments > self.config.max_fragments {
344 self.total_dropped += 1;
345 return PfaReceiveResult::Error(format!(
346 "total_fragments {} exceeds max_fragments {}",
347 fragment.total_fragments, self.config.max_fragments
348 ));
349 }
350
351 let msg_id = fragment.msg_id;
352 let frag_idx = fragment.fragment_index;
353 let total = fragment.total_fragments;
354
355 let buf = self
357 .buffers
358 .entry(msg_id)
359 .or_insert_with(|| PfaReassemblyBuffer::new(msg_id, total, now_ts));
360
361 if buf.total_fragments != total {
363 self.total_dropped += 1;
364 return PfaReceiveResult::Error(format!(
365 "total_fragments mismatch: buffer expects {} but fragment says {}",
366 buf.total_fragments, total
367 ));
368 }
369
370 if frag_idx as usize >= buf.received.len() {
372 self.total_dropped += 1;
373 return PfaReceiveResult::Error(format!(
374 "fragment_index {} out of buffer range {}",
375 frag_idx,
376 buf.received.len()
377 ));
378 }
379
380 if buf.received[frag_idx as usize].is_some() {
382 self.push_log(PfaFragmentRecord {
383 ts: now_ts,
384 msg_id,
385 fragment_index: frag_idx,
386 is_dup: true,
387 assembled: false,
388 });
389 return PfaReceiveResult::Duplicate;
390 }
391
392 buf.received[frag_idx as usize] = Some(fragment);
394 buf.received_count += 1;
395 buf.last_updated = now_ts;
396 let complete = buf.is_complete();
397
398 if complete {
400 if let Some(completed_buf) = self.buffers.remove(&msg_id) {
402 match Self::assemble_buffer(completed_buf) {
403 Ok(data) => {
404 self.total_assembled += 1;
405 self.push_log(PfaFragmentRecord {
406 ts: now_ts,
407 msg_id,
408 fragment_index: frag_idx,
409 is_dup: false,
410 assembled: true,
411 });
412 return PfaReceiveResult::Assembled(data);
413 }
414 Err(e) => {
415 self.total_dropped += 1;
416 self.push_log(PfaFragmentRecord {
417 ts: now_ts,
418 msg_id,
419 fragment_index: frag_idx,
420 is_dup: false,
421 assembled: false,
422 });
423 return PfaReceiveResult::Error(e);
424 }
425 }
426 }
427 }
428
429 self.push_log(PfaFragmentRecord {
430 ts: now_ts,
431 msg_id,
432 fragment_index: frag_idx,
433 is_dup: false,
434 assembled: false,
435 });
436 PfaReceiveResult::Buffered
437 }
438
439 pub fn reassemble(&mut self, msg_id: PfaMessageId) -> Option<Vec<u8>> {
445 let complete = self
447 .buffers
448 .get(&msg_id)
449 .map(|b| b.is_complete())
450 .unwrap_or(false);
451
452 if !complete {
453 return None;
454 }
455
456 let buf = self.buffers.remove(&msg_id)?;
457 match Self::assemble_buffer(buf) {
458 Ok(data) => {
459 self.total_assembled += 1;
460 Some(data)
461 }
462 Err(_) => {
463 self.total_dropped += 1;
464 None
465 }
466 }
467 }
468
469 fn assemble_buffer(buf: PfaReassemblyBuffer) -> Result<Vec<u8>, String> {
471 let mut indexed: Vec<(u32, Vec<u8>)> = Vec::with_capacity(buf.total_fragments as usize);
473 for (idx, slot) in buf.received.into_iter().enumerate() {
474 match slot {
475 Some(frag) => indexed.push((frag.fragment_index, frag.data)),
476 None => {
477 return Err(format!("missing fragment at slot {}", idx));
478 }
479 }
480 }
481 indexed.sort_unstable_by_key(|(i, _)| *i);
482
483 let total_len: usize = indexed.iter().map(|(_, d)| d.len()).sum();
484 let mut out = Vec::with_capacity(total_len);
485 for (_, data) in indexed {
486 out.extend_from_slice(&data);
487 }
488 Ok(out)
489 }
490
491 pub fn expire_stale(&mut self, now_ts: u64) {
495 let timeout = self.config.reassembly_timeout_secs;
496 let before = self.buffers.len();
497 self.buffers
498 .retain(|_, buf| now_ts.saturating_sub(buf.created_at) < timeout);
499 let dropped_count = before - self.buffers.len();
500 self.total_dropped = self.total_dropped.saturating_add(dropped_count as u64);
501 }
502
503 pub fn pending_messages(&self) -> Vec<(PfaMessageId, u32, u32)> {
507 self.buffers
508 .values()
509 .map(|b| (b.msg_id, b.received_count(), b.total_fragments))
510 .collect()
511 }
512
513 pub fn verify_checksum(&self, fragment: &PfaFragment) -> bool {
515 if !self.config.checksum_enabled {
516 return true;
517 }
518 Self::verify_checksum_static(fragment)
519 }
520
521 fn verify_checksum_static(fragment: &PfaFragment) -> bool {
523 fnv1a_32(&fragment.data) == fragment.checksum
524 }
525
526 pub fn assembler_stats(&self) -> PfaAssemblerStats {
528 let avg_fragments = if self.total_fragmented == 0 {
529 0.0
530 } else {
531 self.fragment_count_sum as f64 / self.total_fragmented as f64
532 };
533 PfaAssemblerStats {
534 total_fragmented: self.total_fragmented,
535 total_assembled: self.total_assembled,
536 total_dropped: self.total_dropped,
537 avg_fragments,
538 }
539 }
540
541 pub fn config(&self) -> &PfaAssemblerConfig {
545 &self.config
546 }
547
548 pub fn pending_count(&self) -> usize {
550 self.buffers.len()
551 }
552
553 pub fn fragment_log(&self) -> &VecDeque<PfaFragmentRecord> {
555 &self.fragment_log
556 }
557
558 pub fn has_pending(&self) -> bool {
560 !self.buffers.is_empty()
561 }
562
563 pub fn get_buffer(&self, msg_id: PfaMessageId) -> Option<&PfaReassemblyBuffer> {
565 self.buffers.get(&msg_id)
566 }
567
568 pub fn drain_log(&mut self) -> Vec<PfaFragmentRecord> {
570 self.fragment_log.drain(..).collect()
571 }
572
573 pub fn reset(&mut self) {
575 self.buffers.clear();
576 self.fragment_log.clear();
577 self.total_fragmented = 0;
578 self.total_assembled = 0;
579 self.total_dropped = 0;
580 self.fragment_count_sum = 0;
581 }
582
583 fn push_log(&mut self, record: PfaFragmentRecord) {
587 if self.fragment_log.len() >= 1_000 {
588 self.fragment_log.pop_front();
589 }
590 self.fragment_log.push_back(record);
591 }
592}
593
594#[cfg(test)]
599mod tests {
600 use super::*;
601
602 fn make_assembler() -> PacketFragmentationAssembler {
605 PacketFragmentationAssembler::with_defaults()
606 }
607
608 fn make_assembler_cfg(
609 mtu: usize,
610 timeout: u64,
611 checksum: bool,
612 ) -> PacketFragmentationAssembler {
613 PacketFragmentationAssembler::new(PfaAssemblerConfig {
614 mtu,
615 max_fragments: 4_096,
616 reassembly_timeout_secs: timeout,
617 checksum_enabled: checksum,
618 })
619 }
620
621 fn receive_all(
622 asm: &mut PacketFragmentationAssembler,
623 frags: Vec<PfaFragment>,
624 ts: u64,
625 ) -> PfaReceiveResult {
626 let len = frags.len();
627 let mut last = PfaReceiveResult::Buffered;
628 for (i, f) in frags.into_iter().enumerate() {
629 last = asm.receive_fragment(f, ts);
630 if i == len - 1 {
631 return last;
632 }
633 }
634 last
635 }
636
637 #[test]
640 fn test_fnv1a_empty() {
641 assert_eq!(fnv1a_64(&[]), 14_695_981_039_346_656_037);
642 }
643
644 #[test]
645 fn test_fnv1a_known_value() {
646 let h = fnv1a_64(b"abc");
648 assert_ne!(h, 0);
649 assert_ne!(h, 14_695_981_039_346_656_037);
650 }
651
652 #[test]
653 fn test_fnv1a_deterministic() {
654 let a = fnv1a_64(b"hello world");
655 let b = fnv1a_64(b"hello world");
656 assert_eq!(a, b);
657 }
658
659 #[test]
660 fn test_fnv1a_different_inputs() {
661 assert_ne!(fnv1a_64(b"foo"), fnv1a_64(b"bar"));
662 }
663
664 #[test]
667 fn test_xorshift_nonzero() {
668 let mut s = 12345u64;
669 let v = xorshift64(&mut s);
670 assert_ne!(v, 0);
671 }
672
673 #[test]
674 fn test_xorshift_changes_state() {
675 let mut s = 99999u64;
676 let a = xorshift64(&mut s);
677 let b = xorshift64(&mut s);
678 assert_ne!(a, b);
679 }
680
681 #[test]
682 fn test_xorshift_reproducible() {
683 let mut s1 = 42u64;
684 let mut s2 = 42u64;
685 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
686 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
687 }
688
689 #[test]
692 fn test_default_config() {
693 let cfg = PfaAssemblerConfig::default();
694 assert_eq!(cfg.mtu, 1_400);
695 assert_eq!(cfg.max_fragments, 4_096);
696 assert_eq!(cfg.reassembly_timeout_secs, 30);
697 assert!(cfg.checksum_enabled);
698 }
699
700 #[test]
703 fn test_new_empty_state() {
704 let asm = make_assembler();
705 assert_eq!(asm.pending_count(), 0);
706 assert!(!asm.has_pending());
707 let stats = asm.assembler_stats();
708 assert_eq!(stats.total_fragmented, 0);
709 assert_eq!(stats.total_assembled, 0);
710 assert_eq!(stats.total_dropped, 0);
711 }
712
713 #[test]
716 fn test_generate_msg_id_nonzero() {
717 let mut asm = make_assembler();
718 let id = asm.generate_msg_id();
719 assert_ne!(id, 0);
720 }
721
722 #[test]
723 fn test_generate_msg_id_unique() {
724 let mut asm = make_assembler();
725 let ids: Vec<u64> = (0..100).map(|_| asm.generate_msg_id()).collect();
726 let unique: std::collections::HashSet<_> = ids.iter().cloned().collect();
727 assert_eq!(unique.len(), 100);
728 }
729
730 #[test]
733 fn test_fragment_empty_data() {
734 let mut asm = make_assembler();
735 let frags = asm
736 .fragment(1, &[])
737 .expect("test: fragment empty data should succeed");
738 assert!(frags.is_empty());
739 }
740
741 #[test]
742 fn test_fragment_single_chunk() {
743 let mut asm = make_assembler_cfg(1_400, 30, true);
744 let data: Vec<u8> = (0..100).collect();
745 let frags = asm
746 .fragment(1, &data)
747 .expect("test: fragment single chunk should succeed");
748 assert_eq!(frags.len(), 1);
749 assert_eq!(frags[0].fragment_index, 0);
750 assert_eq!(frags[0].total_fragments, 1);
751 assert_eq!(frags[0].offset, 0);
752 assert_eq!(frags[0].data, data);
753 }
754
755 #[test]
756 fn test_fragment_exact_mtu() {
757 let mut asm = make_assembler_cfg(10, 30, true);
758 let data = vec![0xABu8; 10];
759 let frags = asm
760 .fragment(42, &data)
761 .expect("test: fragment exact mtu should succeed");
762 assert_eq!(frags.len(), 1);
763 assert_eq!(frags[0].data.len(), 10);
764 }
765
766 #[test]
767 fn test_fragment_two_chunks() {
768 let mut asm = make_assembler_cfg(10, 30, true);
769 let data = vec![0u8; 15];
770 let frags = asm
771 .fragment(1, &data)
772 .expect("test: fragment two chunks should succeed");
773 assert_eq!(frags.len(), 2);
774 assert_eq!(frags[0].data.len(), 10);
775 assert_eq!(frags[1].data.len(), 5);
776 }
777
778 #[test]
779 fn test_fragment_many_chunks() {
780 let mut asm = make_assembler_cfg(8, 30, true);
781 let data = vec![0u8; 100];
782 let frags = asm
783 .fragment(1, &data)
784 .expect("test: fragment many chunks should succeed");
785 assert_eq!(frags.len(), 13); }
787
788 #[test]
789 fn test_fragment_offsets_contiguous() {
790 let mut asm = make_assembler_cfg(10, 30, true);
791 let data: Vec<u8> = (0..35).collect();
792 let frags = asm
793 .fragment(1, &data)
794 .expect("test: fragment offsets contiguous should succeed");
795 let mut expected_offset = 0usize;
796 for f in &frags {
797 assert_eq!(f.offset, expected_offset);
798 expected_offset += f.data.len();
799 }
800 assert_eq!(expected_offset, 35);
801 }
802
803 #[test]
804 fn test_fragment_checksum_set() {
805 let mut asm = make_assembler_cfg(10, 30, true);
806 let data = vec![1u8, 2, 3];
807 let frags = asm
808 .fragment(1, &data)
809 .expect("test: fragment checksum set should succeed");
810 assert_ne!(frags[0].checksum, 0);
811 }
812
813 #[test]
814 fn test_fragment_no_checksum() {
815 let mut asm = make_assembler_cfg(10, 30, false);
816 let data = vec![1u8, 2, 3];
817 let frags = asm
818 .fragment(1, &data)
819 .expect("test: fragment no checksum should succeed");
820 assert_eq!(frags[0].checksum, 0);
821 }
822
823 #[test]
824 fn test_fragment_too_many() {
825 let mut asm = PacketFragmentationAssembler::new(PfaAssemblerConfig {
826 mtu: 1,
827 max_fragments: 4,
828 reassembly_timeout_secs: 30,
829 checksum_enabled: true,
830 });
831 let data = vec![0u8; 10];
832 assert!(asm.fragment(1, &data).is_err());
833 }
834
835 #[test]
836 fn test_fragment_increments_stats() {
837 let mut asm = make_assembler();
838 let data = vec![0u8; 2_800];
839 asm.fragment(1, &data)
840 .expect("test: fragment increments stats should succeed");
841 let stats = asm.assembler_stats();
842 assert_eq!(stats.total_fragmented, 1);
843 assert!(stats.avg_fragments >= 2.0);
844 }
845
846 #[test]
849 fn test_roundtrip_small() {
850 let mut asm = make_assembler_cfg(10, 30, true);
851 let data: Vec<u8> = (0..25u8).collect();
852 let frags = asm
853 .fragment(1, &data)
854 .expect("test: roundtrip small fragment should succeed");
855 let result = receive_all(&mut asm, frags, 0);
856 if let PfaReceiveResult::Assembled(reassembled) = result {
857 assert_eq!(reassembled, data);
858 } else {
859 panic!("expected Assembled, got {:?}", result);
860 }
861 }
862
863 #[test]
864 fn test_roundtrip_exact_mtu() {
865 let mut asm = make_assembler_cfg(8, 30, true);
866 let data = vec![0xFFu8; 8];
867 let frags = asm
868 .fragment(1, &data)
869 .expect("test: roundtrip exact mtu fragment should succeed");
870 let result = receive_all(&mut asm, frags, 0);
871 assert!(matches!(result, PfaReceiveResult::Assembled(d) if d == data));
872 }
873
874 #[test]
875 fn test_roundtrip_single_byte() {
876 let mut asm = make_assembler_cfg(1_400, 30, true);
877 let data = vec![42u8];
878 let frags = asm
879 .fragment(99, &data)
880 .expect("test: roundtrip single byte fragment should succeed");
881 let result = receive_all(&mut asm, frags, 0);
882 assert!(matches!(result, PfaReceiveResult::Assembled(d) if d == data));
883 }
884
885 #[test]
886 fn test_roundtrip_large_payload() {
887 let mut asm = make_assembler_cfg(500, 60, true);
888 let data: Vec<u8> = (0..u8::MAX).cycle().take(10_000).collect();
889 let frags = asm
890 .fragment(7, &data)
891 .expect("test: roundtrip large payload fragment should succeed");
892 let result = receive_all(&mut asm, frags, 0);
893 assert!(matches!(result, PfaReceiveResult::Assembled(d) if d == data));
894 }
895
896 #[test]
897 fn test_roundtrip_no_checksum() {
898 let mut asm = make_assembler_cfg(16, 30, false);
899 let data: Vec<u8> = (0..48).collect();
900 let frags = asm
901 .fragment(5, &data)
902 .expect("test: roundtrip no checksum fragment should succeed");
903 let result = receive_all(&mut asm, frags, 0);
904 assert!(matches!(result, PfaReceiveResult::Assembled(d) if d == data));
905 }
906
907 #[test]
908 fn test_roundtrip_reverse_order() {
909 let mut asm = make_assembler_cfg(10, 60, true);
910 let data: Vec<u8> = (0..30u8).collect();
911 let mut frags = asm
912 .fragment(3, &data)
913 .expect("test: roundtrip reverse order fragment should succeed");
914 frags.reverse();
915 let result = receive_all(&mut asm, frags, 0);
916 assert!(matches!(result, PfaReceiveResult::Assembled(d) if d == data));
917 }
918
919 #[test]
922 fn test_duplicate_fragment() {
923 let mut asm = make_assembler_cfg(10, 30, true);
924 let data = vec![0u8; 20];
925 let frags = asm
926 .fragment(1, &data)
927 .expect("test: duplicate fragment setup should succeed");
928 let dup = frags[0].clone();
929 asm.receive_fragment(frags[0].clone(), 0);
930 let result = asm.receive_fragment(dup, 0);
931 assert!(matches!(result, PfaReceiveResult::Duplicate));
932 }
933
934 #[test]
935 fn test_duplicate_last_fragment() {
936 let mut asm = make_assembler_cfg(10, 30, true);
939 let data = vec![0u8; 30];
940 let frags = asm
941 .fragment(1, &data)
942 .expect("test: duplicate last fragment setup should succeed");
943 assert_eq!(frags.len(), 3);
944 let dup = frags[1].clone();
945 asm.receive_fragment(frags[0].clone(), 0);
947 asm.receive_fragment(frags[1].clone(), 0);
948 let result = asm.receive_fragment(dup, 0);
950 assert!(matches!(result, PfaReceiveResult::Duplicate));
951 }
952
953 #[test]
956 fn test_checksum_mismatch_is_error() {
957 let mut asm = make_assembler_cfg(100, 30, true);
958 let data = vec![1u8; 50];
959 let mut frags = asm
960 .fragment(1, &data)
961 .expect("test: checksum mismatch setup should succeed");
962 frags[0].checksum ^= 0xFFFF_FFFF; let result = asm.receive_fragment(frags.remove(0), 0);
964 assert!(matches!(result, PfaReceiveResult::Error(_)));
965 }
966
967 #[test]
968 fn test_checksum_disabled_ignores_bad_checksum() {
969 let mut asm = make_assembler_cfg(100, 30, false);
970 let data = vec![1u8; 50];
971 let mut frags = asm
972 .fragment(1, &data)
973 .expect("test: checksum disabled setup should succeed");
974 frags[0].checksum = 0xDEAD_BEEF; let result = asm.receive_fragment(frags.remove(0), 0);
976 assert!(matches!(result, PfaReceiveResult::Assembled(_)));
978 }
979
980 #[test]
983 fn test_zero_total_fragments_error() {
984 let mut asm = make_assembler();
985 let bad = PfaFragment {
986 msg_id: 1,
987 fragment_index: 0,
988 total_fragments: 0,
989 offset: 0,
990 data: vec![],
991 checksum: 0,
992 };
993 let result = asm.receive_fragment(bad, 0);
994 assert!(matches!(result, PfaReceiveResult::Error(_)));
995 }
996
997 #[test]
998 fn test_index_gte_total_is_error() {
999 let mut asm = make_assembler();
1000 let bad = PfaFragment {
1001 msg_id: 2,
1002 fragment_index: 5,
1003 total_fragments: 3,
1004 offset: 0,
1005 data: vec![],
1006 checksum: 0,
1007 };
1008 let result = asm.receive_fragment(bad, 0);
1009 assert!(matches!(result, PfaReceiveResult::Error(_)));
1010 }
1011
1012 #[test]
1013 fn test_max_fragments_exceeded_error() {
1014 let mut asm = PacketFragmentationAssembler::new(PfaAssemblerConfig {
1015 mtu: 1,
1016 max_fragments: 2,
1017 reassembly_timeout_secs: 30,
1018 checksum_enabled: false,
1019 });
1020 let bad = PfaFragment {
1021 msg_id: 3,
1022 fragment_index: 0,
1023 total_fragments: 10,
1024 offset: 0,
1025 data: vec![0],
1026 checksum: 0,
1027 };
1028 let result = asm.receive_fragment(bad, 0);
1029 assert!(matches!(result, PfaReceiveResult::Error(_)));
1030 }
1031
1032 #[test]
1035 fn test_reassemble_incomplete_returns_none() {
1036 let mut asm = make_assembler_cfg(10, 30, true);
1037 let data = vec![0u8; 20];
1038 let frags = asm
1039 .fragment(1, &data)
1040 .expect("test: reassemble incomplete setup should succeed");
1041 asm.receive_fragment(frags[0].clone(), 0);
1042 assert!(asm.reassemble(1).is_none());
1044 }
1045
1046 #[test]
1047 fn test_reassemble_unknown_msg_id_returns_none() {
1048 let mut asm = make_assembler();
1049 assert!(asm.reassemble(999_999).is_none());
1050 }
1051
1052 #[test]
1053 fn test_reassemble_after_all_received() {
1054 let mut asm = make_assembler_cfg(10, 30, true);
1055 let data: Vec<u8> = (0..30).collect();
1056 let frags = asm
1057 .fragment(1, &data)
1058 .expect("test: reassemble after all received setup should succeed");
1059 for f in &frags {
1060 asm.receive_fragment(f.clone(), 0);
1061 }
1062 let mut asm2 = make_assembler_cfg(10, 30, true);
1066 let frags2 = asm2
1067 .fragment(2, &data)
1068 .expect("test: reassemble asm2 fragment should succeed");
1069 let last_idx = frags2.len() - 1;
1070 for (i, f) in frags2.into_iter().enumerate() {
1071 if i < last_idx {
1072 asm2.receive_fragment(f, 0);
1073 } else {
1074 assert!(asm2.reassemble(2).is_none());
1076 asm2.receive_fragment(f, 0);
1077 }
1078 }
1079 assert!(asm2.reassemble(2).is_none()); }
1082
1083 #[test]
1086 fn test_expire_stale_removes_old_buffers() {
1087 let mut asm = make_assembler_cfg(10, 10, true);
1088 let data = vec![0u8; 20];
1089 let frags = asm
1090 .fragment(1, &data)
1091 .expect("test: expire stale removes old buffers setup should succeed");
1092 asm.receive_fragment(frags[0].clone(), 0);
1093 assert_eq!(asm.pending_count(), 1);
1094 asm.expire_stale(100); assert_eq!(asm.pending_count(), 0);
1096 }
1097
1098 #[test]
1099 fn test_expire_stale_keeps_fresh_buffers() {
1100 let mut asm = make_assembler_cfg(10, 60, true);
1101 let data = vec![0u8; 20];
1102 let frags = asm
1103 .fragment(1, &data)
1104 .expect("test: expire stale keeps fresh buffers setup should succeed");
1105 asm.receive_fragment(frags[0].clone(), 50);
1106 asm.expire_stale(55); assert_eq!(asm.pending_count(), 1);
1108 }
1109
1110 #[test]
1111 fn test_expire_stale_increments_dropped() {
1112 let mut asm = make_assembler_cfg(10, 5, true);
1113 let data = vec![0u8; 20];
1114 let frags = asm
1115 .fragment(1, &data)
1116 .expect("test: expire stale increments dropped setup should succeed");
1117 asm.receive_fragment(frags[0].clone(), 0);
1118 let before = asm.assembler_stats().total_dropped;
1119 asm.expire_stale(100);
1120 assert!(asm.assembler_stats().total_dropped > before);
1121 }
1122
1123 #[test]
1124 fn test_expire_stale_no_op_on_empty() {
1125 let mut asm = make_assembler();
1126 asm.expire_stale(999_999); assert_eq!(asm.pending_count(), 0);
1128 }
1129
1130 #[test]
1133 fn test_pending_messages_empty() {
1134 let asm = make_assembler();
1135 assert!(asm.pending_messages().is_empty());
1136 }
1137
1138 #[test]
1139 fn test_pending_messages_one_entry() {
1140 let mut asm = make_assembler_cfg(10, 30, true);
1141 let data = vec![0u8; 25];
1142 let frags = asm
1143 .fragment(7, &data)
1144 .expect("test: pending messages one entry setup should succeed");
1145 asm.receive_fragment(frags[0].clone(), 0);
1146 let pending = asm.pending_messages();
1147 assert_eq!(pending.len(), 1);
1148 let (mid, recv, total) = pending[0];
1149 assert_eq!(mid, 7);
1150 assert_eq!(recv, 1);
1151 assert_eq!(total, 3);
1152 }
1153
1154 #[test]
1155 fn test_pending_messages_multiple() {
1156 let mut asm = make_assembler_cfg(10, 30, true);
1157 let data1 = vec![0u8; 20];
1158 let data2 = vec![1u8; 30];
1159 let frags1 = asm
1160 .fragment(10, &data1)
1161 .expect("test: pending messages multiple frags1 should succeed");
1162 let frags2 = asm
1163 .fragment(20, &data2)
1164 .expect("test: pending messages multiple frags2 should succeed");
1165 asm.receive_fragment(frags1[0].clone(), 0);
1166 asm.receive_fragment(frags2[0].clone(), 0);
1167 let pending = asm.pending_messages();
1168 assert_eq!(pending.len(), 2);
1169 }
1170
1171 #[test]
1174 fn test_verify_checksum_valid() {
1175 let asm = make_assembler();
1176 let data = vec![1u8, 2, 3, 4];
1177 let frag = PfaFragment {
1178 msg_id: 1,
1179 fragment_index: 0,
1180 total_fragments: 1,
1181 offset: 0,
1182 checksum: fnv1a_32(&data),
1183 data,
1184 };
1185 assert!(asm.verify_checksum(&frag));
1186 }
1187
1188 #[test]
1189 fn test_verify_checksum_invalid() {
1190 let asm = make_assembler();
1191 let data = vec![1u8, 2, 3, 4];
1192 let frag = PfaFragment {
1193 msg_id: 1,
1194 fragment_index: 0,
1195 total_fragments: 1,
1196 offset: 0,
1197 checksum: 0xDEAD_BEEF,
1198 data,
1199 };
1200 assert!(!asm.verify_checksum(&frag));
1201 }
1202
1203 #[test]
1204 fn test_verify_checksum_disabled() {
1205 let asm = make_assembler_cfg(100, 30, false);
1206 let frag = PfaFragment {
1207 msg_id: 1,
1208 fragment_index: 0,
1209 total_fragments: 1,
1210 offset: 0,
1211 checksum: 0xDEAD_BEEF, data: vec![42u8],
1213 };
1214 assert!(asm.verify_checksum(&frag));
1215 }
1216
1217 #[test]
1220 fn test_stats_after_full_roundtrip() {
1221 let mut asm = make_assembler_cfg(10, 30, true);
1222 let data = vec![0u8; 30];
1223 let frags = asm
1224 .fragment(1, &data)
1225 .expect("test: stats after full roundtrip setup should succeed");
1226 receive_all(&mut asm, frags, 0);
1227 let stats = asm.assembler_stats();
1228 assert_eq!(stats.total_fragmented, 1);
1229 assert_eq!(stats.total_assembled, 1);
1230 assert_eq!(stats.total_dropped, 0);
1231 assert!((stats.avg_fragments - 3.0).abs() < f64::EPSILON);
1232 }
1233
1234 #[test]
1235 fn test_stats_dropped_on_checksum_fail() {
1236 let mut asm = make_assembler_cfg(100, 30, true);
1237 let data = vec![1u8; 50];
1238 let mut frags = asm
1239 .fragment(1, &data)
1240 .expect("test: stats dropped on checksum fail setup should succeed");
1241 frags[0].checksum ^= 0xFF;
1242 asm.receive_fragment(frags.remove(0), 0);
1243 let stats = asm.assembler_stats();
1244 assert_eq!(stats.total_dropped, 1);
1245 }
1246
1247 #[test]
1248 fn test_stats_avg_across_multiple() {
1249 let mut asm = make_assembler_cfg(10, 30, true);
1250 asm.fragment(1, &[0u8; 10])
1251 .expect("test: stats avg fragment 1 should succeed"); asm.fragment(2, &[0u8; 20])
1253 .expect("test: stats avg fragment 2 should succeed"); asm.fragment(3, &[0u8; 30])
1255 .expect("test: stats avg fragment 3 should succeed"); let stats = asm.assembler_stats();
1257 assert_eq!(stats.total_fragmented, 3);
1258 assert!((stats.avg_fragments - 2.0).abs() < f64::EPSILON);
1260 }
1261
1262 #[test]
1265 fn test_log_grows_on_receive() {
1266 let mut asm = make_assembler_cfg(10, 30, true);
1267 let data = vec![0u8; 30];
1268 let frags = asm
1269 .fragment(1, &data)
1270 .expect("test: log grows on receive setup should succeed");
1271 for f in frags {
1272 asm.receive_fragment(f, 0);
1273 }
1274 assert!(!asm.fragment_log().is_empty());
1275 }
1276
1277 #[test]
1278 fn test_log_capped_at_1000() {
1279 let mut asm = make_assembler_cfg(1, 30, false);
1280 for id in 1u64..=1_100 {
1282 let frags = asm
1283 .fragment(id, &[0u8])
1284 .expect("test: log capped at 1000 fragment should succeed");
1285 asm.receive_fragment(
1286 frags
1287 .into_iter()
1288 .next()
1289 .expect("test: log capped at 1000 first fragment should exist"),
1290 0,
1291 );
1292 }
1293 assert_eq!(asm.fragment_log().len(), 1_000);
1294 }
1295
1296 #[test]
1297 fn test_log_drain_empties() {
1298 let mut asm = make_assembler_cfg(1, 30, false);
1299 let frags = asm
1300 .fragment(1, &[42u8])
1301 .expect("test: log drain empties fragment should succeed");
1302 asm.receive_fragment(
1303 frags
1304 .into_iter()
1305 .next()
1306 .expect("test: log drain empties first fragment should exist"),
1307 0,
1308 );
1309 let drained = asm.drain_log();
1310 assert!(!drained.is_empty());
1311 assert!(asm.fragment_log().is_empty());
1312 }
1313
1314 #[test]
1315 fn test_log_assembled_flag() {
1316 let mut asm = make_assembler_cfg(100, 30, true);
1317 let data = vec![0u8; 50];
1318 let frags = asm
1319 .fragment(1, &data)
1320 .expect("test: log assembled flag setup should succeed");
1321 receive_all(&mut asm, frags, 0);
1322 let log: Vec<_> = asm.fragment_log().iter().cloned().collect();
1323 let assembled_entry = log.iter().find(|r| r.assembled);
1324 assert!(assembled_entry.is_some());
1325 }
1326
1327 #[test]
1328 fn test_log_dup_flag() {
1329 let mut asm = make_assembler_cfg(10, 30, true);
1331 let data = vec![0u8; 30];
1332 let frags = asm
1333 .fragment(1, &data)
1334 .expect("test: log dup flag setup should succeed");
1335 assert_eq!(frags.len(), 3);
1336 let dup = frags[0].clone();
1337 asm.receive_fragment(frags[0].clone(), 0);
1339 asm.receive_fragment(frags[1].clone(), 0);
1340 asm.receive_fragment(dup, 0);
1342 let log: Vec<_> = asm.fragment_log().iter().cloned().collect();
1343 let dup_entry = log.iter().find(|r| r.is_dup);
1344 assert!(dup_entry.is_some());
1345 }
1346
1347 #[test]
1350 fn test_reset_clears_all() {
1351 let mut asm = make_assembler_cfg(10, 30, true);
1352 let data = vec![0u8; 20];
1353 let frags = asm
1354 .fragment(1, &data)
1355 .expect("test: reset clears all setup should succeed");
1356 asm.receive_fragment(frags[0].clone(), 0);
1357 asm.reset();
1358 assert_eq!(asm.pending_count(), 0);
1359 assert!(asm.fragment_log().is_empty());
1360 let stats = asm.assembler_stats();
1361 assert_eq!(stats.total_fragmented, 0);
1362 assert_eq!(stats.total_assembled, 0);
1363 }
1364
1365 #[test]
1368 fn test_get_buffer_present() {
1369 let mut asm = make_assembler_cfg(10, 30, true);
1370 let data = vec![0u8; 20];
1371 let frags = asm
1372 .fragment(1, &data)
1373 .expect("test: get buffer present setup should succeed");
1374 asm.receive_fragment(frags[0].clone(), 5);
1375 let buf = asm
1376 .get_buffer(1)
1377 .expect("test: get buffer present should return buffer");
1378 assert_eq!(buf.msg_id, 1);
1379 assert_eq!(buf.created_at, 5);
1380 assert_eq!(buf.received_count(), 1);
1381 }
1382
1383 #[test]
1384 fn test_get_buffer_absent() {
1385 let asm = make_assembler();
1386 assert!(asm.get_buffer(42).is_none());
1387 }
1388
1389 #[test]
1392 fn test_reassembly_buffer_not_complete_initially() {
1393 let buf = PfaReassemblyBuffer::new(1, 3, 0);
1394 assert!(!buf.is_complete());
1395 assert_eq!(buf.received_count(), 0);
1396 }
1397
1398 #[test]
1399 fn test_total_fragments_mismatch_error() {
1400 let mut asm = make_assembler_cfg(10, 30, false);
1401 let f1 = PfaFragment {
1403 msg_id: 99,
1404 fragment_index: 0,
1405 total_fragments: 2,
1406 offset: 0,
1407 data: vec![0u8; 5],
1408 checksum: 0,
1409 };
1410 let f2 = PfaFragment {
1412 msg_id: 99,
1413 fragment_index: 1,
1414 total_fragments: 3,
1415 offset: 5,
1416 data: vec![0u8; 5],
1417 checksum: 0,
1418 };
1419 asm.receive_fragment(f1, 0);
1420 let result = asm.receive_fragment(f2, 0);
1421 assert!(matches!(result, PfaReceiveResult::Error(_)));
1422 }
1423
1424 #[test]
1427 fn test_multiple_messages_in_flight() {
1428 let mut asm = make_assembler_cfg(8, 60, true);
1429 let payload_a: Vec<u8> = (0..24u8).collect();
1430 let payload_b: Vec<u8> = (0..16u8).map(|x| x * 2).collect();
1431
1432 let frags_a = asm
1433 .fragment(100, &payload_a)
1434 .expect("test: multiple messages in flight frags_a should succeed"); let frags_b = asm
1436 .fragment(200, &payload_b)
1437 .expect("test: multiple messages in flight frags_b should succeed"); asm.receive_fragment(frags_a[0].clone(), 0);
1441 asm.receive_fragment(frags_b[0].clone(), 0);
1442 asm.receive_fragment(frags_a[1].clone(), 0);
1443 asm.receive_fragment(frags_b[1].clone(), 0);
1444 let result_a = asm.receive_fragment(frags_a[2].clone(), 0);
1445
1446 assert_eq!(asm.pending_count(), 0); assert!(matches!(result_a, PfaReceiveResult::Assembled(d) if d == payload_a));
1448 }
1449
1450 #[test]
1451 fn test_stats_type_alias() {
1452 let stats: PfaAssemblerStats = PfaAssemblerStats::default();
1453 assert_eq!(stats.total_fragmented, 0);
1454 }
1455
1456 #[test]
1457 fn test_type_alias_packet_fragmentation_assembler() {
1458 let _asm: PfaPacketFragmentationAssembler = PacketFragmentationAssembler::with_defaults();
1459 }
1460}