Skip to main content

bytearray_ringbuffer/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![forbid(unsafe_code)]
3#![doc = include_str!("../README.md")]
4
5/// Fixed-capacity FIFO of variable-length byte slices, backed by `[u8; N]` with no heap allocation.
6///
7/// Each stored packet uses `data.len() + 8` bytes: a leading `u32` length (native endian), the
8/// payload, then the same length again. The queue is a ring: `head` is where the next `push` writes;
9/// `tail` is the oldest packet. Payloads may wrap across the end of the array; most accessors return
10/// a [`Packet`] whose slices `a` and `b` concatenate to the full payload.
11///
12/// The backing array is only modified by this crate's own logic (the field is private). Methods
13/// maintain consistent framing; [`Self::pop_front`] and iterators rely on that.
14///
15/// Compile-time requirements: `N > 8` and `N < u32::MAX` (see [`Self::new`]).
16pub struct BytearrayRingbuffer<const N: usize> {
17    buffer: [u8; N],
18    /// Byte index in `buffer` where the next [`Self::push`] will begin writing.
19    head: usize,
20    /// Byte index in `buffer` where the oldest packet begins.
21    tail: usize,
22    /// Number of packets currently stored.
23    count: usize,
24}
25
26/// A borrowed view of a single packet from the ring buffer.
27///
28/// Because a packet's payload may wrap across the end of the backing array, it is represented as
29/// two contiguous slices. `a` is the first (or only) part of the payload; `b` is the second part
30/// and is empty when the payload is contiguous.
31///
32/// Use [`Self::copy_into`] or [`Self::copy_part_into`] to copy the payload into a flat buffer.
33#[derive(Debug, PartialEq)]
34pub struct Packet<'a> {
35    /// First (or only) slice of the packet payload.
36    pub a: &'a [u8],
37    /// Second slice of the packet payload; empty when the payload is contiguous.
38    pub b: &'a [u8],
39}
40
41impl<'a> Packet<'a> {
42    /// Returns the total length of the packet payload (`a.len() + b.len()`).
43    pub fn len(&self) -> usize {
44        self.a.len() + self.b.len()
45    }
46
47    /// Returns `true` if the packet payload is empty.
48    pub fn is_empty(&self) -> bool {
49        self.a.is_empty() && self.b.is_empty()
50    }
51
52    /// Copies the full packet payload into `buffer`.
53    ///
54    /// # Panics
55    ///
56    /// Panics if `buffer.len() != self.a.len() + self.b.len()`.
57    pub fn copy_into(&self, buffer: &mut [u8]) {
58        assert_eq!(
59            buffer.len(),
60            self.len(),
61            "buffer length must equal packet length"
62        );
63        buffer[..self.a.len()].copy_from_slice(self.a);
64        buffer[self.a.len()..].copy_from_slice(self.b);
65    }
66
67    /// Copies the bytes in `range` of the packet payload into `buffer`.
68    ///
69    /// The range is interpreted over the logical concatenation `[a | b]`.
70    ///
71    /// # Panics
72    ///
73    /// Panics if `buffer.len() != range.len()` or if `range.end > self.a.len() + self.b.len()`.
74    pub fn copy_part_into(&self, range: core::ops::Range<usize>, buffer: &mut [u8]) {
75        assert_eq!(
76            buffer.len(),
77            range.len(),
78            "buffer length must equal range length"
79        );
80        assert!(range.end <= self.len(), "range out of bounds");
81
82        let start = range.start;
83        let end = range.end;
84        let a_len = self.a.len();
85        let mut buf_pos = 0;
86
87        // Copy the portion of `a` that falls within [start, end).
88        if start < a_len {
89            let a_end = end.min(a_len);
90            let chunk = &self.a[start..a_end];
91            buffer[buf_pos..buf_pos + chunk.len()].copy_from_slice(chunk);
92            buf_pos += chunk.len();
93        }
94
95        // Copy the portion of `b` that falls within [start, end).
96        if end > a_len {
97            let b_start = start.saturating_sub(a_len);
98            let b_end = end.saturating_sub(a_len);
99            let chunk = &self.b[b_start..b_end];
100            buffer[buf_pos..buf_pos + chunk.len()].copy_from_slice(chunk);
101        }
102    }
103
104    /// Extends `target` with the full packet payload.
105    ///
106    /// Appends the bytes from `a` followed by `b` to `target`. Works with any collection
107    /// that implements [`Extend<u8>`](core::iter::Extend), such as `Vec<u8>` or
108    /// `heapless::Vec<u8, N>`.
109    pub fn extend_into<E: Extend<u8>>(&self, target: &mut E) {
110        target.extend(self.a.iter().copied());
111        target.extend(self.b.iter().copied());
112    }
113}
114
115/// Returned when a [`BytearrayRingbuffer::push`] cannot store `data` without dropping older packets.
116///
117/// For [`BytearrayRingbuffer::push`], this means the unused region is too small. For
118/// [`BytearrayRingbuffer::push_force`], this is only returned when `data.len() > N - 8` (a single
119/// packet cannot fit in the buffer at all).
120#[derive(Copy, Clone, Debug)]
121pub struct NotEnoughSpaceError;
122
123/// Guard returned by [`BytearrayRingbuffer::push_multipart`] and
124/// [`BytearrayRingbuffer::push_multipart_force`].
125///
126/// Accumulates payload bytes written via repeated [`Self::push`] calls. When dropped, the
127/// completed packet (header + payload + footer) is committed to the ring buffer. Call
128/// [`Self::cancel`] to discard the in-progress write without committing a packet.
129///
130/// In force mode any existing packets that were displaced to make room are permanently lost, even
131/// if the write is cancelled.
132pub struct MultipartPush<'a, const N: usize> {
133    buf: &'a mut BytearrayRingbuffer<N>,
134    /// Ring index where the 4-byte header will be written on finalise.
135    start: usize,
136    /// Payload bytes written so far.
137    len: usize,
138    /// Whether to drop old packets when space is tight.
139    force: bool,
140    /// Set by [`Self::cancel`]; prevents [`Drop`] from committing the packet.
141    cancelled: bool,
142}
143
144impl<'a, const N: usize> MultipartPush<'a, N> {
145    /// Appends `data` to the packet currently being written.
146    ///
147    /// May be called multiple times. The chunks are concatenated in order.
148    ///
149    /// In normal mode returns [`NotEnoughSpaceError`] when there is not enough space in the buffer
150    /// to fit `data` plus the 4-byte footer. In force mode it drops the oldest packets until there
151    /// is room.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`NotEnoughSpaceError`] if:
156    /// - The total accumulated payload would exceed `N - 8` (the maximum for any single packet).
157    /// - In normal mode: there is not enough unused space.
158    /// - In force mode: even after dropping all existing packets there is still not enough space
159    ///   (meaning the total accumulated payload exceeds what can fit in the buffer).
160    pub fn push(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
161        if data.is_empty() {
162            return Ok(());
163        }
164
165        // Absolute ceiling: a single packet can never hold more than N-8 bytes.
166        if self.len + data.len() > N - 8 {
167            return Err(NotEnoughSpaceError);
168        }
169
170        // Need room for data + 4-byte footer.
171        let needed = data.len() + 4;
172
173        if self.force {
174            while self.buf.bytes_unused() < needed && !self.buf.empty() {
175                self.buf.pop_front();
176            }
177            if self.buf.bytes_unused() < needed {
178                return Err(NotEnoughSpaceError);
179            }
180        } else if self.buf.bytes_unused() < needed {
181            return Err(NotEnoughSpaceError);
182        }
183
184        write_wrapping(&mut self.buf.buffer, self.buf.head, data);
185        self.buf.head = add_wrapping::<N>(self.buf.head, data.len());
186        self.len += data.len();
187
188        Ok(())
189    }
190
191    /// Discards the in-progress packet without committing it to the ring buffer.
192    ///
193    /// Rewinds `head` to the position it had before [`BytearrayRingbuffer::push_multipart`] was
194    /// called. Any packets that were already dropped in force mode are permanently lost.
195    pub fn cancel(mut self) {
196        self.cancelled = true;
197        self.buf.head = self.start;
198        // Drop runs but the cancelled flag prevents committing.
199    }
200}
201
202impl<'a, const N: usize> Drop for MultipartPush<'a, N> {
203    fn drop(&mut self) {
204        if self.cancelled {
205            return;
206        }
207        let len_bytes: [u8; 4] = (self.len as u32).to_ne_bytes();
208        // Write header at the reserved slot.
209        write_wrapping(&mut self.buf.buffer, self.start, &len_bytes);
210        // Write footer immediately after the payload (current head).
211        write_wrapping(&mut self.buf.buffer, self.buf.head, &len_bytes);
212        self.buf.head = add_wrapping::<N>(self.buf.head, 4);
213        self.buf.count += 1;
214    }
215}
216
217impl<const N: usize> BytearrayRingbuffer<N> {
218    /// Creates an empty ring buffer.
219    ///
220    /// # Panics
221    ///
222    /// Panics at compile time if `N <= 8` or `N >= u32::MAX`.
223    pub const fn new() -> Self {
224        assert!(N > 8);
225        assert!(N < (u32::MAX as usize));
226        Self {
227            buffer: [0; N],
228            head: 0,
229            tail: 0,
230            count: 0,
231        }
232    }
233
234    /// Returns the largest payload length that can fit in the currently unused byte range, after
235    /// accounting for the 8-byte packet framing (two `u32` lengths).
236    ///
237    /// Computed from the unused span between write and read positions, minus `8`, saturated at zero.
238    pub const fn free(&self) -> usize {
239        self.bytes_unused().saturating_sub(8)
240    }
241
242    /// Appends `data` as the newest packet.
243    ///
244    /// # Errors
245    ///
246    /// Returns [`NotEnoughSpaceError`] if fewer than `data.len() + 8` bytes are unused.
247    ///
248    /// # Panics
249    ///
250    /// Panics if `data.len() > u32::MAX` (debug assertion).
251    pub fn push(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
252        self._push(data, false)
253    }
254
255    /// Appends `data` as the newest packet, dropping the oldest packets until there is room.
256    ///
257    /// Unlike [`Self::push`], this never fails for lack of space as long as a single packet can fit
258    /// in the backing array (`data.len() <= N - 8`).
259    ///
260    /// # Errors
261    ///
262    /// Returns [`NotEnoughSpaceError`] only when `data.len() > N - 8` (one frame cannot fit at all).
263    pub fn push_force(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
264        self._push(data, true)
265    }
266
267    /// Begins a multi-part push in normal mode.
268    ///
269    /// Returns a [`MultipartPush`] guard whose [`MultipartPush::push`] method appends chunks of
270    /// payload. When the guard is dropped the completed packet is committed. Call
271    /// [`MultipartPush::cancel`] to discard the write.
272    ///
273    /// # Errors
274    ///
275    /// Returns [`NotEnoughSpaceError`] if there are fewer than 8 unused bytes (not enough for even
276    /// an empty packet).
277    pub fn push_multipart(&mut self) -> Result<MultipartPush<'_, N>, NotEnoughSpaceError> {
278        // Need at least 8 bytes for the header + footer of an empty packet.
279        if self.bytes_unused() < 8 {
280            return Err(NotEnoughSpaceError);
281        }
282        let start = self.head;
283        self.head = add_wrapping::<N>(self.head, 4);
284        Ok(MultipartPush {
285            buf: self,
286            start,
287            len: 0,
288            force: false,
289            cancelled: false,
290        })
291    }
292
293    /// Begins a multi-part push in force mode.
294    ///
295    /// Like [`Self::push_multipart`] but drops the oldest packets as needed to make room. Dropped
296    /// packets are permanently lost even if the write is later cancelled.
297    ///
298    /// Returns a [`MultipartPush`] guard. Calling [`MultipartPush::push`] will drop further old
299    /// packets on demand.
300    pub fn push_multipart_force(&mut self) -> MultipartPush<'_, N> {
301        // Ensure there are at least 8 bytes free for an empty packet.
302        while self.bytes_unused() < 8 && !self.empty() {
303            self.pop_front();
304        }
305        let start = self.head;
306        self.head = add_wrapping::<N>(self.head, 4);
307        MultipartPush {
308            buf: self,
309            start,
310            len: 0,
311            force: true,
312            cancelled: false,
313        }
314    }
315
316    /// Returns `true` if there are no packets stored.
317    #[inline(always)]
318    pub const fn empty(&self) -> bool {
319        self.count == 0
320    }
321
322    /// Number of bytes in the ring between `head` and `tail` that do not belong to any packet.
323    const fn bytes_unused(&self) -> usize {
324        if self.empty() {
325            N
326        } else if self.head > self.tail {
327            N + self.tail - self.head
328        } else {
329            self.tail - self.head
330        }
331    }
332
333    fn _push(&mut self, data: &[u8], force: bool) -> Result<(), NotEnoughSpaceError> {
334        assert!(data.len() <= u32::MAX as usize);
335
336        // data is longer than entire buffer
337        if data.len() > N - 8 {
338            return Err(NotEnoughSpaceError);
339        }
340
341        // need to overwrite old data to fit new data
342        if (data.len() + 8) > self.bytes_unused() {
343            if !force {
344                return Err(NotEnoughSpaceError);
345            }
346            while (data.len() + 8) > self.bytes_unused() {
347                self.pop_front();
348            }
349        }
350
351        // write length + data + length
352        let addr_a = self.head;
353        let addr_b = add_wrapping::<N>(self.head, 4);
354        let addr_c = add_wrapping::<N>(self.head, 4 + data.len());
355        let len_buffer: [u8; 4] = (data.len() as u32).to_ne_bytes();
356        write_wrapping(&mut self.buffer, addr_a, &len_buffer);
357        write_wrapping(&mut self.buffer, addr_b, data);
358        write_wrapping(&mut self.buffer, addr_c, &len_buffer);
359
360        self.head = add_wrapping::<N>(self.head, 8 + data.len());
361        self.count += 1;
362
363        Ok(())
364    }
365
366    /// Removes and returns the oldest packet.
367    ///
368    /// The payload may be split across the end of the backing array; use [`Packet::copy_into`] or
369    /// access [`Packet::a`] and [`Packet::b`] directly. If the payload is contiguous, `b` is empty.
370    pub fn pop_front(&mut self) -> Option<Packet<'_>> {
371        if self.empty() {
372            return None;
373        }
374        let mut len_buffer = [0; 4];
375        read_wrapping(&self.buffer, self.tail, &mut len_buffer);
376        let len = u32::from_ne_bytes(len_buffer) as usize;
377
378        let index_data = add_wrapping::<N>(self.tail, 4);
379        let len_a = (N - index_data).min(len);
380        let a = &self.buffer[index_data..index_data + len_a];
381        let b = if len_a == len {
382            &[]
383        } else {
384            &self.buffer[..len - len_a]
385        };
386
387        self.tail = add_wrapping::<N>(self.tail, len + 8);
388        self.count -= 1;
389        Some(Packet { a, b })
390    }
391
392    /// Borrows the buffer and yields packets from newest to oldest.
393    pub fn iter_backwards<'a>(&'a self) -> IterBackwards<'a, N> {
394        IterBackwards {
395            buffer: &self.buffer,
396            head: self.head,
397            count: self.count,
398        }
399    }
400
401    /// Borrows the buffer and yields packets from oldest to newest.
402    pub fn iter<'a>(&'a self) -> Iter<'a, N> {
403        Iter {
404            buffer: &self.buffer,
405            head: self.head,
406            tail: self.tail,
407            count: self.count,
408        }
409    }
410
411    /// Returns how many packets are stored.
412    #[inline(always)]
413    pub const fn count(&self) -> usize {
414        self.count
415    }
416
417    /// Returns the `n`-th packet in oldest-to-newest order (`n == 0` is the oldest).
418    ///
419    /// Same as [`Iterator::nth`] on [`Self::iter`].
420    pub fn nth(&self, n: usize) -> Option<Packet<'_>> {
421        self.iter().nth(n)
422    }
423
424    /// Returns the `n`-th packet in newest-to-oldest order (`n == 0` is the newest).
425    ///
426    /// Same as [`Iterator::nth`] on [`Self::iter_backwards`].
427    pub fn nth_reverse(&self, n: usize) -> Option<Packet<'_>> {
428        self.iter_backwards().nth(n)
429    }
430
431    /// Returns the `n`-th packet in oldest-to-newest order as a single contiguous slice.
432    ///
433    /// If the payload already lies in one contiguous range of the backing array, returns that
434    /// subslice. If it wraps around the end of the ring, rotates the array in place so the payload is
435    /// contiguous at the front, adjusts internal indices, and returns a prefix of the array.
436    ///
437    /// `n == 0` is the oldest packet. Returns [`None`] if the buffer is empty or if `n >= count()`.
438    pub fn nth_contiguous(&mut self, mut n: usize) -> Option<&[u8]> {
439        if self.empty() || n >= self.count {
440            return None;
441        }
442
443        // iterate through buffer until we find this one
444        let mut tail = self.tail;
445        let len_data = loop {
446            let mut buf = [0u8; 4];
447            read_wrapping(&self.buffer, tail, &mut buf);
448            let len_data = u32::from_ne_bytes(buf) as usize;
449
450            if n == 0 {
451                break len_data;
452            }
453            n -= 1;
454
455            tail = add_wrapping::<N>(tail, len_data + 8);
456        };
457
458        let index_data = add_wrapping::<N>(tail, 4);
459
460        // happy path, no rotate necessary
461        if index_data + len_data <= N {
462            return Some(&self.buffer[index_data..index_data + len_data]);
463        }
464
465        // otherwise rotate
466        self.buffer.rotate_left(index_data);
467        self.tail = sub_wrapping::<N>(self.tail, index_data);
468        self.head = sub_wrapping::<N>(self.head, index_data);
469
470        Some(&self.buffer[..len_data])
471    }
472}
473
474/// Iterator over packets from newest to oldest. See [`BytearrayRingbuffer::iter_backwards`].
475pub struct IterBackwards<'a, const N: usize> {
476    buffer: &'a [u8; N],
477    head: usize,
478    count: usize,
479}
480
481impl<'a, const N: usize> Iterator for IterBackwards<'a, N> {
482    type Item = Packet<'a>;
483
484    fn next(&mut self) -> Option<Self::Item> {
485        if self.count == 0 {
486            return None;
487        }
488
489        // read length of newest packet
490        let index_len = sub_wrapping::<N>(self.head, 4);
491        let mut buf = [0u8; 4];
492        read_wrapping(self.buffer, index_len, &mut buf);
493        let len_data = u32::from_ne_bytes(buf) as usize;
494        debug_assert!((len_data + 8) <= N);
495
496        #[cfg(test)]
497        {
498            let index_len = sub_wrapping::<N>(self.head, 8 + len_data);
499            let mut buf = [0u8; 4];
500            read_wrapping(self.buffer, index_len, &mut buf);
501            let len_2 = u32::from_ne_bytes(buf) as usize;
502            assert_eq!(len_data, len_2);
503        }
504
505        // read out data
506        let index_data = sub_wrapping::<N>(self.head, 4 + len_data);
507        let first = (N - index_data).min(len_data);
508        let slice_a = &self.buffer[index_data..index_data + first];
509        let slice_b = if first < len_data {
510            &self.buffer[..len_data - first]
511        } else {
512            &[]
513        };
514
515        self.head = sub_wrapping::<N>(self.head, 8 + len_data);
516        self.count -= 1;
517
518        Some(Packet {
519            a: slice_a,
520            b: slice_b,
521        })
522    }
523}
524
525impl<const N: usize> Default for BytearrayRingbuffer<N> {
526    fn default() -> Self {
527        Self::new()
528    }
529}
530
531/// Iterator over packets from oldest to newest. See [`BytearrayRingbuffer::iter`].
532pub struct Iter<'a, const N: usize> {
533    buffer: &'a [u8; N],
534    head: usize,
535    tail: usize,
536    count: usize,
537}
538
539impl<'a, const N: usize> Iterator for Iter<'a, N> {
540    type Item = Packet<'a>;
541
542    fn next(&mut self) -> Option<Self::Item> {
543        if self.count == 0 {
544            return None;
545        }
546
547        // Occupied span (same as `N - bytes_unused()` for a non-empty queue).
548        let bytes_unused = if self.head > self.tail {
549            N + self.tail - self.head
550        } else {
551            self.tail - self.head
552        };
553        let bytes_occupied = N - bytes_unused;
554        debug_assert!(bytes_occupied >= 8);
555
556        // Oldest packet length at `tail`.
557        let mut buf = [0u8; 4];
558        read_wrapping(self.buffer, self.tail, &mut buf);
559        let len_data = u32::from_ne_bytes(buf) as usize;
560        debug_assert!((len_data + 8) <= N);
561        debug_assert!((len_data + 8) <= bytes_occupied);
562
563        // read out data
564        let index_data = add_wrapping::<N>(self.tail, 4);
565        let first = (N - index_data).min(len_data);
566        let slice_a = &self.buffer[index_data..index_data + first];
567        let slice_b = if first < len_data {
568            &self.buffer[..len_data - first]
569        } else {
570            &[]
571        };
572
573        self.tail = add_wrapping::<N>(self.tail, 8 + len_data);
574        self.count -= 1;
575
576        Some(Packet {
577            a: slice_a,
578            b: slice_b,
579        })
580    }
581}
582
583fn add_wrapping<const N: usize>(addr: usize, offset: usize) -> usize {
584    debug_assert!(addr < N);
585    debug_assert!(offset <= N);
586    let s = addr + offset;
587    if s < N { s } else { s - N }
588}
589
590fn sub_wrapping<const N: usize>(addr: usize, offset: usize) -> usize {
591    debug_assert!(addr < N);
592    debug_assert!(offset <= N);
593    if addr >= offset {
594        addr - offset
595    } else {
596        N + addr - offset
597    }
598}
599
600/// Copies `data` into `buffer` starting at `index`, continuing at index `0` if the write crosses the end.
601fn write_wrapping(buffer: &mut [u8], index: usize, data: &[u8]) {
602    let first = (buffer.len() - index).min(data.len());
603    buffer[index..index + first].copy_from_slice(&data[..first]);
604    if first < data.len() {
605        buffer[..data.len() - first].copy_from_slice(&data[first..]);
606    }
607}
608
609/// Fills `data` from `buffer` starting at `index`, wrapping to index `0` when the read crosses the end.
610fn read_wrapping(buffer: &[u8], index: usize, data: &mut [u8]) {
611    let first = (buffer.len() - index).min(data.len());
612    data[..first].copy_from_slice(&buffer[index..index + first]);
613    if first < data.len() {
614        let remaining = data.len() - first;
615        data[first..].copy_from_slice(&buffer[..remaining]);
616    }
617}
618
619#[cfg(test)]
620mod tests {
621    use std::collections::VecDeque;
622
623    use super::BytearrayRingbuffer;
624
625    #[test]
626    fn push_some_packets() {
627        const N: usize = 64;
628        for start_offset in 0..N {
629            let mut buf = BytearrayRingbuffer::<N>::new();
630            buf.head = start_offset;
631            buf.tail = start_offset;
632
633            let free = 64 - 8;
634            assert_eq!(buf.free(), free);
635
636            buf.push(b"01234567").unwrap();
637            let free = free - 8 - 8;
638            assert_eq!(buf.free(), free);
639
640            buf.push(b"").unwrap();
641            let free = free - 8;
642            assert_eq!(buf.free(), free);
643
644            buf.push(b"0123").unwrap();
645            let free = free - 4 - 8;
646            assert_eq!(buf.free(), free);
647
648            buf.push(b"0123").unwrap();
649            let free = free - 4 - 8;
650            assert_eq!(buf.free(), free);
651        }
652    }
653
654    #[test]
655    fn push_force() {
656        let mut buf = BytearrayRingbuffer::<16>::new();
657        assert_eq!(buf.bytes_unused(), 16);
658
659        let a = b"012345";
660        let b = b"0123";
661
662        buf.push(a).unwrap();
663        assert_eq!(buf.bytes_unused(), 16 - a.len() - 8);
664
665        buf.push(b).unwrap_err();
666        assert_eq!(buf.bytes_unused(), 16 - a.len() - 8);
667
668        buf.push_force(b).unwrap();
669        assert_eq!(buf.bytes_unused(), 16 - b.len() - 8);
670    }
671
672    #[test]
673    fn push_all_data_lengths() {
674        for n in 0..(32 - 8) {
675            let mut buf = BytearrayRingbuffer::<32>::new();
676            // push n bytes
677            let data = (0..n as u8).collect::<Vec<u8>>();
678
679            assert_eq!(buf.free(), 32 - 8);
680            buf.push(&data).unwrap();
681            assert_eq!(buf.free(), (32usize - 16).saturating_sub(n));
682        }
683    }
684
685    #[test]
686    fn push_sum_of_lengths_possible() {
687        let mut buf = BytearrayRingbuffer::<32>::new();
688        // push 2 x 8 bytes
689        assert_eq!(buf.free(), 32 - 8);
690        buf.push(b"01234567").unwrap();
691        assert_eq!(buf.free(), 32 - 8 - 16);
692        buf.push(b"01234567").unwrap();
693        assert_eq!(buf.free(), 0);
694    }
695
696    #[test]
697    fn push_pop() {
698        const N: usize = 64;
699        for start_offset in 0..N {
700            eprintln!("--------------");
701            let mut buf = BytearrayRingbuffer::<N>::new();
702            buf.head = start_offset;
703            buf.tail = start_offset;
704
705            let data = b"01234567";
706            buf.push(data).unwrap();
707
708            let p = buf.pop_front().unwrap();
709            let mut out = Vec::new();
710            out.extend_from_slice(p.a);
711            out.extend_from_slice(p.b);
712
713            dbg!(out.as_slice());
714            assert!(data == out.as_slice());
715
716            assert_eq!(buf.head, buf.tail);
717            assert_eq!(buf.bytes_unused(), N);
718        }
719    }
720
721    #[test]
722    fn push_read_back() {
723        let data = [b"hello world" as &[u8], b"", b"test"];
724
725        const N: usize = 64;
726        for start_offset in 0..N {
727            let mut buf = BytearrayRingbuffer::<N>::new();
728            buf.head = start_offset;
729            buf.tail = start_offset;
730
731            for &d in &data {
732                buf.push(d).unwrap();
733            }
734
735            // test forward iteration
736            let mut it = buf.iter();
737            for &d in data.iter() {
738                let p = it.next().unwrap();
739                let mut ab = Vec::new();
740                ab.extend_from_slice(p.a);
741                ab.extend_from_slice(p.b);
742                let ab = ab.as_slice();
743                assert_eq!(d, ab);
744            }
745            assert_eq!(it.next(), None);
746
747            // test backward iteration
748            let mut it = buf.iter_backwards();
749            for &d in data.iter().rev() {
750                let p = it.next().unwrap();
751                let mut ab = Vec::new();
752                ab.extend_from_slice(p.a);
753                ab.extend_from_slice(p.b);
754                let ab = ab.as_slice();
755                assert_eq!(d, ab);
756            }
757            assert_eq!(it.next(), None);
758        }
759    }
760
761    #[test]
762    fn push_count() {
763        let mut buf = BytearrayRingbuffer::<64>::new();
764        buf.push(b"1234").unwrap();
765        assert_eq!(buf.count(), 1);
766        buf.push(b"1234").unwrap();
767        assert_eq!(buf.count(), 2);
768        buf.push(b"1234").unwrap();
769        assert_eq!(buf.count(), 3);
770    }
771
772    fn test_with_readback<const N: usize>(words: &[&'static str]) {
773        eprintln!("--------------------------");
774        let mut buf = BytearrayRingbuffer::<N>::new();
775        let mut current_words = VecDeque::new();
776        for &word in words {
777            eprintln!("adding {word:?}");
778            let word = word.to_owned();
779            let current_bytes: usize = current_words.iter().map(|w: &String| w.len() + 8).sum();
780            if current_bytes + 8 + word.len() > N {
781                current_words.pop_front();
782            }
783
784            buf.push_force(word.as_bytes()).unwrap();
785            current_words.push_back(word);
786
787            for (p, word) in buf.iter_backwards().zip(current_words.iter().rev()) {
788                eprintln!("read back {word:?}");
789                let mut st = String::new();
790                st.push_str(core::str::from_utf8(p.a).unwrap());
791                st.push_str(core::str::from_utf8(p.b).unwrap());
792                assert_eq!(st, *word);
793            }
794        }
795    }
796
797    #[test]
798    fn readback_various() {
799        test_with_readback::<32>(&["ab", "123", "hello", "world"]);
800        test_with_readback::<32>(&["", "", "a", "", "", ""]);
801        test_with_readback::<32>(&["", "", "ab", "", "", ""]);
802        test_with_readback::<32>(&["", "", "abc", "", "", ""]);
803        test_with_readback::<32>(&["", "", "abcd", "", "", ""]);
804        test_with_readback::<32>(&["", "", "abcde", "", "", ""]);
805
806        test_with_readback::<24>(&["0", "1", "a", "2", "3", "4"]);
807        test_with_readback::<24>(&["0", "1", "ab", "2", "3", "4"]);
808        test_with_readback::<24>(&["0", "1", "abc", "2", "3", "4"]);
809        test_with_readback::<24>(&["0", "1", "abcd", "2", "3", "4"]);
810        test_with_readback::<24>(&["0", "1", "abcde", "2", "3", "4"]);
811        test_with_readback::<24>(&["0", "1", "abcdef", "2", "3", "4"]);
812        test_with_readback::<24>(&["0", "1", "abcdefg", "2", "3", "4"]);
813    }
814
815    #[test]
816    fn nth_contiguous_out_of_range_returns_none() {
817        let mut buf = BytearrayRingbuffer::<64>::new();
818        buf.push(b"hello").unwrap();
819        assert_eq!(buf.count(), 1);
820
821        assert_eq!(buf.nth_contiguous(1), None);
822    }
823
824    #[test]
825    fn rotate_contiguous() {
826        const N: usize = 48;
827        let data: [&[u8]; _] = [b"012345", b"hello world", b"xyz"];
828
829        for offset in 0..N {
830            let mut buf = BytearrayRingbuffer::<N>::new();
831            buf.head = offset;
832            buf.tail = offset;
833
834            for &d in &data {
835                buf.push(d).unwrap();
836            }
837
838            let read = buf.nth_contiguous(1).unwrap();
839            assert_eq!(data[1], read);
840
841            // check if the contents are still the same
842            for (&r, p) in data.iter().zip(buf.iter()) {
843                let mut out = Vec::new();
844                out.extend_from_slice(p.a);
845                out.extend_from_slice(p.b);
846                assert_eq!(out.as_slice(), r);
847            }
848        }
849    }
850
851    // ---- multipart push tests ----
852
853    fn collect(a: &[u8], b: &[u8]) -> Vec<u8> {
854        let mut v = Vec::new();
855        v.extend_from_slice(a);
856        v.extend_from_slice(b);
857        v
858    }
859
860    #[test]
861    fn multipart_normal_fits() {
862        const N: usize = 64;
863        for offset in 0..N {
864            let mut buf = BytearrayRingbuffer::<N>::new();
865            buf.head = offset;
866            buf.tail = offset;
867
868            let mut mp = buf.push_multipart().unwrap();
869            mp.push(b"hello").unwrap();
870            mp.push(b" ").unwrap();
871            mp.push(b"world").unwrap();
872            drop(mp);
873
874            assert_eq!(buf.count(), 1);
875            let p = buf.pop_front().unwrap();
876            assert_eq!(collect(p.a, p.b), b"hello world");
877            assert_eq!(buf.count(), 0);
878        }
879    }
880
881    #[test]
882    fn multipart_empty_packet() {
883        let mut buf = BytearrayRingbuffer::<64>::new();
884        let mp = buf.push_multipart().unwrap();
885        drop(mp); // no push calls
886        assert_eq!(buf.count(), 1);
887        let p = buf.pop_front().unwrap();
888        assert_eq!(collect(p.a, p.b), b"");
889    }
890
891    #[test]
892    fn multipart_normal_overflow_returns_err() {
893        // Buffer: 24 bytes. One existing packet of 8 bytes (payload 0, 8 bytes overhead).
894        // That leaves 16 bytes unused. Starting multipart reserves 4 for the header → 12 bytes
895        // free for payload+footer. First push of 4 bytes is fine (leaves 8 for footer). Second
896        // push of 5 bytes should fail (needs 9 for data+footer, only 8 remain).
897        let mut buf = BytearrayRingbuffer::<24>::new();
898        buf.push(b"").unwrap(); // occupies 8 bytes, leaving 16 unused
899
900        let mut mp = buf.push_multipart().unwrap();
901        mp.push(b"abcd").unwrap(); // 4 bytes payload + 4 footer still needed → OK
902        let err = mp.push(b"12345"); // would need 9 bytes (5 data + 4 footer), only 8 unused → Err
903        assert!(err.is_err());
904
905        // The guard is still usable; drop it and confirm the partial write is committed.
906        drop(mp);
907        assert_eq!(buf.count(), 2);
908        // The committed packet contains only the first successful chunk.
909        let p = buf.nth(1).unwrap();
910        assert_eq!(collect(p.a, p.b), b"abcd");
911    }
912
913    #[test]
914    fn multipart_cancel_normal_mode() {
915        let mut buf = BytearrayRingbuffer::<64>::new();
916        let original_unused = buf.bytes_unused();
917        let original_count = buf.count();
918
919        let mut mp = buf.push_multipart().unwrap();
920        mp.push(b"data that will be discarded").unwrap();
921        mp.cancel();
922
923        assert_eq!(buf.count(), original_count);
924        assert_eq!(buf.bytes_unused(), original_unused);
925    }
926
927    #[test]
928    fn multipart_normal_no_room_for_start() {
929        // 24 - 8 = 16 max payload; filling with 16 bytes leaves 0 bytes unused.
930        let mut buf = BytearrayRingbuffer::<24>::new();
931        buf.push(&[0u8; 16]).unwrap(); // fills entire buffer
932        assert_eq!(buf.bytes_unused(), 0);
933        assert!(buf.push_multipart().is_err());
934    }
935
936    #[test]
937    fn multipart_force_drops_old_packets() {
938        // Buffer: 24 bytes. Push two 2-byte packets (10 bytes each → 20 used, 4 free).
939        // push_multipart_force initially pops "AA" to get 8+ bytes for the header reservation.
940        // Then push "hello world" (11 bytes) needs 11+4=15 bytes; only 10 available after the
941        // header reservation, so "BB" is also dropped, leaving 24 bytes free.
942        let mut buf = BytearrayRingbuffer::<24>::new();
943        buf.push(b"AA").unwrap(); // 10 bytes
944        buf.push(b"BB").unwrap(); // 10 bytes → 20 bytes used, 4 bytes free
945        assert_eq!(buf.count(), 2);
946
947        let mut mp = buf.push_multipart_force();
948        mp.push(b"hello world").unwrap(); // 11+4=15 needed; forces drop of both old packets
949        drop(mp);
950
951        assert_eq!(buf.count(), 1);
952        let p = buf.pop_front().unwrap();
953        assert_eq!(collect(p.a, p.b), b"hello world");
954    }
955
956    #[test]
957    fn multipart_force_cancel_drops_are_permanent() {
958        // Same setup as multipart_force_drops_old_packets but we cancel instead of committing.
959        let mut buf = BytearrayRingbuffer::<24>::new();
960        buf.push(b"AA").unwrap();
961        buf.push(b"BB").unwrap();
962        let count_before = buf.count(); // 2
963
964        let mut mp = buf.push_multipart_force();
965        mp.push(b"hello world").unwrap(); // forces drops of both "AA" and "BB"
966        mp.cancel(); // discard the new packet
967
968        // New packet is not committed, but both dropped packets are permanently gone.
969        assert!(buf.count() < count_before);
970        assert_eq!(buf.count(), 0);
971    }
972
973    #[test]
974    fn multipart_push_after_multipart() {
975        let mut buf = BytearrayRingbuffer::<64>::new();
976        {
977            let mut mp = buf.push_multipart().unwrap();
978            mp.push(b"first").unwrap();
979        }
980        buf.push(b"second").unwrap();
981
982        assert_eq!(buf.count(), 2);
983        let p = buf.nth(0).unwrap();
984        assert_eq!(collect(p.a, p.b), b"first");
985        let p = buf.nth(1).unwrap();
986        assert_eq!(collect(p.a, p.b), b"second");
987    }
988
989    #[test]
990    fn multipart_force_max_payload() {
991        // Push exactly N-8 bytes in force mode.
992        const N: usize = 32;
993        let mut buf = BytearrayRingbuffer::<N>::new();
994        buf.push(b"old").unwrap(); // will be displaced
995
996        let payload: Vec<u8> = (0..((N - 8) as u8)).collect();
997        let mut mp = buf.push_multipart_force();
998        mp.push(&payload).unwrap();
999        drop(mp);
1000
1001        assert_eq!(buf.count(), 1);
1002        let p = buf.pop_front().unwrap();
1003        assert_eq!(collect(p.a, p.b), payload);
1004    }
1005
1006    #[test]
1007    fn multipart_wraparound_all_offsets() {
1008        const N: usize = 48;
1009        for offset in 0..N {
1010            let mut buf = BytearrayRingbuffer::<N>::new();
1011            buf.head = offset;
1012            buf.tail = offset;
1013
1014            // Push a normal packet first.
1015            buf.push(b"prefix").unwrap();
1016
1017            // Multi-part push with two chunks that together wrap the ring.
1018            let mut mp = buf.push_multipart().unwrap();
1019            mp.push(b"foo").unwrap();
1020            mp.push(b"bar").unwrap();
1021            drop(mp);
1022
1023            // Push another after.
1024            buf.push(b"suffix").unwrap();
1025
1026            assert_eq!(buf.count(), 3);
1027            let p = buf.nth(0).unwrap();
1028            assert_eq!(collect(p.a, p.b), b"prefix");
1029            let p = buf.nth(1).unwrap();
1030            assert_eq!(collect(p.a, p.b), b"foobar");
1031            let p = buf.nth(2).unwrap();
1032            assert_eq!(collect(p.a, p.b), b"suffix");
1033        }
1034    }
1035
1036    // ---- pop_front / empty / count lifecycle ----
1037
1038    #[test]
1039    fn pop_front_empty_returns_none() {
1040        let mut buf = BytearrayRingbuffer::<32>::new();
1041        assert_eq!(buf.pop_front(), None);
1042    }
1043
1044    #[test]
1045    fn empty_and_count_lifecycle() {
1046        let mut buf = BytearrayRingbuffer::<32>::new();
1047
1048        // Fresh buffer is empty with count 0.
1049        assert!(buf.empty());
1050        assert_eq!(buf.count(), 0);
1051
1052        buf.push(b"a").unwrap();
1053        assert!(!buf.empty());
1054        assert_eq!(buf.count(), 1);
1055
1056        buf.push(b"b").unwrap();
1057        assert!(!buf.empty());
1058        assert_eq!(buf.count(), 2);
1059
1060        buf.pop_front().unwrap();
1061        assert!(!buf.empty());
1062        assert_eq!(buf.count(), 1);
1063
1064        buf.pop_front().unwrap();
1065        assert!(buf.empty());
1066        assert_eq!(buf.count(), 0);
1067
1068        // pop on now-empty buffer returns None.
1069        assert_eq!(buf.pop_front(), None);
1070    }
1071
1072    #[test]
1073    fn default_creates_empty_buffer() {
1074        let buf = BytearrayRingbuffer::<32>::default();
1075        assert!(buf.empty());
1076        assert_eq!(buf.count(), 0);
1077        assert_eq!(buf.free(), 32 - 8);
1078    }
1079
1080    // ---- oversized payload rejection ----
1081
1082    #[test]
1083    fn push_oversized_returns_error() {
1084        let mut buf = BytearrayRingbuffer::<16>::new();
1085        // Maximum payload is N-8 = 8 bytes; 9 bytes must be rejected.
1086        let oversized = [0u8; 9];
1087        assert!(buf.push(&oversized).is_err());
1088        // Buffer must remain unmodified.
1089        assert!(buf.empty());
1090    }
1091
1092    #[test]
1093    fn push_force_oversized_returns_error() {
1094        let mut buf = BytearrayRingbuffer::<16>::new();
1095        // Even push_force must reject payloads larger than N-8.
1096        let oversized = [0u8; 9];
1097        assert!(buf.push_force(&oversized).is_err());
1098        assert!(buf.empty());
1099    }
1100
1101    // ---- iter / iter_backwards on empty buffer ----
1102
1103    #[test]
1104    fn iter_empty_buffer() {
1105        let buf = BytearrayRingbuffer::<32>::new();
1106        assert_eq!(buf.iter().next(), None);
1107    }
1108
1109    #[test]
1110    fn iter_backwards_empty_buffer() {
1111        let buf = BytearrayRingbuffer::<32>::new();
1112        assert_eq!(buf.iter_backwards().next(), None);
1113    }
1114
1115    // ---- nth / nth_reverse ----
1116
1117    #[test]
1118    fn nth_empty_returns_none() {
1119        let buf = BytearrayRingbuffer::<32>::new();
1120        assert_eq!(buf.nth(0), None);
1121    }
1122
1123    #[test]
1124    fn nth_out_of_bounds_returns_none() {
1125        let buf = {
1126            let mut b = BytearrayRingbuffer::<64>::new();
1127            b.push(b"x").unwrap();
1128            b.push(b"y").unwrap();
1129            b
1130        };
1131        assert_eq!(buf.nth(2), None);
1132        assert_eq!(buf.nth(100), None);
1133    }
1134
1135    #[test]
1136    fn nth_reverse_basic() {
1137        let mut buf = BytearrayRingbuffer::<64>::new();
1138        buf.push(b"oldest").unwrap();
1139        buf.push(b"middle").unwrap();
1140        buf.push(b"newest").unwrap();
1141
1142        let p = buf.nth_reverse(0).unwrap();
1143        assert_eq!(collect(p.a, p.b), b"newest");
1144        let p = buf.nth_reverse(1).unwrap();
1145        assert_eq!(collect(p.a, p.b), b"middle");
1146        let p = buf.nth_reverse(2).unwrap();
1147        assert_eq!(collect(p.a, p.b), b"oldest");
1148    }
1149
1150    #[test]
1151    fn nth_reverse_empty_returns_none() {
1152        let buf = BytearrayRingbuffer::<32>::new();
1153        assert_eq!(buf.nth_reverse(0), None);
1154    }
1155
1156    #[test]
1157    fn nth_reverse_out_of_bounds_returns_none() {
1158        let buf = {
1159            let mut b = BytearrayRingbuffer::<64>::new();
1160            b.push(b"only").unwrap();
1161            b
1162        };
1163        assert_eq!(buf.nth_reverse(1), None);
1164        assert_eq!(buf.nth_reverse(99), None);
1165    }
1166
1167    // ---- nth_contiguous ----
1168
1169    #[test]
1170    fn nth_contiguous_empty_returns_none() {
1171        let mut buf = BytearrayRingbuffer::<32>::new();
1172        assert_eq!(buf.nth_contiguous(0), None);
1173    }
1174
1175    #[test]
1176    fn nth_contiguous_n0_no_rotation() {
1177        // Packet at the beginning of the buffer – no rotation needed.
1178        let mut buf = BytearrayRingbuffer::<64>::new();
1179        buf.push(b"hello").unwrap();
1180        buf.push(b"world").unwrap();
1181        // n=0 is the oldest packet, which starts at index 4 (after its 4-byte header).
1182        // It is contiguous, so the buffer should not be rotated.
1183        let slice = buf.nth_contiguous(0).unwrap();
1184        assert_eq!(slice, b"hello");
1185        // Remaining packets are still accessible.
1186        let slice = buf.nth_contiguous(1).unwrap();
1187        assert_eq!(slice, b"world");
1188    }
1189
1190    #[test]
1191    fn nth_contiguous_called_twice() {
1192        // After the first nth_contiguous triggers a rotation, the second call must
1193        // still return the correct data for the (now relocated) packet.
1194        const N: usize = 48;
1195        for offset in 0..N {
1196            let mut buf = BytearrayRingbuffer::<N>::new();
1197            buf.head = offset;
1198            buf.tail = offset;
1199
1200            buf.push(b"alpha").unwrap();
1201            buf.push(b"beta").unwrap();
1202            buf.push(b"gamma").unwrap();
1203
1204            // First call (may or may not trigger a rotation depending on offset).
1205            let slice = buf.nth_contiguous(1).unwrap();
1206            assert_eq!(slice, b"beta");
1207
1208            // Second call on the same (possibly rotated) buffer.
1209            let slice = buf.nth_contiguous(2).unwrap();
1210            assert_eq!(slice, b"gamma");
1211
1212            // Forward iteration must still be consistent.
1213            let payloads: Vec<Vec<u8>> = buf
1214                .iter()
1215                .map(|p| {
1216                    let mut v = p.a.to_vec();
1217                    v.extend_from_slice(p.b);
1218                    v
1219                })
1220                .collect();
1221            assert_eq!(payloads[0], b"alpha");
1222            assert_eq!(payloads[1], b"beta");
1223            assert_eq!(payloads[2], b"gamma");
1224        }
1225    }
1226
1227    // ---- free() edge cases ----
1228
1229    #[test]
1230    fn free_returns_zero_when_full() {
1231        // N=32, two packets of 8-byte payload each fill the buffer exactly (2×16 = 32).
1232        let mut buf = BytearrayRingbuffer::<32>::new();
1233        buf.push(b"01234567").unwrap();
1234        buf.push(b"01234567").unwrap();
1235        assert_eq!(buf.free(), 0);
1236        // A third push must fail.
1237        assert!(buf.push(b"x").is_err());
1238    }
1239
1240    // ---- push_force dropping multiple packets ----
1241
1242    #[test]
1243    fn push_force_drops_multiple_packets() {
1244        // N=32; fill with three 1-byte packets (each 9 bytes → 27 bytes used, 5 bytes free).
1245        // A 16-byte payload needs 24 bytes (16 + 8 overhead).  Dropping "a" frees 9 → 14 free
1246        // (still < 24); dropping "b" → 23 free (still < 24); dropping "c" → 32 free (≥ 24).
1247        // So all three old packets are evicted and only the new one survives.
1248        let mut buf = BytearrayRingbuffer::<32>::new();
1249        buf.push(b"a").unwrap();
1250        buf.push(b"b").unwrap();
1251        buf.push(b"c").unwrap();
1252        assert_eq!(buf.count(), 3);
1253
1254        let payload = b"0123456789abcdef"; // 16 bytes
1255        buf.push_force(payload).unwrap();
1256
1257        // Only the newly pushed packet must survive.
1258        assert_eq!(buf.count(), 1);
1259        let p = buf.pop_front().unwrap();
1260        assert_eq!(collect(p.a, p.b), payload);
1261    }
1262
1263    // ---- multipart: total payload exceeds N-8 ----
1264
1265    #[test]
1266    fn multipart_push_total_exceeds_max_returns_error() {
1267        // Buffer size 16: max payload = 16-8 = 8 bytes.
1268        // Push 5 bytes then try to push 4 more (total 9 > 8) – must fail.
1269        let mut buf = BytearrayRingbuffer::<16>::new();
1270        let mut mp = buf.push_multipart().unwrap();
1271        mp.push(b"abcde").unwrap(); // 5 bytes – fine
1272        let err = mp.push(b"wxyz"); // would bring total to 9 > 8
1273        assert!(err.is_err());
1274        drop(mp); // commits the 5-byte partial packet
1275        assert_eq!(buf.count(), 1);
1276        let p = buf.pop_front().unwrap();
1277        assert_eq!(collect(p.a, p.b), b"abcde");
1278    }
1279
1280    // ---- push_multipart_force starting from a completely full buffer ----
1281
1282    #[test]
1283    fn multipart_force_full_buffer_start() {
1284        // N=16, fill with the maximum-size single packet (8-byte payload).
1285        // push_multipart_force must drop it to reserve space for the header.
1286        let mut buf = BytearrayRingbuffer::<16>::new();
1287        buf.push(&[0u8; 8]).unwrap(); // fills the entire 16-byte buffer
1288        assert_eq!(buf.bytes_unused(), 0);
1289
1290        let mut mp = buf.push_multipart_force();
1291        mp.push(b"hi").unwrap();
1292        drop(mp);
1293
1294        assert_eq!(buf.count(), 1);
1295        let p = buf.pop_front().unwrap();
1296        assert_eq!(collect(p.a, p.b), b"hi");
1297    }
1298
1299    // ---- Packet::copy_into ----
1300
1301    #[test]
1302    fn copy_into_contiguous() {
1303        // Packet starting at offset 0: payload lies in a single contiguous slice (b is empty).
1304        let mut buf = BytearrayRingbuffer::<64>::new();
1305        buf.push(b"hello").unwrap();
1306        let p = buf.pop_front().unwrap();
1307        assert!(p.b.is_empty());
1308        let mut out = [0u8; 5];
1309        p.copy_into(&mut out);
1310        assert_eq!(&out, b"hello");
1311    }
1312
1313    #[test]
1314    fn copy_into_wrapped() {
1315        // N=16, head=tail=9: a 5-byte payload wraps (3 bytes at end, 2 bytes at start).
1316        const N: usize = 16;
1317        let mut buf = BytearrayRingbuffer::<N>::new();
1318        buf.head = 9;
1319        buf.tail = 9;
1320        buf.push(b"abcde").unwrap();
1321        let p = buf.pop_front().unwrap();
1322        assert!(!p.b.is_empty(), "expected a wrapped packet");
1323        let mut out = [0u8; 5];
1324        p.copy_into(&mut out);
1325        assert_eq!(&out, b"abcde");
1326    }
1327
1328    #[test]
1329    #[should_panic(expected = "buffer length must equal packet length")]
1330    fn copy_into_wrong_length_panics() {
1331        let mut buf = BytearrayRingbuffer::<64>::new();
1332        buf.push(b"hello").unwrap();
1333        let p = buf.pop_front().unwrap();
1334        let mut out = [0u8; 4]; // one byte too short
1335        p.copy_into(&mut out);
1336    }
1337
1338    // ---- Packet::copy_part_into ----
1339
1340    #[test]
1341    fn copy_part_into_in_a() {
1342        // Range fully within the first slice.
1343        // N=16, head=tail=9: a="abc" (3 bytes), b="de" (2 bytes).
1344        const N: usize = 16;
1345        let mut buf = BytearrayRingbuffer::<N>::new();
1346        buf.head = 9;
1347        buf.tail = 9;
1348        buf.push(b"abcde").unwrap();
1349        let p = buf.pop_front().unwrap();
1350        assert!(!p.b.is_empty(), "expected a wrapped packet");
1351        let mut out = [0u8; 2];
1352        p.copy_part_into(0..2, &mut out);
1353        assert_eq!(&out, b"ab");
1354    }
1355
1356    #[test]
1357    fn copy_part_into_in_b() {
1358        // Range fully within the second slice.
1359        // a="abc" (3 bytes), b="de" (2 bytes); range 3..4 selects "d".
1360        const N: usize = 16;
1361        let mut buf = BytearrayRingbuffer::<N>::new();
1362        buf.head = 9;
1363        buf.tail = 9;
1364        buf.push(b"abcde").unwrap();
1365        let p = buf.pop_front().unwrap();
1366        let mut out = [0u8; 1];
1367        p.copy_part_into(3..4, &mut out);
1368        assert_eq!(&out, b"d");
1369    }
1370
1371    #[test]
1372    fn copy_part_into_spanning() {
1373        // Range spanning the boundary between a and b.
1374        // a="abc" (3 bytes), b="de" (2 bytes); range 1..4 selects "bcd".
1375        const N: usize = 16;
1376        let mut buf = BytearrayRingbuffer::<N>::new();
1377        buf.head = 9;
1378        buf.tail = 9;
1379        buf.push(b"abcde").unwrap();
1380        let p = buf.pop_front().unwrap();
1381        let mut out = [0u8; 3];
1382        p.copy_part_into(1..4, &mut out);
1383        assert_eq!(&out, b"bcd");
1384    }
1385
1386    #[test]
1387    #[should_panic(expected = "buffer length must equal range length")]
1388    fn copy_part_into_wrong_buffer_length_panics() {
1389        let mut buf = BytearrayRingbuffer::<64>::new();
1390        buf.push(b"hello").unwrap();
1391        let p = buf.pop_front().unwrap();
1392        let mut out = [0u8; 3]; // range is 0..2 (2 bytes) but buffer is 3 bytes
1393        p.copy_part_into(0..2, &mut out);
1394    }
1395
1396    #[test]
1397    #[should_panic(expected = "range out of bounds")]
1398    fn copy_part_into_out_of_bounds_panics() {
1399        let mut buf = BytearrayRingbuffer::<64>::new();
1400        buf.push(b"hello").unwrap(); // 5 bytes
1401        let p = buf.pop_front().unwrap();
1402        let mut out = [0u8; 2];
1403        p.copy_part_into(4..6, &mut out); // end=6 > total=5
1404    }
1405
1406    // ---- Packet::len / is_empty ----
1407
1408    #[test]
1409    fn packet_len_contiguous() {
1410        let mut buf = BytearrayRingbuffer::<64>::new();
1411        buf.push(b"hello").unwrap();
1412        let p = buf.pop_front().unwrap();
1413        assert_eq!(p.len(), 5);
1414        assert!(!p.is_empty());
1415    }
1416
1417    #[test]
1418    fn packet_len_wrapped() {
1419        const N: usize = 16;
1420        let mut buf = BytearrayRingbuffer::<N>::new();
1421        buf.head = 9;
1422        buf.tail = 9;
1423        buf.push(b"abcde").unwrap();
1424        let p = buf.pop_front().unwrap();
1425        assert!(!p.b.is_empty(), "expected a wrapped packet");
1426        assert_eq!(p.len(), 5);
1427        assert!(!p.is_empty());
1428    }
1429
1430    #[test]
1431    fn packet_len_empty_payload() {
1432        let mut buf = BytearrayRingbuffer::<64>::new();
1433        buf.push(b"").unwrap();
1434        let p = buf.pop_front().unwrap();
1435        assert_eq!(p.len(), 0);
1436        assert!(p.is_empty());
1437    }
1438}