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/// two slices `(a, b)` that concatenate to the full packet.
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/// Returned when a [`BytearrayRingbuffer::push`] cannot store `data` without dropping older packets.
27///
28/// For [`BytearrayRingbuffer::push`], this means the unused region is too small. For
29/// [`BytearrayRingbuffer::push_force`], this is only returned when `data.len() > N - 8` (a single
30/// packet cannot fit in the buffer at all).
31#[derive(Copy, Clone, Debug)]
32pub struct NotEnoughSpaceError;
33
34/// Guard returned by [`BytearrayRingbuffer::push_multipart`] and
35/// [`BytearrayRingbuffer::push_multipart_force`].
36///
37/// Accumulates payload bytes written via repeated [`Self::push`] calls. When dropped, the
38/// completed packet (header + payload + footer) is committed to the ring buffer. Call
39/// [`Self::cancel`] to discard the in-progress write without committing a packet.
40///
41/// In force mode any existing packets that were displaced to make room are permanently lost, even
42/// if the write is cancelled.
43pub struct MultipartPush<'a, const N: usize> {
44    buf: &'a mut BytearrayRingbuffer<N>,
45    /// Ring index where the 4-byte header will be written on finalise.
46    start: usize,
47    /// Payload bytes written so far.
48    len: usize,
49    /// Whether to drop old packets when space is tight.
50    force: bool,
51    /// Set by [`Self::cancel`]; prevents [`Drop`] from committing the packet.
52    cancelled: bool,
53}
54
55impl<'a, const N: usize> MultipartPush<'a, N> {
56    /// Appends `data` to the packet currently being written.
57    ///
58    /// May be called multiple times. The chunks are concatenated in order.
59    ///
60    /// In normal mode returns [`NotEnoughSpaceError`] when there is not enough space in the buffer
61    /// to fit `data` plus the 4-byte footer. In force mode it drops the oldest packets until there
62    /// is room.
63    ///
64    /// # Errors
65    ///
66    /// Returns [`NotEnoughSpaceError`] if:
67    /// - The total accumulated payload would exceed `N - 8` (the maximum for any single packet).
68    /// - In normal mode: there is not enough unused space.
69    /// - In force mode: even after dropping all existing packets there is still not enough space
70    ///   (meaning the total accumulated payload exceeds what can fit in the buffer).
71    pub fn push(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
72        if data.is_empty() {
73            return Ok(());
74        }
75
76        // Absolute ceiling: a single packet can never hold more than N-8 bytes.
77        if self.len + data.len() > N - 8 {
78            return Err(NotEnoughSpaceError);
79        }
80
81        // Need room for data + 4-byte footer.
82        let needed = data.len() + 4;
83
84        if self.force {
85            while self.buf.bytes_unused() < needed && !self.buf.empty() {
86                self.buf.pop_front();
87            }
88            if self.buf.bytes_unused() < needed {
89                return Err(NotEnoughSpaceError);
90            }
91        } else if self.buf.bytes_unused() < needed {
92            return Err(NotEnoughSpaceError);
93        }
94
95        write_wrapping(&mut self.buf.buffer, self.buf.head, data);
96        self.buf.head = add_wrapping::<N>(self.buf.head, data.len());
97        self.len += data.len();
98
99        Ok(())
100    }
101
102    /// Discards the in-progress packet without committing it to the ring buffer.
103    ///
104    /// Rewinds `head` to the position it had before [`BytearrayRingbuffer::push_multipart`] was
105    /// called. Any packets that were already dropped in force mode are permanently lost.
106    pub fn cancel(mut self) {
107        self.cancelled = true;
108        self.buf.head = self.start;
109        // Drop runs but the cancelled flag prevents committing.
110    }
111}
112
113impl<'a, const N: usize> Drop for MultipartPush<'a, N> {
114    fn drop(&mut self) {
115        if self.cancelled {
116            return;
117        }
118        let len_bytes: [u8; 4] = (self.len as u32).to_ne_bytes();
119        // Write header at the reserved slot.
120        write_wrapping(&mut self.buf.buffer, self.start, &len_bytes);
121        // Write footer immediately after the payload (current head).
122        write_wrapping(&mut self.buf.buffer, self.buf.head, &len_bytes);
123        self.buf.head = add_wrapping::<N>(self.buf.head, 4);
124        self.buf.count += 1;
125    }
126}
127
128impl<const N: usize> BytearrayRingbuffer<N> {
129    /// Creates an empty ring buffer.
130    ///
131    /// # Panics
132    ///
133    /// Panics at compile time if `N <= 8` or `N >= u32::MAX`.
134    pub const fn new() -> Self {
135        assert!(N > 8);
136        assert!(N < (u32::MAX as usize));
137        Self {
138            buffer: [0; N],
139            head: 0,
140            tail: 0,
141            count: 0,
142        }
143    }
144
145    /// Returns the largest payload length that can fit in the currently unused byte range, after
146    /// accounting for the 8-byte packet framing (two `u32` lengths).
147    ///
148    /// Computed from the unused span between write and read positions, minus `8`, saturated at zero.
149    pub const fn free(&self) -> usize {
150        self.bytes_unused().saturating_sub(8)
151    }
152
153    /// Appends `data` as the newest packet.
154    ///
155    /// # Errors
156    ///
157    /// Returns [`NotEnoughSpaceError`] if fewer than `data.len() + 8` bytes are unused.
158    ///
159    /// # Panics
160    ///
161    /// Panics if `data.len() > u32::MAX` (debug assertion).
162    pub fn push(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
163        self._push(data, false)
164    }
165
166    /// Appends `data` as the newest packet, dropping the oldest packets until there is room.
167    ///
168    /// Unlike [`Self::push`], this never fails for lack of space as long as a single packet can fit
169    /// in the backing array (`data.len() <= N - 8`).
170    ///
171    /// # Errors
172    ///
173    /// Returns [`NotEnoughSpaceError`] only when `data.len() > N - 8` (one frame cannot fit at all).
174    pub fn push_force(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
175        self._push(data, true)
176    }
177
178    /// Begins a multi-part push in normal mode.
179    ///
180    /// Returns a [`MultipartPush`] guard whose [`MultipartPush::push`] method appends chunks of
181    /// payload. When the guard is dropped the completed packet is committed. Call
182    /// [`MultipartPush::cancel`] to discard the write.
183    ///
184    /// # Errors
185    ///
186    /// Returns [`NotEnoughSpaceError`] if there are fewer than 8 unused bytes (not enough for even
187    /// an empty packet).
188    pub fn push_multipart(&mut self) -> Result<MultipartPush<'_, N>, NotEnoughSpaceError> {
189        // Need at least 8 bytes for the header + footer of an empty packet.
190        if self.bytes_unused() < 8 {
191            return Err(NotEnoughSpaceError);
192        }
193        let start = self.head;
194        self.head = add_wrapping::<N>(self.head, 4);
195        Ok(MultipartPush {
196            buf: self,
197            start,
198            len: 0,
199            force: false,
200            cancelled: false,
201        })
202    }
203
204    /// Begins a multi-part push in force mode.
205    ///
206    /// Like [`Self::push_multipart`] but drops the oldest packets as needed to make room. Dropped
207    /// packets are permanently lost even if the write is later cancelled.
208    ///
209    /// Returns a [`MultipartPush`] guard. Calling [`MultipartPush::push`] will drop further old
210    /// packets on demand.
211    pub fn push_multipart_force(&mut self) -> MultipartPush<'_, N> {
212        // Ensure there are at least 8 bytes free for an empty packet.
213        while self.bytes_unused() < 8 && !self.empty() {
214            self.pop_front();
215        }
216        let start = self.head;
217        self.head = add_wrapping::<N>(self.head, 4);
218        MultipartPush {
219            buf: self,
220            start,
221            len: 0,
222            force: true,
223            cancelled: false,
224        }
225    }
226
227    /// Returns `true` if there are no packets stored.
228    #[inline(always)]
229    pub const fn empty(&self) -> bool {
230        self.count == 0
231    }
232
233    /// Number of bytes in the ring between `head` and `tail` that do not belong to any packet.
234    const fn bytes_unused(&self) -> usize {
235        if self.empty() {
236            N
237        } else if self.head > self.tail {
238            N + self.tail - self.head
239        } else {
240            self.tail - self.head
241        }
242    }
243
244    fn _push(&mut self, data: &[u8], force: bool) -> Result<(), NotEnoughSpaceError> {
245        assert!(data.len() <= u32::MAX as usize);
246
247        // data is longer than entire buffer
248        if data.len() > N - 8 {
249            return Err(NotEnoughSpaceError);
250        }
251
252        // need to overwrite old data to fit new data
253        if (data.len() + 8) > self.bytes_unused() {
254            if !force {
255                return Err(NotEnoughSpaceError);
256            }
257            while (data.len() + 8) > self.bytes_unused() {
258                self.pop_front();
259            }
260        }
261
262        // write length + data + length
263        let addr_a = self.head;
264        let addr_b = add_wrapping::<N>(self.head, 4);
265        let addr_c = add_wrapping::<N>(self.head, 4 + data.len());
266        let len_buffer: [u8; 4] = (data.len() as u32).to_ne_bytes();
267        write_wrapping(&mut self.buffer, addr_a, &len_buffer);
268        write_wrapping(&mut self.buffer, addr_b, data);
269        write_wrapping(&mut self.buffer, addr_c, &len_buffer);
270
271        self.head = add_wrapping::<N>(self.head, 8 + data.len());
272        self.count += 1;
273
274        Ok(())
275    }
276
277    /// Removes and returns the oldest packet.
278    ///
279    /// The payload may be split across the end of the backing array; concatenate the two slices to
280    /// reconstruct `data`. If the payload is contiguous, the second slice is empty.
281    pub fn pop_front(&mut self) -> Option<(&[u8], &[u8])> {
282        if self.empty() {
283            return None;
284        }
285        let mut len_buffer = [0; 4];
286        read_wrapping(&self.buffer, self.tail, &mut len_buffer);
287        let len = u32::from_ne_bytes(len_buffer) as usize;
288
289        let index_data = add_wrapping::<N>(self.tail, 4);
290        let len_a = (N - index_data).min(len);
291        let a = &self.buffer[index_data..index_data + len_a];
292        let b = if len_a == len {
293            &[]
294        } else {
295            &self.buffer[..len - len_a]
296        };
297
298        self.tail = add_wrapping::<N>(self.tail, len + 8);
299        self.count -= 1;
300        Some((a, b))
301    }
302
303    /// Borrows the buffer and yields packets from newest to oldest.
304    pub fn iter_backwards<'a>(&'a self) -> IterBackwards<'a, N> {
305        IterBackwards {
306            buffer: &self.buffer,
307            head: self.head,
308            count: self.count,
309        }
310    }
311
312    /// Borrows the buffer and yields packets from oldest to newest.
313    pub fn iter<'a>(&'a self) -> Iter<'a, N> {
314        Iter {
315            buffer: &self.buffer,
316            head: self.head,
317            tail: self.tail,
318            count: self.count,
319        }
320    }
321
322    /// Returns how many packets are stored.
323    #[inline(always)]
324    pub const fn count(&self) -> usize {
325        self.count
326    }
327
328    /// Returns the `n`-th packet in oldest-to-newest order (`n == 0` is the oldest).
329    ///
330    /// Same as [`Iterator::nth`] on [`Self::iter`].
331    pub fn nth(&self, n: usize) -> Option<(&[u8], &[u8])> {
332        self.iter().nth(n)
333    }
334
335    /// Returns the `n`-th packet in newest-to-oldest order (`n == 0` is the newest).
336    ///
337    /// Same as [`Iterator::nth`] on [`Self::iter_backwards`].
338    pub fn nth_reverse(&self, n: usize) -> Option<(&[u8], &[u8])> {
339        self.iter_backwards().nth(n)
340    }
341
342    /// Returns the `n`-th packet in oldest-to-newest order as a single contiguous slice.
343    ///
344    /// If the payload already lies in one contiguous range of the backing array, returns that
345    /// subslice. If it wraps around the end of the ring, rotates the array in place so the payload is
346    /// contiguous at the front, adjusts internal indices, and returns a prefix of the array.
347    ///
348    /// `n == 0` is the oldest packet. Returns [`None`] if the buffer is empty or if `n >= count()`.
349    pub fn nth_contiguous(&mut self, mut n: usize) -> Option<&[u8]> {
350        if self.empty() || n >= self.count {
351            return None;
352        }
353
354        // iterate through buffer until we find this one
355        let mut tail = self.tail;
356        let len_data = loop {
357            let mut buf = [0u8; 4];
358            read_wrapping(&self.buffer, tail, &mut buf);
359            let len_data = u32::from_ne_bytes(buf) as usize;
360
361            if n == 0 {
362                break len_data;
363            }
364            n -= 1;
365
366            tail = add_wrapping::<N>(tail, len_data + 8);
367        };
368
369        let index_data = add_wrapping::<N>(tail, 4);
370
371        // happy path, no rotate necessary
372        if index_data + len_data <= N {
373            return Some(&self.buffer[index_data..index_data + len_data]);
374        }
375
376        // otherwise rotate
377        self.buffer.rotate_left(index_data);
378        self.tail = sub_wrapping::<N>(self.tail, index_data);
379        self.head = sub_wrapping::<N>(self.head, index_data);
380
381        Some(&self.buffer[..len_data])
382    }
383}
384
385/// Iterator over packets from newest to oldest. See [`BytearrayRingbuffer::iter_backwards`].
386pub struct IterBackwards<'a, const N: usize> {
387    buffer: &'a [u8; N],
388    head: usize,
389    count: usize,
390}
391
392impl<'a, const N: usize> Iterator for IterBackwards<'a, N> {
393    type Item = (&'a [u8], &'a [u8]);
394
395    fn next(&mut self) -> Option<Self::Item> {
396        if self.count == 0 {
397            return None;
398        }
399
400        // read length of newest packet
401        let index_len = sub_wrapping::<N>(self.head, 4);
402        let mut buf = [0u8; 4];
403        read_wrapping(self.buffer, index_len, &mut buf);
404        let len_data = u32::from_ne_bytes(buf) as usize;
405        debug_assert!((len_data + 8) <= N);
406
407        #[cfg(test)]
408        {
409            let index_len = sub_wrapping::<N>(self.head, 8 + len_data);
410            let mut buf = [0u8; 4];
411            read_wrapping(self.buffer, index_len, &mut buf);
412            let len_2 = u32::from_ne_bytes(buf) as usize;
413            assert_eq!(len_data, len_2);
414        }
415
416        // read out data
417        let index_data = sub_wrapping::<N>(self.head, 4 + len_data);
418        let first = (N - index_data).min(len_data);
419        let slice_a = &self.buffer[index_data..index_data + first];
420        let slice_b = if first < len_data {
421            &self.buffer[..len_data - first]
422        } else {
423            &[]
424        };
425
426        self.head = sub_wrapping::<N>(self.head, 8 + len_data);
427        self.count -= 1;
428
429        Some((slice_a, slice_b))
430    }
431}
432
433impl<const N: usize> Default for BytearrayRingbuffer<N> {
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439/// Iterator over packets from oldest to newest. See [`BytearrayRingbuffer::iter`].
440pub struct Iter<'a, const N: usize> {
441    buffer: &'a [u8; N],
442    head: usize,
443    tail: usize,
444    count: usize,
445}
446
447impl<'a, const N: usize> Iterator for Iter<'a, N> {
448    type Item = (&'a [u8], &'a [u8]);
449
450    fn next(&mut self) -> Option<Self::Item> {
451        if self.count == 0 {
452            return None;
453        }
454
455        // Occupied span (same as `N - bytes_unused()` for a non-empty queue).
456        let bytes_unused = if self.head > self.tail {
457            N + self.tail - self.head
458        } else {
459            self.tail - self.head
460        };
461        let bytes_occupied = N - bytes_unused;
462        debug_assert!(bytes_occupied >= 8);
463
464        // Oldest packet length at `tail`.
465        let mut buf = [0u8; 4];
466        read_wrapping(self.buffer, self.tail, &mut buf);
467        let len_data = u32::from_ne_bytes(buf) as usize;
468        debug_assert!((len_data + 8) <= N);
469        debug_assert!((len_data + 8) <= bytes_occupied);
470
471        // read out data
472        let index_data = add_wrapping::<N>(self.tail, 4);
473        let first = (N - index_data).min(len_data);
474        let slice_a = &self.buffer[index_data..index_data + first];
475        let slice_b = if first < len_data {
476            &self.buffer[..len_data - first]
477        } else {
478            &[]
479        };
480
481        self.tail = add_wrapping::<N>(self.tail, 8 + len_data);
482        self.count -= 1;
483
484        Some((slice_a, slice_b))
485    }
486}
487
488fn add_wrapping<const N: usize>(addr: usize, offset: usize) -> usize {
489    debug_assert!(addr < N);
490    debug_assert!(offset <= N);
491    let s = addr + offset;
492    if s < N { s } else { s - N }
493}
494
495fn sub_wrapping<const N: usize>(addr: usize, offset: usize) -> usize {
496    debug_assert!(addr < N);
497    debug_assert!(offset <= N);
498    if addr >= offset {
499        addr - offset
500    } else {
501        N + addr - offset
502    }
503}
504
505/// Copies `data` into `buffer` starting at `index`, continuing at index `0` if the write crosses the end.
506fn write_wrapping(buffer: &mut [u8], index: usize, data: &[u8]) {
507    let first = (buffer.len() - index).min(data.len());
508    buffer[index..index + first].copy_from_slice(&data[..first]);
509    if first < data.len() {
510        buffer[..data.len() - first].copy_from_slice(&data[first..]);
511    }
512}
513
514/// Fills `data` from `buffer` starting at `index`, wrapping to index `0` when the read crosses the end.
515fn read_wrapping(buffer: &[u8], index: usize, data: &mut [u8]) {
516    let first = (buffer.len() - index).min(data.len());
517    data[..first].copy_from_slice(&buffer[index..index + first]);
518    if first < data.len() {
519        let remaining = data.len() - first;
520        data[first..].copy_from_slice(&buffer[..remaining]);
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use std::collections::VecDeque;
527
528    use super::BytearrayRingbuffer;
529
530    #[test]
531    fn push_some_packets() {
532        const N: usize = 64;
533        for start_offset in 0..N {
534            let mut buf = BytearrayRingbuffer::<N>::new();
535            buf.head = start_offset;
536            buf.tail = start_offset;
537
538            let free = 64 - 8;
539            assert_eq!(buf.free(), free);
540
541            buf.push(b"01234567").unwrap();
542            let free = free - 8 - 8;
543            assert_eq!(buf.free(), free);
544
545            buf.push(b"").unwrap();
546            let free = free - 8;
547            assert_eq!(buf.free(), free);
548
549            buf.push(b"0123").unwrap();
550            let free = free - 4 - 8;
551            assert_eq!(buf.free(), free);
552
553            buf.push(b"0123").unwrap();
554            let free = free - 4 - 8;
555            assert_eq!(buf.free(), free);
556        }
557    }
558
559    #[test]
560    fn push_force() {
561        let mut buf = BytearrayRingbuffer::<16>::new();
562        assert_eq!(buf.bytes_unused(), 16);
563
564        let a = b"012345";
565        let b = b"0123";
566
567        buf.push(a).unwrap();
568        assert_eq!(buf.bytes_unused(), 16 - a.len() - 8);
569
570        buf.push(b).unwrap_err();
571        assert_eq!(buf.bytes_unused(), 16 - a.len() - 8);
572
573        buf.push_force(b).unwrap();
574        assert_eq!(buf.bytes_unused(), 16 - b.len() - 8);
575    }
576
577    #[test]
578    fn push_all_data_lengths() {
579        for n in 0..(32 - 8) {
580            let mut buf = BytearrayRingbuffer::<32>::new();
581            // push n bytes
582            let data = (0..n as u8).collect::<Vec<u8>>();
583
584            assert_eq!(buf.free(), 32 - 8);
585            buf.push(&data).unwrap();
586            assert_eq!(buf.free(), (32usize - 16).saturating_sub(n));
587        }
588    }
589
590    #[test]
591    fn push_sum_of_lengths_possible() {
592        let mut buf = BytearrayRingbuffer::<32>::new();
593        // push 2 x 8 bytes
594        assert_eq!(buf.free(), 32 - 8);
595        buf.push(b"01234567").unwrap();
596        assert_eq!(buf.free(), 32 - 8 - 16);
597        buf.push(b"01234567").unwrap();
598        assert_eq!(buf.free(), 0);
599    }
600
601    #[test]
602    fn push_pop() {
603        const N: usize = 64;
604        for start_offset in 0..N {
605            eprintln!("--------------");
606            let mut buf = BytearrayRingbuffer::<N>::new();
607            buf.head = start_offset;
608            buf.tail = start_offset;
609
610            let data = b"01234567";
611            buf.push(data).unwrap();
612
613            let (a, b) = buf.pop_front().unwrap();
614            let mut out = Vec::new();
615            out.extend_from_slice(a);
616            out.extend_from_slice(b);
617
618            dbg!(out.as_slice());
619            assert!(data == out.as_slice());
620
621            assert_eq!(buf.head, buf.tail);
622            assert_eq!(buf.bytes_unused(), N);
623        }
624    }
625
626    #[test]
627    fn push_read_back() {
628        let data = [b"hello world" as &[u8], b"", b"test"];
629
630        const N: usize = 64;
631        for start_offset in 0..N {
632            let mut buf = BytearrayRingbuffer::<N>::new();
633            buf.head = start_offset;
634            buf.tail = start_offset;
635
636            for &d in &data {
637                buf.push(d).unwrap();
638            }
639
640            // test forward iteration
641            let mut it = buf.iter();
642            for &d in data.iter() {
643                let (a, b) = it.next().unwrap();
644                let mut ab = Vec::new();
645                ab.extend_from_slice(a);
646                ab.extend_from_slice(b);
647                let ab = ab.as_slice();
648                assert_eq!(d, ab);
649            }
650            assert_eq!(it.next(), None);
651
652            // test backward iteration
653            let mut it = buf.iter_backwards();
654            for &d in data.iter().rev() {
655                let (a, b) = it.next().unwrap();
656                let mut ab = Vec::new();
657                ab.extend_from_slice(a);
658                ab.extend_from_slice(b);
659                let ab = ab.as_slice();
660                assert_eq!(d, ab);
661            }
662            assert_eq!(it.next(), None);
663        }
664    }
665
666    #[test]
667    fn push_count() {
668        let mut buf = BytearrayRingbuffer::<64>::new();
669        buf.push(b"1234").unwrap();
670        assert_eq!(buf.count(), 1);
671        buf.push(b"1234").unwrap();
672        assert_eq!(buf.count(), 2);
673        buf.push(b"1234").unwrap();
674        assert_eq!(buf.count(), 3);
675    }
676
677    fn test_with_readback<const N: usize>(words: &[&'static str]) {
678        eprintln!("--------------------------");
679        let mut buf = BytearrayRingbuffer::<N>::new();
680        let mut current_words = VecDeque::new();
681        for &word in words {
682            eprintln!("adding {word:?}");
683            let word = word.to_owned();
684            let current_bytes: usize = current_words.iter().map(|w: &String| w.len() + 8).sum();
685            if current_bytes + 8 + word.len() > N {
686                current_words.pop_front();
687            }
688
689            buf.push_force(word.as_bytes()).unwrap();
690            current_words.push_back(word);
691
692            for (a, b) in buf.iter_backwards().zip(current_words.iter().rev()) {
693                eprintln!("read back {b:?}");
694                let mut st = String::new();
695                st.push_str(core::str::from_utf8(a.0).unwrap());
696                st.push_str(core::str::from_utf8(a.1).unwrap());
697                assert_eq!(st, *b);
698            }
699        }
700    }
701
702    #[test]
703    fn readback_various() {
704        test_with_readback::<32>(&["ab", "123", "hello", "world"]);
705        test_with_readback::<32>(&["", "", "a", "", "", ""]);
706        test_with_readback::<32>(&["", "", "ab", "", "", ""]);
707        test_with_readback::<32>(&["", "", "abc", "", "", ""]);
708        test_with_readback::<32>(&["", "", "abcd", "", "", ""]);
709        test_with_readback::<32>(&["", "", "abcde", "", "", ""]);
710
711        test_with_readback::<24>(&["0", "1", "a", "2", "3", "4"]);
712        test_with_readback::<24>(&["0", "1", "ab", "2", "3", "4"]);
713        test_with_readback::<24>(&["0", "1", "abc", "2", "3", "4"]);
714        test_with_readback::<24>(&["0", "1", "abcd", "2", "3", "4"]);
715        test_with_readback::<24>(&["0", "1", "abcde", "2", "3", "4"]);
716        test_with_readback::<24>(&["0", "1", "abcdef", "2", "3", "4"]);
717        test_with_readback::<24>(&["0", "1", "abcdefg", "2", "3", "4"]);
718    }
719
720    #[test]
721    fn nth_contiguous_out_of_range_returns_none() {
722        let mut buf = BytearrayRingbuffer::<64>::new();
723        buf.push(b"hello").unwrap();
724        assert_eq!(buf.count(), 1);
725
726        assert_eq!(buf.nth_contiguous(1), None);
727    }
728
729    #[test]
730    fn rotate_contiguous() {
731        const N: usize = 48;
732        let data: [&[u8]; _] = [b"012345", b"hello world", b"xyz"];
733
734        for offset in 0..N {
735            let mut buf = BytearrayRingbuffer::<N>::new();
736            buf.head = offset;
737            buf.tail = offset;
738
739            for &d in &data {
740                buf.push(d).unwrap();
741            }
742
743            let read = buf.nth_contiguous(1).unwrap();
744            assert_eq!(data[1], read);
745
746            // check if the contents are still the same
747            for (&r, (a, b)) in data.iter().zip(buf.iter()) {
748                let mut out = Vec::new();
749                out.extend_from_slice(a);
750                out.extend_from_slice(b);
751                assert_eq!(out.as_slice(), r);
752            }
753        }
754    }
755
756    // ---- multipart push tests ----
757
758    fn collect(a: &[u8], b: &[u8]) -> Vec<u8> {
759        let mut v = Vec::new();
760        v.extend_from_slice(a);
761        v.extend_from_slice(b);
762        v
763    }
764
765    #[test]
766    fn multipart_normal_fits() {
767        const N: usize = 64;
768        for offset in 0..N {
769            let mut buf = BytearrayRingbuffer::<N>::new();
770            buf.head = offset;
771            buf.tail = offset;
772
773            let mut mp = buf.push_multipart().unwrap();
774            mp.push(b"hello").unwrap();
775            mp.push(b" ").unwrap();
776            mp.push(b"world").unwrap();
777            drop(mp);
778
779            assert_eq!(buf.count(), 1);
780            let (a, b) = buf.pop_front().unwrap();
781            assert_eq!(collect(a, b), b"hello world");
782            assert_eq!(buf.count(), 0);
783        }
784    }
785
786    #[test]
787    fn multipart_empty_packet() {
788        let mut buf = BytearrayRingbuffer::<64>::new();
789        let mp = buf.push_multipart().unwrap();
790        drop(mp); // no push calls
791        assert_eq!(buf.count(), 1);
792        let (a, b) = buf.pop_front().unwrap();
793        assert_eq!(collect(a, b), b"");
794    }
795
796    #[test]
797    fn multipart_normal_overflow_returns_err() {
798        // Buffer: 24 bytes. One existing packet of 8 bytes (payload 0, 8 bytes overhead).
799        // That leaves 16 bytes unused. Starting multipart reserves 4 for the header → 12 bytes
800        // free for payload+footer. First push of 4 bytes is fine (leaves 8 for footer). Second
801        // push of 5 bytes should fail (needs 9 for data+footer, only 8 remain).
802        let mut buf = BytearrayRingbuffer::<24>::new();
803        buf.push(b"").unwrap(); // occupies 8 bytes, leaving 16 unused
804
805        let mut mp = buf.push_multipart().unwrap();
806        mp.push(b"abcd").unwrap(); // 4 bytes payload + 4 footer still needed → OK
807        let err = mp.push(b"12345"); // would need 9 bytes (5 data + 4 footer), only 8 unused → Err
808        assert!(err.is_err());
809
810        // The guard is still usable; drop it and confirm the partial write is committed.
811        drop(mp);
812        assert_eq!(buf.count(), 2);
813        // The committed packet contains only the first successful chunk.
814        let (a, b) = buf.nth(1).unwrap();
815        assert_eq!(collect(a, b), b"abcd");
816    }
817
818    #[test]
819    fn multipart_cancel_normal_mode() {
820        let mut buf = BytearrayRingbuffer::<64>::new();
821        let original_unused = buf.bytes_unused();
822        let original_count = buf.count();
823
824        let mut mp = buf.push_multipart().unwrap();
825        mp.push(b"data that will be discarded").unwrap();
826        mp.cancel();
827
828        assert_eq!(buf.count(), original_count);
829        assert_eq!(buf.bytes_unused(), original_unused);
830    }
831
832    #[test]
833    fn multipart_normal_no_room_for_start() {
834        // 24 - 8 = 16 max payload; filling with 16 bytes leaves 0 bytes unused.
835        let mut buf = BytearrayRingbuffer::<24>::new();
836        buf.push(&[0u8; 16]).unwrap(); // fills entire buffer
837        assert_eq!(buf.bytes_unused(), 0);
838        assert!(buf.push_multipart().is_err());
839    }
840
841    #[test]
842    fn multipart_force_drops_old_packets() {
843        // Buffer: 24 bytes. Push two 2-byte packets (10 bytes each → 20 used, 4 free).
844        // push_multipart_force initially pops "AA" to get 8+ bytes for the header reservation.
845        // Then push "hello world" (11 bytes) needs 11+4=15 bytes; only 10 available after the
846        // header reservation, so "BB" is also dropped, leaving 24 bytes free.
847        let mut buf = BytearrayRingbuffer::<24>::new();
848        buf.push(b"AA").unwrap(); // 10 bytes
849        buf.push(b"BB").unwrap(); // 10 bytes → 20 bytes used, 4 bytes free
850        assert_eq!(buf.count(), 2);
851
852        let mut mp = buf.push_multipart_force();
853        mp.push(b"hello world").unwrap(); // 11+4=15 needed; forces drop of both old packets
854        drop(mp);
855
856        assert_eq!(buf.count(), 1);
857        let (a, b) = buf.pop_front().unwrap();
858        assert_eq!(collect(a, b), b"hello world");
859    }
860
861    #[test]
862    fn multipart_force_cancel_drops_are_permanent() {
863        // Same setup as multipart_force_drops_old_packets but we cancel instead of committing.
864        let mut buf = BytearrayRingbuffer::<24>::new();
865        buf.push(b"AA").unwrap();
866        buf.push(b"BB").unwrap();
867        let count_before = buf.count(); // 2
868
869        let mut mp = buf.push_multipart_force();
870        mp.push(b"hello world").unwrap(); // forces drops of both "AA" and "BB"
871        mp.cancel(); // discard the new packet
872
873        // New packet is not committed, but both dropped packets are permanently gone.
874        assert!(buf.count() < count_before);
875        assert_eq!(buf.count(), 0);
876    }
877
878    #[test]
879    fn multipart_push_after_multipart() {
880        let mut buf = BytearrayRingbuffer::<64>::new();
881        {
882            let mut mp = buf.push_multipart().unwrap();
883            mp.push(b"first").unwrap();
884        }
885        buf.push(b"second").unwrap();
886
887        assert_eq!(buf.count(), 2);
888        let (a, b) = buf.nth(0).unwrap();
889        assert_eq!(collect(a, b), b"first");
890        let (a, b) = buf.nth(1).unwrap();
891        assert_eq!(collect(a, b), b"second");
892    }
893
894    #[test]
895    fn multipart_force_max_payload() {
896        // Push exactly N-8 bytes in force mode.
897        const N: usize = 32;
898        let mut buf = BytearrayRingbuffer::<N>::new();
899        buf.push(b"old").unwrap(); // will be displaced
900
901        let payload: Vec<u8> = (0..((N - 8) as u8)).collect();
902        let mut mp = buf.push_multipart_force();
903        mp.push(&payload).unwrap();
904        drop(mp);
905
906        assert_eq!(buf.count(), 1);
907        let (a, b) = buf.pop_front().unwrap();
908        assert_eq!(collect(a, b), payload);
909    }
910
911    #[test]
912    fn multipart_wraparound_all_offsets() {
913        const N: usize = 48;
914        for offset in 0..N {
915            let mut buf = BytearrayRingbuffer::<N>::new();
916            buf.head = offset;
917            buf.tail = offset;
918
919            // Push a normal packet first.
920            buf.push(b"prefix").unwrap();
921
922            // Multi-part push with two chunks that together wrap the ring.
923            let mut mp = buf.push_multipart().unwrap();
924            mp.push(b"foo").unwrap();
925            mp.push(b"bar").unwrap();
926            drop(mp);
927
928            // Push another after.
929            buf.push(b"suffix").unwrap();
930
931            assert_eq!(buf.count(), 3);
932            let (a, b) = buf.nth(0).unwrap();
933            assert_eq!(collect(a, b), b"prefix");
934            let (a, b) = buf.nth(1).unwrap();
935            assert_eq!(collect(a, b), b"foobar");
936            let (a, b) = buf.nth(2).unwrap();
937            assert_eq!(collect(a, b), b"suffix");
938        }
939    }
940}