noq_proto/connection/
assembler.rs

1use std::{
2    cmp::Ordering,
3    collections::{BinaryHeap, binary_heap::PeekMut},
4    mem,
5};
6
7use bytes::{Buf, Bytes, BytesMut};
8
9use crate::range_set::ArrayRangeSet;
10
11/// Helper to assemble unordered stream frames into an ordered stream
12#[derive(Debug, Default)]
13pub(super) struct Assembler {
14    state: State,
15    data: BinaryHeap<Buffer>,
16    /// Total number of buffered bytes, including duplicates in ordered mode.
17    buffered: usize,
18    /// Estimated number of allocated bytes, will never be less than `buffered`.
19    allocated: usize,
20    /// Number of bytes read by the application. When only ordered reads have been used, this is
21    /// the length of the contiguous prefix of the stream which has been consumed by the
22    /// application, aka the stream offset.
23    bytes_read: u64,
24    end: u64,
25}
26
27impl Assembler {
28    pub(super) fn new() -> Self {
29        Self::default()
30    }
31
32    /// Reset to the initial state
33    pub(super) fn reinit(&mut self) {
34        let old_data = mem::take(&mut self.data);
35        *self = Self::default();
36        self.data = old_data;
37        self.data.clear();
38    }
39
40    pub(super) fn is_ordered(&self) -> bool {
41        self.state.is_ordered()
42    }
43
44    pub(super) fn ensure_ordering(&mut self, ordered: bool) -> Result<(), IllegalOrderedRead> {
45        if ordered && !self.state.is_ordered() {
46            return Err(IllegalOrderedRead);
47        } else if !ordered && self.state.is_ordered() {
48            // Enter unordered mode
49            if !self.data.is_empty() {
50                // Get rid of possible duplicates
51                self.defragment();
52            }
53            let mut recvd = ArrayRangeSet::new();
54            recvd.insert(0..self.bytes_read);
55            for chunk in &self.data {
56                recvd.insert(chunk.offset..chunk.offset + chunk.bytes.len() as u64);
57            }
58            self.state = State::Unordered { recvd };
59        }
60        Ok(())
61    }
62
63    /// Get the the next chunk
64    pub(super) fn read(&mut self, max_length: usize, ordered: bool) -> Option<Chunk> {
65        loop {
66            let mut chunk = self.data.peek_mut()?;
67
68            if ordered {
69                if chunk.offset > self.bytes_read {
70                    // Next chunk is after current read index
71                    return None;
72                } else if (chunk.offset + chunk.bytes.len() as u64) <= self.bytes_read {
73                    // Next chunk is useless as the read index is beyond its end
74                    self.buffered -= chunk.bytes.len();
75                    self.allocated -= chunk.allocation_size;
76                    PeekMut::pop(chunk);
77                    continue;
78                }
79
80                // Determine `start` and `len` of the slice of useful data in chunk
81                let start = (self.bytes_read - chunk.offset) as usize;
82                if start > 0 {
83                    chunk.bytes.advance(start);
84                    chunk.offset += start as u64;
85                    self.buffered -= start;
86                }
87            }
88
89            return Some(if max_length < chunk.bytes.len() {
90                self.bytes_read += max_length as u64;
91                let offset = chunk.offset;
92                chunk.offset += max_length as u64;
93                self.buffered -= max_length;
94                Chunk::new(offset, chunk.bytes.split_to(max_length))
95            } else {
96                self.bytes_read += chunk.bytes.len() as u64;
97                self.buffered -= chunk.bytes.len();
98                self.allocated -= chunk.allocation_size;
99                let chunk = PeekMut::pop(chunk);
100                Chunk::new(chunk.offset, chunk.bytes)
101            });
102        }
103    }
104
105    /// Copy fragmented chunk data to new chunks backed by a single buffer
106    ///
107    /// This makes sure we're not unnecessarily holding on to many larger allocations.
108    /// We merge contiguous chunks in the process of doing so.
109    fn defragment(&mut self) {
110        let new = BinaryHeap::with_capacity(self.data.len());
111        let old = mem::replace(&mut self.data, new);
112        let mut buffers = old.into_sorted_vec();
113        self.buffered = 0;
114        let mut fragmented_buffered = 0;
115        let mut offset = self.bytes_read;
116        for chunk in buffers.iter_mut().rev() {
117            chunk.try_mark_defragment(offset);
118            let size = chunk.bytes.len();
119            offset = chunk.offset + size as u64;
120            self.buffered += size;
121            if !chunk.defragmented {
122                fragmented_buffered += size;
123            }
124        }
125        self.allocated = self.buffered;
126        let mut buffer = BytesMut::with_capacity(fragmented_buffered);
127        let mut offset = self.bytes_read;
128        for chunk in buffers.into_iter().rev() {
129            if chunk.defragmented {
130                // bytes might be empty after try_mark_defragment
131                if !chunk.bytes.is_empty() {
132                    self.data.push(chunk);
133                }
134                continue;
135            }
136            // Overlap is resolved by try_mark_defragment
137            if chunk.offset != offset + (buffer.len() as u64) {
138                if !buffer.is_empty() {
139                    self.data
140                        .push(Buffer::new_defragmented(offset, buffer.split().freeze()));
141                }
142                offset = chunk.offset;
143            }
144            buffer.extend_from_slice(&chunk.bytes);
145        }
146        if !buffer.is_empty() {
147            self.data
148                .push(Buffer::new_defragmented(offset, buffer.split().freeze()));
149        }
150    }
151
152    // Note: If a packet contains many frames from the same stream, the estimated over-allocation
153    // will be much higher because we are counting the same allocation multiple times.
154    pub(super) fn insert(&mut self, mut offset: u64, mut bytes: Bytes, allocation_size: usize) {
155        debug_assert!(
156            bytes.len() <= allocation_size,
157            "allocation_size less than bytes.len(): {:?} < {:?}",
158            allocation_size,
159            bytes.len()
160        );
161        self.end = self.end.max(offset + bytes.len() as u64);
162        if let State::Unordered { ref mut recvd } = self.state {
163            // Discard duplicate data
164            let range = offset..offset + bytes.len() as u64;
165            for duplicate in recvd.iter_range(range.clone()) {
166                if duplicate.start > offset {
167                    let buffer = Buffer::new(
168                        offset,
169                        bytes.split_to((duplicate.start - offset) as usize),
170                        allocation_size,
171                    );
172                    self.buffered += buffer.bytes.len();
173                    self.allocated += buffer.allocation_size;
174                    self.data.push(buffer);
175                    offset = duplicate.start;
176                }
177                bytes.advance((duplicate.end - offset) as usize);
178                offset = duplicate.end;
179            }
180            recvd.insert(range);
181        } else if offset < self.bytes_read {
182            if (offset + bytes.len() as u64) <= self.bytes_read {
183                return;
184            } else {
185                let diff = self.bytes_read - offset;
186                offset += diff;
187                bytes.advance(diff as usize);
188            }
189        }
190
191        if bytes.is_empty() {
192            return;
193        }
194        let buffer = Buffer::new(offset, bytes, allocation_size);
195        self.buffered += buffer.bytes.len();
196        self.allocated += buffer.allocation_size;
197        self.data.push(buffer);
198        // `self.buffered` also counts duplicate bytes, therefore we use
199        // `self.end - self.bytes_read` as an upper bound of buffered unique
200        // bytes. This will cause a defragmentation if the amount of duplicate
201        // bytes exceedes a proportion of the receive window size.
202        let buffered = self.buffered.min((self.end - self.bytes_read) as usize);
203        let over_allocation = self.allocated - buffered;
204        // Rationale: on the one hand, we want to defragment rarely, ideally never
205        // in non-pathological scenarios. However, a pathological or malicious
206        // peer could send us one-byte frames, and since we use reference-counted
207        // buffers in order to prevent copying, this could result in keeping a lot
208        // of memory allocated. This limits over-allocation in proportion to the
209        // buffered data. The constants are chosen somewhat arbitrarily and try to
210        // balance between defragmentation overhead and over-allocation.
211        let threshold = 32768.max(buffered * 3 / 2);
212        if over_allocation > threshold {
213            self.defragment()
214        }
215    }
216
217    /// Number of bytes consumed by the application
218    pub(super) fn bytes_read(&self) -> u64 {
219        self.bytes_read
220    }
221
222    /// Discard all buffered data
223    pub(super) fn clear(&mut self) {
224        self.data.clear();
225        self.buffered = 0;
226        self.allocated = 0;
227    }
228}
229
230/// A chunk of data from the receive stream
231#[derive(Debug, PartialEq, Eq)]
232pub struct Chunk {
233    /// The offset in the stream
234    pub offset: u64,
235    /// The contents of the chunk
236    pub bytes: Bytes,
237}
238
239impl Chunk {
240    fn new(offset: u64, bytes: Bytes) -> Self {
241        Self { offset, bytes }
242    }
243}
244
245#[derive(Debug, Eq)]
246struct Buffer {
247    offset: u64,
248    bytes: Bytes,
249    /// Size of the allocation behind `bytes`, if `defragmented == false`.
250    /// Otherwise this will be set to `bytes.len()` by `try_mark_defragment`.
251    /// Will never be less than `bytes.len()`.
252    allocation_size: usize,
253    defragmented: bool,
254}
255
256impl Buffer {
257    /// Constructs a new fragmented Buffer
258    fn new(offset: u64, bytes: Bytes, allocation_size: usize) -> Self {
259        Self {
260            offset,
261            bytes,
262            allocation_size,
263            defragmented: false,
264        }
265    }
266
267    /// Constructs a new defragmented Buffer
268    fn new_defragmented(offset: u64, bytes: Bytes) -> Self {
269        let allocation_size = bytes.len();
270        Self {
271            offset,
272            bytes,
273            allocation_size,
274            defragmented: true,
275        }
276    }
277
278    /// Discards data before `offset` and flags `self` as defragmented if it has good utilization
279    fn try_mark_defragment(&mut self, offset: u64) {
280        let duplicate = offset.saturating_sub(self.offset) as usize;
281        self.offset = self.offset.max(offset);
282        if duplicate >= self.bytes.len() {
283            // All bytes are duplicate
284            self.bytes = Bytes::new();
285            self.defragmented = true;
286            self.allocation_size = 0;
287            return;
288        }
289        self.bytes.advance(duplicate);
290        // Make sure that fragmented buffers with high utilization become defragmented and
291        // defragmented buffers remain defragmented
292        self.defragmented = self.defragmented || self.bytes.len() * 6 / 5 >= self.allocation_size;
293        if self.defragmented {
294            // Make sure that defragmented buffers do not contribute to over-allocation
295            self.allocation_size = self.bytes.len();
296        }
297    }
298}
299
300impl Ord for Buffer {
301    // Invert ordering based on offset (max-heap, min offset first),
302    // prioritize longer chunks at the same offset.
303    fn cmp(&self, other: &Self) -> Ordering {
304        self.offset
305            .cmp(&other.offset)
306            .reverse()
307            .then(self.bytes.len().cmp(&other.bytes.len()))
308    }
309}
310
311impl PartialOrd for Buffer {
312    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
313        Some(self.cmp(other))
314    }
315}
316
317impl PartialEq for Buffer {
318    fn eq(&self, other: &Self) -> bool {
319        (self.offset, self.bytes.len()) == (other.offset, other.bytes.len())
320    }
321}
322
323#[derive(Debug, Default)]
324enum State {
325    #[default]
326    Ordered,
327    Unordered {
328        /// The set of offsets that have been received from the peer, including portions not yet
329        /// read by the application.
330        recvd: ArrayRangeSet,
331    },
332}
333
334impl State {
335    fn is_ordered(&self) -> bool {
336        matches!(self, Self::Ordered)
337    }
338}
339
340/// Error indicating that an ordered read was performed on a stream after an unordered read
341#[derive(Debug)]
342pub(crate) struct IllegalOrderedRead;
343
344#[cfg(test)]
345mod test {
346    use super::*;
347    use assert_matches::assert_matches;
348
349    #[test]
350    fn assemble_ordered() {
351        let mut x = Assembler::new();
352        assert_matches!(next(&mut x, 32), None);
353        x.insert(0, Bytes::from_static(b"123"), 3);
354        assert_matches!(next(&mut x, 1), Some(ref y) if &y[..] == b"1");
355        assert_matches!(next(&mut x, 3), Some(ref y) if &y[..] == b"23");
356        x.insert(3, Bytes::from_static(b"456"), 3);
357        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"456");
358        x.insert(6, Bytes::from_static(b"789"), 3);
359        x.insert(9, Bytes::from_static(b"10"), 2);
360        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"789");
361        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"10");
362        assert_matches!(next(&mut x, 32), None);
363    }
364
365    #[test]
366    fn assemble_unordered() {
367        let mut x = Assembler::new();
368        x.ensure_ordering(false).unwrap();
369        x.insert(3, Bytes::from_static(b"456"), 3);
370        assert_matches!(next(&mut x, 32), None);
371        x.insert(0, Bytes::from_static(b"123"), 3);
372        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"123");
373        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"456");
374        assert_matches!(next(&mut x, 32), None);
375    }
376
377    #[test]
378    fn assemble_duplicate() {
379        let mut x = Assembler::new();
380        x.insert(0, Bytes::from_static(b"123"), 3);
381        x.insert(0, Bytes::from_static(b"123"), 3);
382        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"123");
383        assert_matches!(next(&mut x, 32), None);
384    }
385
386    #[test]
387    fn assemble_duplicate_compact() {
388        let mut x = Assembler::new();
389        x.insert(0, Bytes::from_static(b"123"), 3);
390        x.insert(0, Bytes::from_static(b"123"), 3);
391        x.defragment();
392        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"123");
393        assert_matches!(next(&mut x, 32), None);
394    }
395
396    #[test]
397    fn assemble_contained() {
398        let mut x = Assembler::new();
399        x.insert(0, Bytes::from_static(b"12345"), 5);
400        x.insert(1, Bytes::from_static(b"234"), 3);
401        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"12345");
402        assert_matches!(next(&mut x, 32), None);
403    }
404
405    #[test]
406    fn assemble_contained_compact() {
407        let mut x = Assembler::new();
408        x.insert(0, Bytes::from_static(b"12345"), 5);
409        x.insert(1, Bytes::from_static(b"234"), 3);
410        x.defragment();
411        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"12345");
412        assert_matches!(next(&mut x, 32), None);
413    }
414
415    #[test]
416    fn assemble_contains() {
417        let mut x = Assembler::new();
418        x.insert(1, Bytes::from_static(b"234"), 3);
419        x.insert(0, Bytes::from_static(b"12345"), 5);
420        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"12345");
421        assert_matches!(next(&mut x, 32), None);
422    }
423
424    #[test]
425    fn assemble_contains_compact() {
426        let mut x = Assembler::new();
427        x.insert(1, Bytes::from_static(b"234"), 3);
428        x.insert(0, Bytes::from_static(b"12345"), 5);
429        x.defragment();
430        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"12345");
431        assert_matches!(next(&mut x, 32), None);
432    }
433
434    #[test]
435    fn assemble_overlapping() {
436        let mut x = Assembler::new();
437        x.insert(0, Bytes::from_static(b"123"), 3);
438        x.insert(1, Bytes::from_static(b"234"), 3);
439        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"123");
440        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"4");
441        assert_matches!(next(&mut x, 32), None);
442    }
443
444    #[test]
445    fn assemble_overlapping_compact() {
446        let mut x = Assembler::new();
447        x.insert(0, Bytes::from_static(b"123"), 4);
448        x.insert(1, Bytes::from_static(b"234"), 4);
449        x.defragment();
450        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"1234");
451        assert_matches!(next(&mut x, 32), None);
452    }
453
454    #[test]
455    fn assemble_complex() {
456        let mut x = Assembler::new();
457        x.insert(0, Bytes::from_static(b"1"), 1);
458        x.insert(2, Bytes::from_static(b"3"), 1);
459        x.insert(4, Bytes::from_static(b"5"), 1);
460        x.insert(0, Bytes::from_static(b"123456"), 6);
461        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"123456");
462        assert_matches!(next(&mut x, 32), None);
463    }
464
465    #[test]
466    fn assemble_complex_compact() {
467        let mut x = Assembler::new();
468        x.insert(0, Bytes::from_static(b"1"), 1);
469        x.insert(2, Bytes::from_static(b"3"), 1);
470        x.insert(4, Bytes::from_static(b"5"), 1);
471        x.insert(0, Bytes::from_static(b"123456"), 6);
472        x.defragment();
473        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"123456");
474        assert_matches!(next(&mut x, 32), None);
475    }
476
477    #[test]
478    fn assemble_old() {
479        let mut x = Assembler::new();
480        x.insert(0, Bytes::from_static(b"1234"), 4);
481        assert_matches!(next(&mut x, 32), Some(ref y) if &y[..] == b"1234");
482        x.insert(0, Bytes::from_static(b"1234"), 4);
483        assert_matches!(next(&mut x, 32), None);
484    }
485
486    #[test]
487    fn compact() {
488        let mut x = Assembler::new();
489        x.insert(0, Bytes::from_static(b"abc"), 4);
490        x.insert(3, Bytes::from_static(b"def"), 4);
491        x.insert(9, Bytes::from_static(b"jkl"), 4);
492        x.insert(12, Bytes::from_static(b"mno"), 4);
493        x.defragment();
494        assert_eq!(
495            next_unordered(&mut x),
496            Chunk::new(0, Bytes::from_static(b"abcdef"))
497        );
498        assert_eq!(
499            next_unordered(&mut x),
500            Chunk::new(9, Bytes::from_static(b"jklmno"))
501        );
502    }
503
504    #[test]
505    fn defrag_with_missing_prefix() {
506        let mut x = Assembler::new();
507        x.insert(3, Bytes::from_static(b"def"), 3);
508        x.defragment();
509        assert_eq!(
510            next_unordered(&mut x),
511            Chunk::new(3, Bytes::from_static(b"def"))
512        );
513    }
514
515    #[test]
516    fn defrag_read_chunk() {
517        let mut x = Assembler::new();
518        x.insert(3, Bytes::from_static(b"def"), 4);
519        x.insert(0, Bytes::from_static(b"abc"), 4);
520        x.insert(7, Bytes::from_static(b"hij"), 4);
521        x.insert(11, Bytes::from_static(b"lmn"), 4);
522        x.defragment();
523        assert_matches!(x.read(usize::MAX, true), Some(ref y) if &y.bytes[..] == b"abcdef");
524        x.insert(5, Bytes::from_static(b"fghijklmn"), 9);
525        assert_matches!(x.read(usize::MAX, true), Some(ref y) if &y.bytes[..] == b"ghijklmn");
526        x.insert(13, Bytes::from_static(b"nopq"), 4);
527        assert_matches!(x.read(usize::MAX, true), Some(ref y) if &y.bytes[..] == b"opq");
528        x.insert(15, Bytes::from_static(b"pqrs"), 4);
529        assert_matches!(x.read(usize::MAX, true), Some(ref y) if &y.bytes[..] == b"rs");
530        assert_matches!(x.read(usize::MAX, true), None);
531    }
532
533    #[test]
534    fn unordered_happy_path() {
535        let mut x = Assembler::new();
536        x.ensure_ordering(false).unwrap();
537        x.insert(0, Bytes::from_static(b"abc"), 3);
538        assert_eq!(
539            next_unordered(&mut x),
540            Chunk::new(0, Bytes::from_static(b"abc"))
541        );
542        assert_eq!(x.read(usize::MAX, false), None);
543        x.insert(3, Bytes::from_static(b"def"), 3);
544        assert_eq!(
545            next_unordered(&mut x),
546            Chunk::new(3, Bytes::from_static(b"def"))
547        );
548        assert_eq!(x.read(usize::MAX, false), None);
549    }
550
551    #[test]
552    fn unordered_dedup() {
553        let mut x = Assembler::new();
554        x.ensure_ordering(false).unwrap();
555        x.insert(3, Bytes::from_static(b"def"), 3);
556        assert_eq!(
557            next_unordered(&mut x),
558            Chunk::new(3, Bytes::from_static(b"def"))
559        );
560        assert_eq!(x.read(usize::MAX, false), None);
561        x.insert(0, Bytes::from_static(b"a"), 1);
562        x.insert(0, Bytes::from_static(b"abcdefghi"), 9);
563        x.insert(0, Bytes::from_static(b"abcd"), 4);
564        assert_eq!(
565            next_unordered(&mut x),
566            Chunk::new(0, Bytes::from_static(b"a"))
567        );
568        assert_eq!(
569            next_unordered(&mut x),
570            Chunk::new(1, Bytes::from_static(b"bc"))
571        );
572        assert_eq!(
573            next_unordered(&mut x),
574            Chunk::new(6, Bytes::from_static(b"ghi"))
575        );
576        assert_eq!(x.read(usize::MAX, false), None);
577        x.insert(8, Bytes::from_static(b"ijkl"), 4);
578        assert_eq!(
579            next_unordered(&mut x),
580            Chunk::new(9, Bytes::from_static(b"jkl"))
581        );
582        assert_eq!(x.read(usize::MAX, false), None);
583        x.insert(12, Bytes::from_static(b"mno"), 3);
584        assert_eq!(
585            next_unordered(&mut x),
586            Chunk::new(12, Bytes::from_static(b"mno"))
587        );
588        assert_eq!(x.read(usize::MAX, false), None);
589        x.insert(2, Bytes::from_static(b"cde"), 3);
590        assert_eq!(x.read(usize::MAX, false), None);
591    }
592
593    #[test]
594    fn chunks_dedup() {
595        let mut x = Assembler::new();
596        x.insert(3, Bytes::from_static(b"def"), 3);
597        assert_eq!(x.read(usize::MAX, true), None);
598        x.insert(0, Bytes::from_static(b"a"), 1);
599        x.insert(1, Bytes::from_static(b"bcdefghi"), 9);
600        x.insert(0, Bytes::from_static(b"abcd"), 4);
601        assert_eq!(
602            x.read(usize::MAX, true),
603            Some(Chunk::new(0, Bytes::from_static(b"abcd")))
604        );
605        assert_eq!(
606            x.read(usize::MAX, true),
607            Some(Chunk::new(4, Bytes::from_static(b"efghi")))
608        );
609        assert_eq!(x.read(usize::MAX, true), None);
610        x.insert(8, Bytes::from_static(b"ijkl"), 4);
611        assert_eq!(
612            x.read(usize::MAX, true),
613            Some(Chunk::new(9, Bytes::from_static(b"jkl")))
614        );
615        assert_eq!(x.read(usize::MAX, true), None);
616        x.insert(12, Bytes::from_static(b"mno"), 3);
617        assert_eq!(
618            x.read(usize::MAX, true),
619            Some(Chunk::new(12, Bytes::from_static(b"mno")))
620        );
621        assert_eq!(x.read(usize::MAX, true), None);
622        x.insert(2, Bytes::from_static(b"cde"), 3);
623        assert_eq!(x.read(usize::MAX, true), None);
624    }
625
626    #[test]
627    fn ordered_eager_discard() {
628        let mut x = Assembler::new();
629        x.insert(0, Bytes::from_static(b"abc"), 3);
630        assert_eq!(x.data.len(), 1);
631        assert_eq!(
632            x.read(usize::MAX, true),
633            Some(Chunk::new(0, Bytes::from_static(b"abc")))
634        );
635        x.insert(0, Bytes::from_static(b"ab"), 2);
636        assert_eq!(x.data.len(), 0);
637        x.insert(2, Bytes::from_static(b"cd"), 2);
638        assert_eq!(
639            x.data.peek(),
640            Some(&Buffer::new(3, Bytes::from_static(b"d"), 2))
641        );
642    }
643
644    #[test]
645    fn ordered_insert_unordered_read() {
646        let mut x = Assembler::new();
647        x.insert(0, Bytes::from_static(b"abc"), 3);
648        x.insert(0, Bytes::from_static(b"abc"), 3);
649        x.ensure_ordering(false).unwrap();
650        assert_eq!(
651            x.read(3, false),
652            Some(Chunk::new(0, Bytes::from_static(b"abc")))
653        );
654        assert_eq!(x.read(3, false), None);
655    }
656
657    #[test]
658    fn no_duplicate_after_mode_switch() {
659        // Regression test: bytes read in ordered mode should not be returned again in unordered
660        // mode
661        let mut x = Assembler::new();
662        x.insert(0, Bytes::from_static(b"a"), 1);
663        x.insert(0, Bytes::from_static(b"a"), 1); // duplicate
664        assert_eq!(
665            x.read(1, true),
666            Some(Chunk::new(0, Bytes::from_static(b"a")))
667        );
668        x.ensure_ordering(false).unwrap();
669        assert_eq!(x.read(1, false), None); // should be None, byte 0 already returned
670    }
671
672    fn next_unordered(x: &mut Assembler) -> Chunk {
673        x.read(usize::MAX, false).unwrap()
674    }
675
676    fn next(x: &mut Assembler, size: usize) -> Option<Bytes> {
677        x.read(size, true).map(|chunk| chunk.bytes)
678    }
679}
680
681#[cfg(all(test, not(target_family = "wasm")))]
682mod proptests {
683    use proptest::prelude::*;
684    use rand::RngExt;
685    use test_strategy::{Arbitrary, proptest};
686
687    use super::*;
688
689    const MAX_OFFSET: u64 = 512;
690    const MAX_LEN: usize = 64;
691
692    #[derive(Debug, Clone, Arbitrary)]
693    enum Op {
694        #[weight(10)]
695        Insert {
696            #[strategy(0..MAX_OFFSET)]
697            offset: u64,
698            #[strategy(1..MAX_LEN)]
699            len: usize,
700        },
701        #[weight(10)]
702        Read {
703            #[strategy(1..MAX_LEN)]
704            max_len: usize,
705        },
706        #[weight(1)]
707        EnsureOrdering { ordered: bool },
708        #[weight(1)]
709        Defragment,
710    }
711
712    /// Tracks the state of the assembler for verification
713    struct RefState {
714        received: Vec<bool>,
715        returned: Vec<bool>,
716        ordered: bool,
717    }
718
719    fn set_range(bits: &mut [bool], start: u64, len: usize) {
720        for i in start..(start + len as u64).min(bits.len() as u64) {
721            bits[i as usize] = true;
722        }
723    }
724
725    impl RefState {
726        fn new() -> Self {
727            Self {
728                received: vec![false; MAX_OFFSET as usize],
729                returned: vec![false; MAX_OFFSET as usize],
730                ordered: true,
731            }
732        }
733
734        fn insert(&mut self, offset: u64, len: usize) {
735            set_range(&mut self.received, offset, len);
736        }
737
738        fn ensure_ordering(&mut self, ordered: bool) -> bool {
739            if ordered && !self.ordered {
740                return false;
741            }
742            self.ordered = ordered;
743            true
744        }
745
746        fn bytes_read(&self) -> u64 {
747            self.returned.iter().filter(|&&x| x).count() as u64
748        }
749    }
750
751    fn make_data() -> Vec<u8> {
752        use rand::SeedableRng;
753        let mut rng = rand::rngs::StdRng::seed_from_u64(0xDEADBEEF);
754        let mut data = vec![0u8; MAX_OFFSET as usize];
755        rng.fill(data.as_mut_slice());
756        data
757    }
758
759    fn get_slice(data: &[u8], offset: u64, len: usize) -> Bytes {
760        let start = offset as usize;
761        let end = (start + len).min(data.len());
762        Bytes::copy_from_slice(&data[start..end])
763    }
764
765    fn verify_chunk(data: &[u8], chunk: &Chunk) -> bool {
766        let start = chunk.offset as usize;
767        chunk.bytes[..] == data[start..start + chunk.bytes.len()]
768    }
769
770    #[proptest]
771    fn assembler_matches_reference(
772        #[strategy(proptest::collection::vec(any::<Op>(), 1..100))] ops: Vec<Op>,
773    ) {
774        let data = make_data();
775        let mut asm = Assembler::new();
776        let mut reference = RefState::new();
777
778        for op in ops {
779            match op {
780                Op::Insert { offset, len } => {
781                    let bytes = get_slice(&data, offset, len);
782                    asm.insert(offset, bytes, len);
783                    reference.insert(offset, len);
784                }
785                Op::Read { max_len } => {
786                    let ordered = reference.ordered;
787                    let actual = asm.read(max_len, ordered);
788
789                    match actual {
790                        None => {
791                            // Should only be None if no unreturned received bytes available
792                            let has_available = if ordered {
793                                // In ordered mode, check if the first unreturned byte is received
794                                reference
795                                    .returned
796                                    .iter()
797                                    .position(|&x| !x)
798                                    .is_some_and(|pos| reference.received[pos])
799                            } else {
800                                // In unordered mode, check if any unreturned received byte exists
801                                reference
802                                    .received
803                                    .iter()
804                                    .zip(&reference.returned)
805                                    .any(|(&r, &ret)| r && !ret)
806                            };
807                            prop_assert!(
808                                !has_available,
809                                "read returned None but data was available"
810                            );
811                        }
812                        Some(chunk) => {
813                            prop_assert!(chunk.bytes.len() <= max_len, "chunk exceeds max_len");
814                            prop_assert!(verify_chunk(&data, &chunk), "data corruption");
815                            // Mark as returned, check for duplicates
816                            for i in 0..chunk.bytes.len() {
817                                let offset = chunk.offset as usize + i;
818                                prop_assert!(
819                                    reference.received[offset],
820                                    "returned unreceived byte at {offset}"
821                                );
822                                prop_assert!(
823                                    !reference.returned[offset],
824                                    "duplicate byte at {offset}"
825                                );
826                                reference.returned[offset] = true;
827                            }
828                        }
829                    }
830                }
831                Op::EnsureOrdering { ordered } => {
832                    let actual = asm.ensure_ordering(ordered).is_ok();
833                    let expected = reference.ensure_ordering(ordered);
834                    prop_assert_eq!(actual, expected, "ensure_ordering result mismatch");
835                }
836                Op::Defragment => {
837                    if asm.state.is_ordered() {
838                        asm.defragment();
839                    }
840                }
841            }
842        }
843
844        // Invariant: bytes_read matches
845        prop_assert_eq!(
846            asm.bytes_read(),
847            reference.bytes_read(),
848            "bytes_read mismatch"
849        );
850    }
851}