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