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
34impl<const N: usize> BytearrayRingbuffer<N> {
35    /// Creates an empty ring buffer.
36    ///
37    /// # Panics
38    ///
39    /// Panics at compile time if `N <= 8` or `N >= u32::MAX`.
40    pub const fn new() -> Self {
41        assert!(N > 8);
42        assert!(N < (u32::MAX as usize));
43        Self {
44            buffer: [0; N],
45            head: 0,
46            tail: 0,
47            count: 0,
48        }
49    }
50
51    /// Returns the largest payload length that can fit in the currently unused byte range, after
52    /// accounting for the 8-byte packet framing (two `u32` lengths).
53    ///
54    /// Computed from the unused span between write and read positions, minus `8`, saturated at zero.
55    pub const fn free(&self) -> usize {
56        self.bytes_unused().saturating_sub(8)
57    }
58
59    /// Appends `data` as the newest packet.
60    ///
61    /// # Errors
62    ///
63    /// Returns [`NotEnoughSpaceError`] if fewer than `data.len() + 8` bytes are unused.
64    ///
65    /// # Panics
66    ///
67    /// Panics if `data.len() > u32::MAX` (debug assertion).
68    pub fn push(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
69        self._push(data, false)
70    }
71
72    /// Appends `data` as the newest packet, dropping the oldest packets until there is room.
73    ///
74    /// Unlike [`Self::push`], this never fails for lack of space as long as a single packet can fit
75    /// in the backing array (`data.len() <= N - 8`).
76    ///
77    /// # Errors
78    ///
79    /// Returns [`NotEnoughSpaceError`] only when `data.len() > N - 8` (one frame cannot fit at all).
80    pub fn push_force(&mut self, data: &[u8]) -> Result<(), NotEnoughSpaceError> {
81        self._push(data, true)
82    }
83
84    /// Returns `true` if there are no packets stored.
85    #[inline(always)]
86    pub const fn empty(&self) -> bool {
87        self.count == 0
88    }
89
90    /// Number of bytes in the ring between `head` and `tail` that do not belong to any packet.
91    const fn bytes_unused(&self) -> usize {
92        if self.empty() {
93            N
94        } else if self.head > self.tail {
95            N + self.tail - self.head
96        } else {
97            self.tail - self.head
98        }
99    }
100
101    fn _push(&mut self, data: &[u8], force: bool) -> Result<(), NotEnoughSpaceError> {
102        assert!(data.len() <= u32::MAX as usize);
103
104        // data is longer than entire buffer
105        if data.len() > N - 8 {
106            return Err(NotEnoughSpaceError);
107        }
108
109        // need to overwrite old data to fit new data
110        if (data.len() + 8) > self.bytes_unused() {
111            if !force {
112                return Err(NotEnoughSpaceError);
113            }
114            while (data.len() + 8) > self.bytes_unused() {
115                self.pop_front();
116            }
117        }
118
119        // write length + data + length
120        let addr_a = self.head;
121        let addr_b = add_wrapping::<N>(self.head, 4);
122        let addr_c = add_wrapping::<N>(self.head, 4 + data.len());
123        let len_buffer: [u8; 4] = (data.len() as u32).to_ne_bytes();
124        write_wrapping(&mut self.buffer, addr_a, &len_buffer);
125        write_wrapping(&mut self.buffer, addr_b, data);
126        write_wrapping(&mut self.buffer, addr_c, &len_buffer);
127
128        self.head = add_wrapping::<N>(self.head, 8 + data.len());
129        self.count += 1;
130
131        Ok(())
132    }
133
134    /// Removes and returns the oldest packet.
135    ///
136    /// The payload may be split across the end of the backing array; concatenate the two slices to
137    /// reconstruct `data`. If the payload is contiguous, the second slice is empty.
138    pub fn pop_front(&mut self) -> Option<(&[u8], &[u8])> {
139        if self.empty() {
140            return None;
141        }
142        let mut len_buffer = [0; 4];
143        read_wrapping(&self.buffer, self.tail, &mut len_buffer);
144        let len = u32::from_ne_bytes(len_buffer) as usize;
145
146        let index_data = add_wrapping::<N>(self.tail, 4);
147        let len_a = (N - index_data).min(len);
148        let a = &self.buffer[index_data..index_data + len_a];
149        let b = if len_a == len {
150            &[]
151        } else {
152            &self.buffer[..len - len_a]
153        };
154
155        self.tail = add_wrapping::<N>(self.tail, len + 8);
156        self.count -= 1;
157        Some((a, b))
158    }
159
160    /// Borrows the buffer and yields packets from newest to oldest.
161    pub fn iter_backwards<'a>(&'a self) -> IterBackwards<'a, N> {
162        IterBackwards {
163            buffer: &self.buffer,
164            head: self.head,
165            count: self.count,
166        }
167    }
168
169    /// Borrows the buffer and yields packets from oldest to newest.
170    pub fn iter<'a>(&'a self) -> Iter<'a, N> {
171        Iter {
172            buffer: &self.buffer,
173            head: self.head,
174            tail: self.tail,
175            count: self.count,
176        }
177    }
178
179    /// Returns how many packets are stored.
180    #[inline(always)]
181    pub const fn count(&self) -> usize {
182        self.count
183    }
184
185    /// Returns the `n`-th packet in oldest-to-newest order (`n == 0` is the oldest).
186    ///
187    /// Same as [`Iterator::nth`] on [`Self::iter`].
188    pub fn nth(&self, n: usize) -> Option<(&[u8], &[u8])> {
189        self.iter().nth(n)
190    }
191
192    /// Returns the `n`-th packet in newest-to-oldest order (`n == 0` is the newest).
193    ///
194    /// Same as [`Iterator::nth`] on [`Self::iter_backwards`].
195    pub fn nth_reverse(&self, n: usize) -> Option<(&[u8], &[u8])> {
196        self.iter_backwards().nth(n)
197    }
198
199    /// Returns the `n`-th packet in oldest-to-newest order as a single contiguous slice.
200    ///
201    /// If the payload already lies in one contiguous range of the backing array, returns that
202    /// subslice. If it wraps around the end of the ring, rotates the array in place so the payload is
203    /// contiguous at the front, adjusts internal indices, and returns a prefix of the array.
204    ///
205    /// `n == 0` is the oldest packet. Returns [`None`] if the buffer is empty or if `n >= count()`.
206    pub fn nth_contiguous(&mut self, mut n: usize) -> Option<&[u8]> {
207        if self.empty() || n >= self.count {
208            return None;
209        }
210
211        // iterate through buffer until we find this one
212        let mut tail = self.tail;
213        let len_data = loop {
214            let mut buf = [0u8; 4];
215            read_wrapping(&self.buffer, tail, &mut buf);
216            let len_data = u32::from_ne_bytes(buf) as usize;
217
218            if n == 0 {
219                break len_data;
220            }
221            n -= 1;
222
223            tail = add_wrapping::<N>(tail, len_data + 8);
224        };
225
226        let index_data = add_wrapping::<N>(tail, 4);
227
228        // happy path, no rotate necessary
229        if index_data + len_data <= N {
230            return Some(&self.buffer[index_data..index_data + len_data]);
231        }
232
233        // otherwise rotate
234        self.buffer.rotate_left(index_data);
235        self.tail = sub_wrapping::<N>(self.tail, index_data);
236        self.head = sub_wrapping::<N>(self.head, index_data);
237
238        Some(&self.buffer[..len_data])
239    }
240}
241
242/// Iterator over packets from newest to oldest. See [`BytearrayRingbuffer::iter_backwards`].
243pub struct IterBackwards<'a, const N: usize> {
244    buffer: &'a [u8; N],
245    head: usize,
246    count: usize,
247}
248
249impl<'a, const N: usize> Iterator for IterBackwards<'a, N> {
250    type Item = (&'a [u8], &'a [u8]);
251
252    fn next(&mut self) -> Option<Self::Item> {
253        if self.count == 0 {
254            return None;
255        }
256
257        // read length of newest packet
258        let index_len = sub_wrapping::<N>(self.head, 4);
259        let mut buf = [0u8; 4];
260        read_wrapping(self.buffer, index_len, &mut buf);
261        let len_data = u32::from_ne_bytes(buf) as usize;
262        debug_assert!((len_data + 8) <= N);
263
264        #[cfg(test)]
265        {
266            let index_len = sub_wrapping::<N>(self.head, 8 + len_data);
267            let mut buf = [0u8; 4];
268            read_wrapping(self.buffer, index_len, &mut buf);
269            let len_2 = u32::from_ne_bytes(buf) as usize;
270            assert_eq!(len_data, len_2);
271        }
272
273        // read out data
274        let index_data = sub_wrapping::<N>(self.head, 4 + len_data);
275        let first = (N - index_data).min(len_data);
276        let slice_a = &self.buffer[index_data..index_data + first];
277        let slice_b = if first < len_data {
278            &self.buffer[..len_data - first]
279        } else {
280            &[]
281        };
282
283        self.head = sub_wrapping::<N>(self.head, 8 + len_data);
284        self.count -= 1;
285
286        Some((slice_a, slice_b))
287    }
288}
289
290impl<const N: usize> Default for BytearrayRingbuffer<N> {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296/// Iterator over packets from oldest to newest. See [`BytearrayRingbuffer::iter`].
297pub struct Iter<'a, const N: usize> {
298    buffer: &'a [u8; N],
299    head: usize,
300    tail: usize,
301    count: usize,
302}
303
304impl<'a, const N: usize> Iterator for Iter<'a, N> {
305    type Item = (&'a [u8], &'a [u8]);
306
307    fn next(&mut self) -> Option<Self::Item> {
308        if self.count == 0 {
309            return None;
310        }
311
312        // Occupied span (same as `N - bytes_unused()` for a non-empty queue).
313        let bytes_unused = if self.head > self.tail {
314            N + self.tail - self.head
315        } else {
316            self.tail - self.head
317        };
318        let bytes_occupied = N - bytes_unused;
319        debug_assert!(bytes_occupied >= 8);
320
321        // Oldest packet length at `tail`.
322        let mut buf = [0u8; 4];
323        read_wrapping(self.buffer, self.tail, &mut buf);
324        let len_data = u32::from_ne_bytes(buf) as usize;
325        debug_assert!((len_data + 8) <= N);
326        debug_assert!((len_data + 8) <= bytes_occupied);
327
328        // read out data
329        let index_data = add_wrapping::<N>(self.tail, 4);
330        let first = (N - index_data).min(len_data);
331        let slice_a = &self.buffer[index_data..index_data + first];
332        let slice_b = if first < len_data {
333            &self.buffer[..len_data - first]
334        } else {
335            &[]
336        };
337
338        self.tail = add_wrapping::<N>(self.tail, 8 + len_data);
339        self.count -= 1;
340
341        Some((slice_a, slice_b))
342    }
343}
344
345fn add_wrapping<const N: usize>(addr: usize, offset: usize) -> usize {
346    debug_assert!(addr < N);
347    debug_assert!(offset <= N);
348    let s = addr + offset;
349    if s < N { s } else { s - N }
350}
351
352fn sub_wrapping<const N: usize>(addr: usize, offset: usize) -> usize {
353    debug_assert!(addr < N);
354    debug_assert!(offset <= N);
355    if addr >= offset {
356        addr - offset
357    } else {
358        N + addr - offset
359    }
360}
361
362/// Copies `data` into `buffer` starting at `index`, continuing at index `0` if the write crosses the end.
363fn write_wrapping(buffer: &mut [u8], index: usize, data: &[u8]) {
364    let first = (buffer.len() - index).min(data.len());
365    buffer[index..index + first].copy_from_slice(&data[..first]);
366    if first < data.len() {
367        buffer[..data.len() - first].copy_from_slice(&data[first..]);
368    }
369}
370
371/// Fills `data` from `buffer` starting at `index`, wrapping to index `0` when the read crosses the end.
372fn read_wrapping(buffer: &[u8], index: usize, data: &mut [u8]) {
373    let first = (buffer.len() - index).min(data.len());
374    data[..first].copy_from_slice(&buffer[index..index + first]);
375    if first < data.len() {
376        let remaining = data.len() - first;
377        data[first..].copy_from_slice(&buffer[..remaining]);
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use std::collections::VecDeque;
384
385    use super::BytearrayRingbuffer;
386
387    #[test]
388    fn push_some_packets() {
389        const N: usize = 64;
390        for start_offset in 0..N {
391            let mut buf = BytearrayRingbuffer::<N>::new();
392            buf.head = start_offset;
393            buf.tail = start_offset;
394
395            let free = 64 - 8;
396            assert_eq!(buf.free(), free);
397
398            buf.push(b"01234567").unwrap();
399            let free = free - 8 - 8;
400            assert_eq!(buf.free(), free);
401
402            buf.push(b"").unwrap();
403            let free = free - 8;
404            assert_eq!(buf.free(), free);
405
406            buf.push(b"0123").unwrap();
407            let free = free - 4 - 8;
408            assert_eq!(buf.free(), free);
409
410            buf.push(b"0123").unwrap();
411            let free = free - 4 - 8;
412            assert_eq!(buf.free(), free);
413        }
414    }
415
416    #[test]
417    fn push_force() {
418        let mut buf = BytearrayRingbuffer::<16>::new();
419        assert_eq!(buf.bytes_unused(), 16);
420
421        let a = b"012345";
422        let b = b"0123";
423
424        buf.push(a).unwrap();
425        assert_eq!(buf.bytes_unused(), 16 - a.len() - 8);
426
427        buf.push(b).unwrap_err();
428        assert_eq!(buf.bytes_unused(), 16 - a.len() - 8);
429
430        buf.push_force(b).unwrap();
431        assert_eq!(buf.bytes_unused(), 16 - b.len() - 8);
432    }
433
434    #[test]
435    fn push_all_data_lengths() {
436        for n in 0..(32 - 8) {
437            let mut buf = BytearrayRingbuffer::<32>::new();
438            // push n bytes
439            let data = (0..n as u8).collect::<Vec<u8>>();
440
441            assert_eq!(buf.free(), 32 - 8);
442            buf.push(&data).unwrap();
443            assert_eq!(buf.free(), (32usize - 16).saturating_sub(n));
444        }
445    }
446
447    #[test]
448    fn push_sum_of_lengths_possible() {
449        let mut buf = BytearrayRingbuffer::<32>::new();
450        // push 2 x 8 bytes
451        assert_eq!(buf.free(), 32 - 8);
452        buf.push(b"01234567").unwrap();
453        assert_eq!(buf.free(), 32 - 8 - 16);
454        buf.push(b"01234567").unwrap();
455        assert_eq!(buf.free(), 0);
456    }
457
458    #[test]
459    fn push_pop() {
460        const N: usize = 64;
461        for start_offset in 0..N {
462            eprintln!("--------------");
463            let mut buf = BytearrayRingbuffer::<N>::new();
464            buf.head = start_offset;
465            buf.tail = start_offset;
466
467            let data = b"01234567";
468            buf.push(data).unwrap();
469
470            let (a, b) = buf.pop_front().unwrap();
471            let mut out = Vec::new();
472            out.extend_from_slice(a);
473            out.extend_from_slice(b);
474
475            dbg!(out.as_slice());
476            assert!(data == out.as_slice());
477
478            assert_eq!(buf.head, buf.tail);
479            assert_eq!(buf.bytes_unused(), N);
480        }
481    }
482
483    #[test]
484    fn push_read_back() {
485        let data = [b"hello world" as &[u8], b"", b"test"];
486
487        const N: usize = 64;
488        for start_offset in 0..N {
489            let mut buf = BytearrayRingbuffer::<N>::new();
490            buf.head = start_offset;
491            buf.tail = start_offset;
492
493            for &d in &data {
494                buf.push(d).unwrap();
495            }
496
497            // test forward iteration
498            let mut it = buf.iter();
499            for &d in data.iter() {
500                let (a, b) = it.next().unwrap();
501                let mut ab = Vec::new();
502                ab.extend_from_slice(a);
503                ab.extend_from_slice(b);
504                let ab = ab.as_slice();
505                assert_eq!(d, ab);
506            }
507            assert_eq!(it.next(), None);
508
509            // test backward iteration
510            let mut it = buf.iter_backwards();
511            for &d in data.iter().rev() {
512                let (a, b) = it.next().unwrap();
513                let mut ab = Vec::new();
514                ab.extend_from_slice(a);
515                ab.extend_from_slice(b);
516                let ab = ab.as_slice();
517                assert_eq!(d, ab);
518            }
519            assert_eq!(it.next(), None);
520        }
521    }
522
523    #[test]
524    fn push_count() {
525        let mut buf = BytearrayRingbuffer::<64>::new();
526        buf.push(b"1234").unwrap();
527        assert_eq!(buf.count(), 1);
528        buf.push(b"1234").unwrap();
529        assert_eq!(buf.count(), 2);
530        buf.push(b"1234").unwrap();
531        assert_eq!(buf.count(), 3);
532    }
533
534    fn test_with_readback<const N: usize>(words: &[&'static str]) {
535        eprintln!("--------------------------");
536        let mut buf = BytearrayRingbuffer::<N>::new();
537        let mut current_words = VecDeque::new();
538        for &word in words {
539            eprintln!("adding {word:?}");
540            let word = word.to_owned();
541            let current_bytes: usize = current_words.iter().map(|w: &String| w.len() + 8).sum();
542            if current_bytes + 8 + word.len() > N {
543                current_words.pop_front();
544            }
545
546            buf.push_force(word.as_bytes()).unwrap();
547            current_words.push_back(word);
548
549            for (a, b) in buf.iter_backwards().zip(current_words.iter().rev()) {
550                eprintln!("read back {b:?}");
551                let mut st = String::new();
552                st.push_str(core::str::from_utf8(a.0).unwrap());
553                st.push_str(core::str::from_utf8(a.1).unwrap());
554                assert_eq!(st, *b);
555            }
556        }
557    }
558
559    #[test]
560    fn readback_various() {
561        test_with_readback::<32>(&["ab", "123", "hello", "world"]);
562        test_with_readback::<32>(&["", "", "a", "", "", ""]);
563        test_with_readback::<32>(&["", "", "ab", "", "", ""]);
564        test_with_readback::<32>(&["", "", "abc", "", "", ""]);
565        test_with_readback::<32>(&["", "", "abcd", "", "", ""]);
566        test_with_readback::<32>(&["", "", "abcde", "", "", ""]);
567
568        test_with_readback::<24>(&["0", "1", "a", "2", "3", "4"]);
569        test_with_readback::<24>(&["0", "1", "ab", "2", "3", "4"]);
570        test_with_readback::<24>(&["0", "1", "abc", "2", "3", "4"]);
571        test_with_readback::<24>(&["0", "1", "abcd", "2", "3", "4"]);
572        test_with_readback::<24>(&["0", "1", "abcde", "2", "3", "4"]);
573        test_with_readback::<24>(&["0", "1", "abcdef", "2", "3", "4"]);
574        test_with_readback::<24>(&["0", "1", "abcdefg", "2", "3", "4"]);
575    }
576
577    #[test]
578    fn nth_contiguous_out_of_range_returns_none() {
579        let mut buf = BytearrayRingbuffer::<64>::new();
580        buf.push(b"hello").unwrap();
581        assert_eq!(buf.count(), 1);
582
583        assert_eq!(buf.nth_contiguous(1), None);
584    }
585
586    #[test]
587    fn rotate_contiguous() {
588        const N: usize = 48;
589        let data: [&[u8]; _] = [b"012345", b"hello world", b"xyz"];
590
591        for offset in 0..N {
592            let mut buf = BytearrayRingbuffer::<N>::new();
593            buf.head = offset;
594            buf.tail = offset;
595
596            for &d in &data {
597                buf.push(d).unwrap();
598            }
599
600            let read = buf.nth_contiguous(1).unwrap();
601            assert_eq!(data[1], read);
602
603            // check if the contents are still the same
604            for (&r, (a, b)) in data.iter().zip(buf.iter()) {
605                let mut out = Vec::new();
606                out.extend_from_slice(a);
607                out.extend_from_slice(b);
608                assert_eq!(out.as_slice(), r);
609            }
610        }
611    }
612}