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