1extern crate alloc;
26use alloc::collections::{BTreeMap, BTreeSet};
27use alloc::vec;
28use alloc::vec::Vec;
29
30use crate::submessages::{DataFragSubmessage, FragmentNumberSet};
31use crate::wire_types::{FragmentNumber, SequenceNumber};
32
33pub const DEFAULT_MAX_PENDING_SNS: usize = 64;
35pub const DEFAULT_MAX_SAMPLE_BYTES: usize = 1024 * 1024;
39pub const DEFAULT_MAX_FRAGMENT_SIZE: u16 = u16::MAX;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct CompletedSample {
45 pub sequence_number: SequenceNumber,
47 pub payload: Vec<u8>,
49}
50
51#[derive(Debug, Clone, Copy)]
53pub struct AssemblerCaps {
54 pub max_pending_sns: usize,
56 pub max_sample_bytes: usize,
58 pub max_fragment_size: u16,
60}
61
62impl Default for AssemblerCaps {
63 fn default() -> Self {
66 Self {
67 max_pending_sns: DEFAULT_MAX_PENDING_SNS,
68 max_sample_bytes: DEFAULT_MAX_SAMPLE_BYTES,
69 max_fragment_size: DEFAULT_MAX_FRAGMENT_SIZE,
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76struct FragmentBuffer {
77 sample_size: u32,
78 fragment_size: u16,
79 total_fragments: u32,
80 received: BTreeSet<FragmentNumber>,
81 data: Vec<u8>,
82}
83
84impl FragmentBuffer {
85 fn new(sample_size: u32, fragment_size: u16) -> Self {
86 let total = if fragment_size == 0 {
87 0
88 } else {
89 sample_size.div_ceil(u32::from(fragment_size))
90 };
91 Self {
92 sample_size,
93 fragment_size,
94 total_fragments: total,
95 received: BTreeSet::new(),
96 data: vec![0u8; sample_size as usize],
97 }
98 }
99
100 fn is_complete(&self) -> bool {
101 self.total_fragments > 0 && self.received.len() as u32 == self.total_fragments
102 }
103
104 fn missing(&self) -> FragmentNumberSet {
105 if self.total_fragments == 0 {
106 return FragmentNumberSet::from_missing(FragmentNumber(1), &[]);
107 }
108 let mut missing_nums = Vec::new();
109 for f in 1..=self.total_fragments {
110 let fnum = FragmentNumber(f);
111 if !self.received.contains(&fnum) {
112 missing_nums.push(fnum);
113 }
114 }
115 let base = missing_nums
116 .first()
117 .copied()
118 .unwrap_or(FragmentNumber(self.total_fragments.saturating_add(1)));
119 FragmentNumberSet::from_missing(base, &missing_nums)
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130#[non_exhaustive]
131pub enum DropReason {
132 SampleTooLarge,
134 FragmentSizeInvalid,
136 FragmentIndexZero,
138 FragmentIndexOutOfRange,
140 PayloadSizeMismatch,
142 InconsistentWithBuffered,
145 FragmentsInSubmessageInvalid,
147 PendingSnsCapExceeded,
150 AssemblerDisabled,
152}
153
154impl DropReason {
155 #[must_use]
158 pub const fn as_str(self) -> &'static str {
159 match self {
160 Self::SampleTooLarge => "sample_too_large",
161 Self::FragmentSizeInvalid => "fragment_size_invalid",
162 Self::FragmentIndexZero => "fragment_index_zero",
163 Self::FragmentIndexOutOfRange => "fragment_index_out_of_range",
164 Self::PayloadSizeMismatch => "payload_size_mismatch",
165 Self::InconsistentWithBuffered => "inconsistent_with_buffered",
166 Self::FragmentsInSubmessageInvalid => "fragments_in_submessage_invalid",
167 Self::PendingSnsCapExceeded => "pending_sns_cap_exceeded",
168 Self::AssemblerDisabled => "assembler_disabled",
169 }
170 }
171}
172
173impl core::fmt::Display for DropReason {
174 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
175 f.write_str(self.as_str())
176 }
177}
178
179#[derive(Debug, Clone, Default)]
184pub struct FragmentAssembler {
185 buffers: BTreeMap<SequenceNumber, FragmentBuffer>,
186 caps: AssemblerCaps,
187 drop_count: u64,
188 last_drop_reason: Option<DropReason>,
189}
190
191impl FragmentAssembler {
192 #[must_use]
194 pub fn new(caps: AssemblerCaps) -> Self {
195 Self {
196 buffers: BTreeMap::new(),
197 caps,
198 drop_count: 0,
199 last_drop_reason: None,
200 }
201 }
202
203 #[must_use]
205 pub fn len(&self) -> usize {
206 self.buffers.len()
207 }
208
209 #[must_use]
211 pub fn is_empty(&self) -> bool {
212 self.buffers.is_empty()
213 }
214
215 #[must_use]
218 pub fn drop_count(&self) -> u64 {
219 self.drop_count
220 }
221
222 #[must_use]
226 pub fn last_drop_reason(&self) -> Option<DropReason> {
227 self.last_drop_reason
228 }
229
230 pub fn reset_diagnostics(&mut self) {
234 self.drop_count = 0;
235 self.last_drop_reason = None;
236 }
237
238 #[must_use]
240 pub fn has_gaps(&self) -> bool {
241 self.buffers.values().any(|b| !b.is_complete())
242 }
243
244 pub fn incomplete_sns(&self) -> impl Iterator<Item = SequenceNumber> + '_ {
246 self.buffers
247 .iter()
248 .filter(|(_, b)| !b.is_complete())
249 .map(|(sn, _)| *sn)
250 }
251
252 #[must_use]
255 pub fn missing_fragments(&self, sn: SequenceNumber) -> FragmentNumberSet {
256 match self.buffers.get(&sn) {
257 Some(b) => b.missing(),
258 None => FragmentNumberSet::from_missing(FragmentNumber(1), &[]),
259 }
260 }
261
262 pub fn discard(&mut self, sn: SequenceNumber) -> bool {
265 self.buffers.remove(&sn).is_some()
266 }
267
268 pub fn insert(&mut self, df: &DataFragSubmessage) -> Option<CompletedSample> {
275 if df.fragment_size == 0 || df.fragment_size > self.caps.max_fragment_size {
277 self.record_drop(DropReason::FragmentSizeInvalid);
278 return None;
279 }
280 if df.fragments_in_submessage == 0 {
281 self.record_drop(DropReason::FragmentsInSubmessageInvalid);
282 return None;
283 }
284 if df.sample_size as usize > self.caps.max_sample_bytes {
285 self.record_drop(DropReason::SampleTooLarge);
286 return None;
287 }
288 if df.fragment_starting_num.0 == 0 {
289 self.record_drop(DropReason::FragmentIndexZero);
290 return None;
291 }
292
293 let total_fragments = df.sample_size.div_ceil(u32::from(df.fragment_size));
295 let last_frag = df
296 .fragment_starting_num
297 .0
298 .checked_add(u32::from(df.fragments_in_submessage) - 1)
299 .unwrap_or(u32::MAX);
300 if last_frag > total_fragments {
301 self.record_drop(DropReason::FragmentIndexOutOfRange);
302 return None;
303 }
304
305 if !self.buffers.contains_key(&df.writer_sn)
307 && self.buffers.len() >= self.caps.max_pending_sns
308 {
309 let Some(&oldest) = self.buffers.keys().next() else {
312 self.record_drop(DropReason::AssemblerDisabled);
314 return None;
315 };
316 self.buffers.remove(&oldest);
317 self.record_drop(DropReason::PendingSnsCapExceeded);
318 }
319
320 let buffer = match self.buffers.get_mut(&df.writer_sn) {
322 Some(existing) => {
323 if existing.sample_size != df.sample_size
324 || existing.fragment_size != df.fragment_size
325 {
326 self.record_drop(DropReason::InconsistentWithBuffered);
327 return None;
328 }
329 existing
330 }
331 None => {
332 self.buffers.insert(
333 df.writer_sn,
334 FragmentBuffer::new(df.sample_size, df.fragment_size),
335 );
336 self.buffers.get_mut(&df.writer_sn)?
337 }
338 };
339
340 let frag_size_usize = buffer.fragment_size as usize;
342 let frag_count = df.fragments_in_submessage as usize;
343 let first_idx = (df.fragment_starting_num.0 - 1) as usize;
344 let byte_start = first_idx * frag_size_usize;
345 let expected_last_frag = core::cmp::min(last_frag, buffer.total_fragments);
346 let full_portion = (frag_count - 1) * frag_size_usize;
349 let tail_size = if expected_last_frag == buffer.total_fragments {
350 buffer.sample_size as usize - ((buffer.total_fragments - 1) as usize) * frag_size_usize
352 } else {
353 frag_size_usize
354 };
355 let expected_len = full_portion + tail_size;
356 let is_last = expected_last_frag == buffer.total_fragments;
362 let too_short = df.serialized_payload.len() < expected_len;
363 let non_last_mismatch = !is_last && df.serialized_payload.len() != expected_len;
364 if too_short || non_last_mismatch {
365 self.record_drop(DropReason::PayloadSizeMismatch);
366 return None;
367 }
368
369 let data_end = byte_start + expected_len;
372 if data_end > buffer.data.len() {
373 self.record_drop(DropReason::PayloadSizeMismatch);
374 return None;
375 }
376 buffer.data[byte_start..data_end].copy_from_slice(&df.serialized_payload[..expected_len]);
377 for f in 0..df.fragments_in_submessage as u32 {
378 buffer
379 .received
380 .insert(FragmentNumber(df.fragment_starting_num.0 + f));
381 }
382
383 if buffer.is_complete() {
384 let buf = self.buffers.remove(&df.writer_sn)?;
386 return Some(CompletedSample {
387 sequence_number: df.writer_sn,
388 payload: buf.data,
389 });
390 }
391 None
392 }
393
394 fn record_drop(&mut self, reason: DropReason) {
395 self.drop_count = self.drop_count.saturating_add(1);
396 self.last_drop_reason = Some(reason);
397 }
398}
399
400#[cfg(test)]
401#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
402mod tests {
403 use super::*;
404 use crate::wire_types::EntityId;
405
406 fn wid() -> EntityId {
407 EntityId::user_writer_with_key([0x10, 0x20, 0x30])
408 }
409 fn rid() -> EntityId {
410 EntityId::user_reader_with_key([0x40, 0x50, 0x60])
411 }
412
413 fn df(
414 sn: i64,
415 starting: u32,
416 count: u16,
417 frag_size: u16,
418 sample_size: u32,
419 payload: Vec<u8>,
420 ) -> DataFragSubmessage {
421 DataFragSubmessage {
422 extra_flags: 0,
423 reader_id: rid(),
424 writer_id: wid(),
425 writer_sn: SequenceNumber(sn),
426 fragment_starting_num: FragmentNumber(starting),
427 fragments_in_submessage: count,
428 fragment_size: frag_size,
429 sample_size,
430 serialized_payload: alloc::sync::Arc::from(payload),
431 inline_qos_flag: false,
432 hash_key_flag: false,
433 key_flag: false,
434 non_standard_flag: false,
435 }
436 }
437
438 #[test]
439 fn single_fragment_sample_completes_immediately() {
440 let mut a = FragmentAssembler::default();
441 let res = a.insert(&df(1, 1, 1, 4, 4, vec![1, 2, 3, 4]));
443 assert!(res.is_some());
444 let s = res.unwrap();
445 assert_eq!(s.sequence_number, SequenceNumber(1));
446 assert_eq!(s.payload, vec![1, 2, 3, 4]);
447 assert_eq!(a.len(), 0);
448 }
449
450 #[test]
451 fn two_fragments_complete_in_order() {
452 let mut a = FragmentAssembler::default();
453 assert!(a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4])).is_none());
454 let res = a.insert(&df(1, 2, 1, 4, 8, vec![5, 6, 7, 8])).unwrap();
455 assert_eq!(res.payload, vec![1, 2, 3, 4, 5, 6, 7, 8]);
456 }
457
458 #[test]
459 fn fragments_complete_out_of_order() {
460 let mut a = FragmentAssembler::default();
461 assert!(a.insert(&df(1, 2, 1, 4, 10, vec![5, 6, 7, 8])).is_none());
463 assert!(a.insert(&df(1, 1, 1, 4, 10, vec![1, 2, 3, 4])).is_none());
464 let res = a.insert(&df(1, 3, 1, 4, 10, vec![9, 10])).unwrap();
465 assert_eq!(res.payload, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
466 }
467
468 #[test]
469 fn last_fragment_shorter_than_fragment_size() {
470 let mut a = FragmentAssembler::default();
471 assert!(a.insert(&df(1, 1, 1, 4, 10, vec![1, 2, 3, 4])).is_none());
472 assert!(a.insert(&df(1, 2, 1, 4, 10, vec![5, 6, 7, 8])).is_none());
473 let res = a.insert(&df(1, 3, 1, 4, 10, vec![9, 10])).unwrap();
474 assert_eq!(res.payload.len(), 10);
475 }
476
477 #[test]
478 fn duplicate_fragment_is_idempotent() {
479 let mut a = FragmentAssembler::default();
480 assert!(a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4])).is_none());
481 assert!(a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4])).is_none());
482 assert_eq!(a.missing_fragments(SequenceNumber(1)).num_bits, 1);
483 }
484
485 #[test]
486 fn missing_fragments_enumerates_gaps() {
487 let mut a = FragmentAssembler::default();
488 assert!(a.insert(&df(1, 1, 1, 4, 10, vec![1, 2, 3, 4])).is_none());
490 assert!(a.insert(&df(1, 3, 1, 4, 10, vec![9, 10])).is_none());
491 let ms = a.missing_fragments(SequenceNumber(1));
492 let collected: Vec<_> = ms.iter_set().collect();
493 assert_eq!(collected, vec![FragmentNumber(2)]);
494 }
495
496 #[test]
497 fn inconsistent_sample_size_drops_fragment() {
498 let mut a = FragmentAssembler::default();
499 assert!(a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4])).is_none());
500 let res = a.insert(&df(1, 2, 1, 4, 12, vec![5, 6, 7, 8]));
502 assert!(res.is_none());
503 assert_eq!(a.drop_count(), 1);
504 assert_eq!(a.missing_fragments(SequenceNumber(1)).num_bits, 1);
506 }
507
508 #[test]
509 fn sample_too_large_drops_without_alloc() {
510 let caps = AssemblerCaps {
511 max_sample_bytes: 16,
512 ..AssemblerCaps::default()
513 };
514 let mut a = FragmentAssembler::new(caps);
515 assert!(a.insert(&df(1, 1, 1, 4, 100, vec![1, 2, 3, 4])).is_none());
517 assert!(a.is_empty());
518 assert_eq!(a.drop_count(), 1);
519 }
520
521 #[test]
522 fn fragment_size_zero_dropped() {
523 let mut a = FragmentAssembler::default();
524 assert!(a.insert(&df(1, 1, 1, 0, 4, vec![1, 2, 3, 4])).is_none());
526 assert_eq!(a.drop_count(), 1);
527 }
528
529 #[test]
530 fn fragment_index_zero_dropped() {
531 let mut a = FragmentAssembler::default();
532 assert!(a.insert(&df(1, 0, 1, 4, 4, vec![1, 2, 3, 4])).is_none());
533 assert_eq!(a.drop_count(), 1);
534 }
535
536 #[test]
537 fn fragment_index_out_of_range_dropped() {
538 let mut a = FragmentAssembler::default();
539 assert!(a.insert(&df(1, 2, 1, 4, 4, vec![0])).is_none());
541 assert_eq!(a.drop_count(), 1);
542 }
543
544 #[test]
545 fn payload_size_mismatch_dropped() {
546 let mut a = FragmentAssembler::default();
547 assert!(a.insert(&df(1, 1, 1, 4, 8, vec![1, 2])).is_none());
549 assert_eq!(a.drop_count(), 1);
550 }
551
552 #[test]
553 fn max_pending_sns_evicts_oldest() {
554 let caps = AssemblerCaps {
555 max_pending_sns: 2,
556 ..AssemblerCaps::default()
557 };
558 let mut a = FragmentAssembler::new(caps);
559 a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4]));
561 a.insert(&df(2, 1, 1, 4, 8, vec![1, 2, 3, 4]));
562 assert_eq!(a.len(), 2);
563 a.insert(&df(3, 1, 1, 4, 8, vec![1, 2, 3, 4]));
565 assert_eq!(a.len(), 2);
566 assert!(a.buffers.contains_key(&SequenceNumber(2)));
567 assert!(a.buffers.contains_key(&SequenceNumber(3)));
568 assert_eq!(a.drop_count(), 1);
569 }
570
571 #[test]
572 fn has_gaps_flips_to_false_after_completion() {
573 let mut a = FragmentAssembler::default();
574 a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4]));
575 assert!(a.has_gaps());
576 a.insert(&df(1, 2, 1, 4, 8, vec![5, 6, 7, 8]));
577 assert!(!a.has_gaps());
578 }
579
580 #[test]
581 fn incomplete_sns_enumerates_in_order() {
582 let mut a = FragmentAssembler::default();
583 a.insert(&df(5, 1, 1, 4, 8, vec![1, 2, 3, 4]));
584 a.insert(&df(2, 1, 1, 4, 8, vec![1, 2, 3, 4]));
585 let sns: Vec<_> = a.incomplete_sns().collect();
586 assert_eq!(sns, vec![SequenceNumber(2), SequenceNumber(5)]);
587 }
588
589 #[test]
590 fn discard_removes_buffer() {
591 let mut a = FragmentAssembler::default();
592 a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4]));
593 assert!(a.discard(SequenceNumber(1)));
594 assert!(a.is_empty());
595 assert!(!a.discard(SequenceNumber(1)));
596 }
597
598 #[test]
599 fn missing_for_unknown_sn_is_empty() {
600 let a = FragmentAssembler::default();
601 assert_eq!(a.missing_fragments(SequenceNumber(42)).num_bits, 0);
602 }
603
604 #[test]
607 fn bundled_fragments_all_full() {
608 let mut a = FragmentAssembler::default();
611 let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
612 let res = a.insert(&df(1, 1, 3, 4, 18, payload.clone()));
613 assert!(res.is_none(), "not yet complete");
614 let ms: Vec<_> = a.missing_fragments(SequenceNumber(1)).iter_set().collect();
616 assert_eq!(ms, vec![FragmentNumber(4), FragmentNumber(5)]);
617 }
618
619 #[test]
620 fn bundled_fragments_including_last_with_tail() {
621 let mut a = FragmentAssembler::default();
624 assert!(
626 a.insert(&df(1, 1, 1, 4, 10, vec![0xA, 0xB, 0xC, 0xD]))
627 .is_none()
628 );
629 let bundle = vec![5, 6, 7, 8, 9, 10];
631 let res = a.insert(&df(1, 2, 2, 4, 10, bundle));
632 assert!(res.is_some());
633 let s = res.unwrap();
634 assert_eq!(s.payload, vec![0xA, 0xB, 0xC, 0xD, 5, 6, 7, 8, 9, 10]);
635 }
636
637 #[test]
638 fn bundled_fragments_payload_size_mismatch_rejected() {
639 let mut a = FragmentAssembler::default();
642 let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
643 assert!(a.insert(&df(1, 1, 3, 4, 20, payload)).is_none());
644 assert_eq!(a.drop_count(), 1);
645 assert_eq!(a.last_drop_reason(), Some(DropReason::PayloadSizeMismatch));
646 }
647
648 #[test]
651 fn last_drop_reason_tracks_most_recent() {
652 let mut a = FragmentAssembler::default();
653 assert_eq!(a.last_drop_reason(), None);
654 a.insert(&df(1, 0, 1, 4, 4, vec![1, 2, 3, 4]));
655 assert_eq!(a.last_drop_reason(), Some(DropReason::FragmentIndexZero));
656 a.insert(&df(1, 1, 1, 0, 4, vec![1, 2, 3, 4]));
657 assert_eq!(a.last_drop_reason(), Some(DropReason::FragmentSizeInvalid));
658 }
659
660 #[test]
661 fn pending_sns_cap_exceeded_uses_dedicated_reason() {
662 let caps = AssemblerCaps {
663 max_pending_sns: 1,
664 ..AssemblerCaps::default()
665 };
666 let mut a = FragmentAssembler::new(caps);
667 a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4]));
668 a.insert(&df(2, 1, 1, 4, 8, vec![1, 2, 3, 4]));
669 assert_eq!(
670 a.last_drop_reason(),
671 Some(DropReason::PendingSnsCapExceeded)
672 );
673 }
674
675 #[test]
676 fn default_assembler_uses_default_caps() {
677 let mut a = FragmentAssembler::default();
680 assert!(a.is_empty());
681 let res = a.insert(&df(1, 1, 1, 4, 4, vec![1, 2, 3, 4]));
683 assert!(res.is_some());
684 }
685
686 #[test]
687 fn reset_diagnostics_clears_counters_but_keeps_buffers() {
688 let mut a = FragmentAssembler::default();
691 a.insert(&df(1, 0, 1, 4, 4, vec![1, 2, 3, 4])); a.insert(&df(2, 1, 1, 4, 8, vec![1, 2, 3, 4])); assert_eq!(a.drop_count(), 1);
694 assert_eq!(a.len(), 1);
695 a.reset_diagnostics();
696 assert_eq!(a.drop_count(), 0);
697 assert!(a.last_drop_reason().is_none());
698 assert_eq!(a.len(), 1, "buffers must stay intact");
699 }
700
701 #[test]
702 fn max_pending_sns_zero_rejects_with_assembler_disabled() {
703 let caps = AssemblerCaps {
704 max_pending_sns: 0,
705 ..AssemblerCaps::default()
706 };
707 let mut a = FragmentAssembler::new(caps);
708 a.insert(&df(1, 1, 1, 4, 8, vec![1, 2, 3, 4]));
709 assert_eq!(a.last_drop_reason(), Some(DropReason::AssemblerDisabled));
710 assert!(a.is_empty());
711 }
712}