Skip to main content

spsc_buffer/
lib.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#![no_std]
8
9use core::sync::atomic::{AtomicU64, Ordering};
10use core::{cmp, ptr, slice};
11pub use kalloc::NoOpAllocator;
12use kalloc::{Allocator, Box, DefaultAllocator};
13use zx_status::Status;
14
15/// A simple convenience type used to hold the read and write pointers as separate values.
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17struct RingPointers {
18    read: u32,
19    write: u32,
20}
21
22impl RingPointers {
23    /// Constructs a new `RingPointers` with the given read and write offsets.
24    const fn new(read: u32, write: u32) -> Self {
25        Self { read, write }
26    }
27
28    /// Splits combined 64-bit pointers into individual read and write pointers.
29    const fn from_combined(combined: u64) -> Self {
30        Self::new((combined >> 32) as u32, combined as u32)
31    }
32
33    /// Combines read and write pointers into a single 64-bit value.
34    const fn as_combined(&self) -> u64 {
35        ((self.read as u64) << 32) | (self.write as u64)
36    }
37
38    /// Returns the amount of data available to read in the buffer.
39    const fn available_data(&self) -> u32 {
40        self.write.wrapping_sub(self.read)
41    }
42}
43
44/// A wrapper around a slot of memory in the ring buffer.
45///
46/// A Reservation has a predetermined size that is determined by the size passed into `reserve`. Any
47/// attempt to write more than this amount of data into the slot is a programming error and will
48/// cause an assertion failure. This class provides a formal way for writers to serialize data in
49/// place in the ring buffer, thus eliminating the need for a temporary serialization buffer.
50#[derive(Debug)]
51pub struct Reservation<'a> {
52    combined_pointers: &'a AtomicU64,
53    storage_len: u32,
54    initial_ring_pointers: RingPointers,
55    region1: &'a mut [u8],
56    region2: &'a mut [u8],
57    write_offset: u32,
58    committed: bool,
59}
60
61impl<'a> Drop for Reservation<'a> {
62    fn drop(&mut self) {
63        debug_assert!(self.committed, "Reservation dropped without being committed");
64    }
65}
66
67impl<'a> Reservation<'a> {
68    /// Writes the given data into this reservation.
69    pub fn write(&mut self, data: &[u8]) -> Result<(), Status> {
70        if self.committed {
71            return Err(Status::BAD_STATE);
72        }
73
74        let mut bytes_to_copy = data.len();
75        let mut region1_copy_amount = 0;
76        let mut write_offset = self.write_offset as usize;
77
78        let region1_len = self.region1.len();
79        if write_offset < region1_len {
80            let space_left_in_region1 = region1_len - write_offset;
81            region1_copy_amount = cmp::min(bytes_to_copy, space_left_in_region1);
82
83            self.region1[write_offset..write_offset + region1_copy_amount]
84                .copy_from_slice(&data[..region1_copy_amount]);
85
86            write_offset += region1_copy_amount;
87            bytes_to_copy -= region1_copy_amount;
88        }
89
90        if bytes_to_copy > 0 {
91            if write_offset < region1_len {
92                return Err(Status::BAD_STATE);
93            }
94            let region2_len = self.region2.len();
95            let region2_offset = write_offset - region1_len;
96            if region2_len < region2_offset {
97                return Err(Status::BAD_STATE);
98            }
99            if region2_len - region2_offset < bytes_to_copy {
100                return Err(Status::BUFFER_TOO_SMALL);
101            }
102
103            self.region2[region2_offset..region2_offset + bytes_to_copy]
104                .copy_from_slice(&data[region1_copy_amount..region1_copy_amount + bytes_to_copy]);
105
106            write_offset += bytes_to_copy;
107        }
108
109        self.write_offset = write_offset as u32;
110        Ok(())
111    }
112
113    /// Advances the write pointer of the associated spsc buffer.
114    ///
115    /// This makes the written data visible to the reader, and thus can only be called once all
116    /// writes have been completed and the reservation is fully written.
117    pub fn commit(mut self) -> Result<(), Status> {
118        if self.committed {
119            return Err(Status::BAD_STATE);
120        }
121        self.committed = true;
122
123        let total_len =
124            self.region1.len().checked_add(self.region2.len()).ok_or(Status::BAD_STATE)? as u32;
125        if self.write_offset != total_len {
126            return Err(Status::BAD_STATE);
127        }
128
129        advance_write_pointer(
130            self.combined_pointers,
131            self.storage_len,
132            self.initial_ring_pointers,
133            total_len,
134        )
135    }
136
137    /// Cancels this reservation, allowing it to be safely dropped without advancing the write
138    /// pointer.
139    pub fn cancel(mut self) {
140        self.committed = true;
141    }
142}
143
144/// A transactional, single-producer, single-consumer ring buffer.
145///
146/// The caller is responsible for ensuring that there is only one reader and one writer; no internal
147/// synchronization is provided to enforce this constraint.
148///
149/// Backing storage is allocated dynamically during the `init` method. The requested size must be a
150/// power of two for correct functionality.
151// TODO(https://fxbug.dev/517301686): Use bindgen or another systematic way to avoid duplicating
152// this structure and causing drift.
153#[repr(C, align(8))]
154pub struct Buffer<A: Allocator + Default = DefaultAllocator> {
155    // The read and write pointers are stored as the upper and lower halves, respectively, of a
156    // single 64-bit atomic.
157    combined_pointers: AtomicU64,
158    // The types used for `storage` and `size` must match those of ktl::span.
159    storage: *mut u8,
160    // The size of the backing storage in bytes. This is enforced to be at most
161    // MAX_STORAGE_SIZE (2 GiB), meaning this value will never exceed u32::MAX.
162    size: usize,
163    _phantom: core::marker::PhantomData<A>,
164}
165
166impl<A: Allocator + Default> Drop for Buffer<A> {
167    fn drop(&mut self) {
168        if !self.storage.is_null() {
169            let slice_ptr = ptr::slice_from_raw_parts_mut(self.storage, self.size);
170            unsafe {
171                let _ = Box::from_raw_in(slice_ptr, A::default());
172            }
173        }
174    }
175}
176
177impl<A: Allocator + Default> Buffer<A> {
178    /// Maximum size of backing storage buffer (2 GiB).
179    const MAX_STORAGE_SIZE: u32 = 1 << 31;
180
181    /// Constructs a new `Buffer` with a dynamically allocated backing storage of the given size,
182    /// using the given allocator.
183    pub fn try_new_in(size: u32, allocator: A) -> Result<Self, Status> {
184        if size > Self::MAX_STORAGE_SIZE {
185            return Err(Status::INVALID_ARGS);
186        }
187        if !size.is_power_of_two() {
188            return Err(Status::INVALID_ARGS);
189        }
190
191        let storage_box = Box::<[u8], A>::try_new_zeroed_slice_in(size as usize, allocator)
192            .map_err(|_| Status::NO_MEMORY)?;
193        let (storage_ptr, _) = Box::into_raw_with_allocator(storage_box);
194
195        Ok(Self {
196            combined_pointers: AtomicU64::new(0),
197            storage: storage_ptr as *mut u8,
198            size: size as usize,
199            _phantom: core::marker::PhantomData,
200        })
201    }
202
203    /// Returns true if the buffer has valid backing storage (non-null and valid power-of-two size).
204    pub fn is_valid(&self) -> bool {
205        !self.storage.is_null()
206            && self.size > 0
207            && self.size <= Self::MAX_STORAGE_SIZE as usize
208            && self.size.is_power_of_two()
209    }
210
211    /// Returns the size of the backing storage.
212    #[inline]
213    pub const fn size(&self) -> u32 {
214        self.size as u32
215    }
216
217    /// Reserves a block of the given size in the buffer.
218    ///
219    /// Any data written into this block will not be visible to readers until `commit` is called on
220    /// the returned `Reservation`.
221    #[inline]
222    pub fn reserve(&mut self, size: u32) -> Result<Reservation<'_>, Status> {
223        // SAFETY: `&mut self` guarantees exclusive producer access.
224        unsafe { self.reserve_shared(size) }
225    }
226
227    /// Reserves a block of the given size in the buffer via a shared reference.
228    ///
229    /// Any data written into this block will not be visible to readers until `commit` is called on
230    /// the returned `Reservation`.
231    ///
232    /// # Safety
233    ///
234    /// The caller must ensure that there is at most one active producer reservation on `self` at a
235    /// time (no concurrent calls to `reserve` or `reserve_shared`, and no live `Reservation`
236    /// overlapping with another call to `reserve` or `reserve_shared`).
237    pub unsafe fn reserve_shared(&self, size: u32) -> Result<Reservation<'_>, Status> {
238        if size == 0 || size > Self::MAX_STORAGE_SIZE {
239            return Err(Status::INVALID_ARGS);
240        }
241
242        if self.storage.is_null() {
243            return Err(Status::BAD_STATE);
244        }
245
246        let storage_len = self.size as u32;
247        if size > storage_len {
248            return Err(Status::NO_SPACE);
249        }
250
251        let initial_state = self.load_pointers();
252        let available_space = self.available_space(initial_state);
253        if available_space < size {
254            return Err(Status::NO_SPACE);
255        }
256
257        let write_offset = self.pointer_to_offset(initial_state.write);
258        let ring_break_distance = storage_len - write_offset;
259        let bytes_before_break = cmp::min(size, ring_break_distance);
260
261        // SAFETY: The creator of `Buffer` guarantees that `storage` points to a valid memory
262        // region of `self.size` bytes, the caller guarantees single-producer exclusivity, and
263        // `available_space >= size` (loaded with `Ordering::Acquire` from `combined_pointers`)
264        // guarantees that the writable sub-ranges are disjoint from any unread bytes currently
265        // accessed by the consumer.
266        let region1 = unsafe {
267            slice::from_raw_parts_mut(
268                self.storage.add(write_offset as usize),
269                bytes_before_break as usize,
270            )
271        };
272
273        let region2 = if bytes_before_break < size {
274            let region2_len = size - bytes_before_break;
275            // SAFETY: See above; the wrapped-around prefix `[0..region2_len)` is within
276            // `available_space` and disjoint from `region1` and any unread consumer bytes.
277            unsafe { slice::from_raw_parts_mut(self.storage, region2_len as usize) }
278        } else {
279            &mut []
280        };
281
282        Ok(Reservation {
283            combined_pointers: &self.combined_pointers,
284            storage_len,
285            initial_ring_pointers: initial_state,
286            region1,
287            region2,
288            write_offset: 0,
289            committed: false,
290        })
291    }
292
293    /// Copies `len` bytes out of the buffer using the provided `copy_fn`.
294    ///
295    /// The copy function has the signature `copy_fn(offset: u32, src: &[u8]) -> Result<(), Status>`
296    /// and may be invoked multiple times.
297    ///
298    /// Returns the number of bytes read on success. If `copy_fn` returns an error, that error is
299    /// propagated.
300    ///
301    /// Importantly, even if an error is returned, `copy_fn` might have already processed a partial
302    /// amount of data (between 0 and `len` bytes). However, these partially processed bytes are
303    /// considered *not read*. Consequently, the internal read pointer of the ring buffer will *not*
304    /// be advanced for these unread bytes, meaning that these same bytes will remain available for
305    /// reading in subsequent calls to `read`.
306    pub fn read<F>(&self, mut copy_fn: F, len: u32) -> Result<u32, Status>
307    where
308        F: FnMut(u32, &[u8]) -> Result<(), Status>,
309    {
310        if len > Self::MAX_STORAGE_SIZE {
311            return Err(Status::INVALID_ARGS);
312        }
313        if len == 0 {
314            return Ok(0);
315        }
316
317        if self.storage.is_null() {
318            return Err(Status::BAD_STATE);
319        }
320
321        let initial_state = self.load_pointers();
322        let available_data = initial_state.available_data();
323        if available_data == 0 {
324            return Ok(0);
325        }
326
327        let amount_to_copy = cmp::min(available_data, len) as usize;
328        let read_offset = self.pointer_to_offset(initial_state.read) as usize;
329        let ring_break_distance = self.size - read_offset;
330        let bytes_before_break = cmp::min(amount_to_copy, ring_break_distance);
331
332        // SAFETY: The creator of `Buffer` guarantees that `storage` points to a valid memory
333        // region of `self.size` bytes, and `amount_to_copy <= available_data` (loaded with
334        // `Ordering::Acquire` from `combined_pointers`) guarantees that the readable sub-ranges
335        // have been initialized and committed by the producer and are disjoint from any
336        // concurrent producer reservation until `advance_read_pointer` releases them.
337        let slice1 =
338            unsafe { slice::from_raw_parts(self.storage.add(read_offset), bytes_before_break) };
339        copy_fn(0, slice1)?;
340
341        if bytes_before_break < amount_to_copy {
342            let bytes_after_break = amount_to_copy - bytes_before_break;
343            // SAFETY: See above; the wrapped-around prefix `[0..bytes_after_break)` is within
344            // `available_data` and disjoint from any concurrent producer reservation.
345            let slice2 = unsafe { slice::from_raw_parts(self.storage, bytes_after_break) };
346            copy_fn(bytes_before_break as u32, slice2)?;
347        }
348
349        self.advance_read_pointer(initial_state, amount_to_copy as u32)?;
350        Ok(amount_to_copy as u32)
351    }
352
353    /// Empties the contents of the buffer.
354    ///
355    /// This is logically a read operation, so a `read` and `drain` cannot be called concurrently.
356    /// Additionally, the behavior of this method is non-deterministic if a write is in-progress.
357    pub fn drain(&self) -> Result<(), Status> {
358        let initial_state = self.load_pointers();
359        let available_data = initial_state.available_data();
360        if available_data == 0 {
361            return Ok(());
362        }
363        self.advance_read_pointer(initial_state, available_data)
364    }
365
366    // Helper function that converts read and write pointers into ring buffer offsets.
367    //
368    // This is logically performing pointer % storage.len(), but because storage.len() is guaranteed
369    // to be a power of two it is equivalent to this logical AND.
370    fn pointer_to_offset(&self, pointer: u32) -> u32 {
371        let storage_len = self.size as u32;
372        pointer & (storage_len - 1)
373    }
374
375    /// Returns the remaining available space in the buffer.
376    fn available_space(&self, pointers: RingPointers) -> u32 {
377        let storage_len = self.size as u32;
378        storage_len.wrapping_sub(pointers.available_data())
379    }
380
381    /// Loads the current values of the read and write pointers.
382    fn load_pointers(&self) -> RingPointers {
383        let combined = self.combined_pointers.load(Ordering::Acquire);
384        RingPointers::from_combined(combined)
385    }
386
387    // Adds the given delta to the read half of the combined_pointers.
388    //
389    // Because we store the pointers in a single combined atomic variable, we must update the entire
390    // combined pointer. We perform this update using a compare and exchange to ensure that
391    // concurrent operations to the write half of the combined pointers are preserved. We also check
392    // to ensure reads do not encounter concurrent reads.
393    //
394    // This is a store-release operation that synchronizes with the load-acquire in load_pointers.
395    // By using release semantics, we ensure that if the updated value is seen in load_pointers, all
396    // memory operations that occurred prior to this update are observable.
397    fn advance_read_pointer(&self, initial: RingPointers, delta: u32) -> Result<(), Status> {
398        if delta > initial.available_data() {
399            return Err(Status::INVALID_ARGS);
400        }
401
402        let target_read = initial.read.wrapping_add(delta);
403        let mut starting_pointers = initial.as_combined();
404        let mut target_pointers = RingPointers::new(target_read, initial.write).as_combined();
405
406        loop {
407            match self.combined_pointers.compare_exchange_weak(
408                starting_pointers,
409                target_pointers,
410                Ordering::Release,
411                Ordering::Relaxed,
412            ) {
413                Ok(_) => break,
414                Err(observed_combined) => {
415                    starting_pointers = observed_combined;
416                    let observed = RingPointers::from_combined(observed_combined);
417                    debug_assert_eq!(
418                        observed.read, initial.read,
419                        "potential concurrent read detected; expected read pointer {}, got {}",
420                        initial.read, observed.read
421                    );
422                    target_pointers = RingPointers::new(target_read, observed.write).as_combined();
423                }
424            }
425        }
426        Ok(())
427    }
428}
429
430// Adds the given delta to the write half of the combined_pointers.
431//
432// Because we store the pointers in a single combined atomic variable, we must update the entire
433// combined pointer. We perform this update using a compare and exchange to ensure that concurrent
434// operations to the read half of the combined pointers are preserved. We also check to ensure
435// writes do not encounter concurrent writes.
436//
437// This is a store-release operation that synchronizes with the load-acquire in load_pointers. By
438// using release semantics, we ensure that if the updated value is seen in load_pointers, all memory
439// operations that occurred prior to this update are observable.
440fn advance_write_pointer(
441    combined_pointers: &AtomicU64,
442    storage_len: u32,
443    initial: RingPointers,
444    delta: u32,
445) -> Result<(), Status> {
446    let available_data = initial.available_data();
447    if delta > storage_len.checked_sub(available_data).ok_or(Status::INVALID_ARGS)? {
448        return Err(Status::INVALID_ARGS);
449    }
450
451    let target_write = initial.write.wrapping_add(delta);
452    let mut starting_pointers = initial.as_combined();
453    let mut target_pointers = RingPointers::new(initial.read, target_write).as_combined();
454
455    loop {
456        match combined_pointers.compare_exchange_weak(
457            starting_pointers,
458            target_pointers,
459            Ordering::Release,
460            Ordering::Relaxed,
461        ) {
462            Ok(_) => break,
463            Err(observed_combined) => {
464                starting_pointers = observed_combined;
465                let observed = RingPointers::from_combined(observed_combined);
466                debug_assert_eq!(
467                    observed.write, initial.write,
468                    "potential concurrent write detected; expected write pointer {}, got {}",
469                    initial.write, observed.write
470                );
471                target_pointers = RingPointers::new(observed.read, target_write).as_combined();
472            }
473        }
474    }
475    Ok(())
476}
477
478impl Buffer<DefaultAllocator> {
479    /// Constructs a new `Buffer` with a dynamically allocated backing storage of the given size,
480    /// using the default allocator.
481    pub fn try_new(size: u32) -> Result<Self, Status> {
482        Self::try_new_in(size, DefaultAllocator)
483    }
484}
485
486impl Buffer<NoOpAllocator> {
487    /// Constructs a `Buffer` from raw pointers using a no-op allocator.
488    ///
489    /// The returned buffer does not own the memory and will not deallocate it when dropped.
490    ///
491    /// # Safety
492    ///
493    /// - `storage` must be non-null and point to a valid, initialized slice of bytes whose length
494    ///   is a power of two and does not exceed `MAX_STORAGE_SIZE`.
495    pub unsafe fn from_raw_parts(storage: *mut u8, size: usize) -> Self {
496        assert!(!storage.is_null());
497        assert!(size <= Self::MAX_STORAGE_SIZE as usize);
498        assert!(size.is_power_of_two());
499        Self {
500            combined_pointers: AtomicU64::new(0),
501            storage,
502            size,
503            _phantom: core::marker::PhantomData,
504        }
505    }
506
507    /// Constructs an empty `Buffer` with null storage and zero size (for testing).
508    pub const fn empty() -> Self {
509        Self {
510            combined_pointers: AtomicU64::new(0),
511            storage: core::ptr::null_mut(),
512            size: 0,
513            _phantom: core::marker::PhantomData,
514        }
515    }
516
517    /// Resets the buffer to empty and returns the raw storage pointer and size.
518    #[inline]
519    pub fn take_raw_parts(&mut self) -> (*mut u8, usize) {
520        let storage = self.storage;
521        let size = self.size;
522        self.storage = core::ptr::null_mut();
523        self.size = 0;
524        (storage, size)
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531
532    #[test]
533    fn test_ring_pointers_from_combined() {
534        struct TestCase {
535            combined_pointers: u64,
536            expected: RingPointers,
537        }
538        let test_cases = [
539            TestCase {
540                combined_pointers: 0x01320087_01005382,
541                expected: RingPointers::new(0x1320087, 0x1005382),
542            },
543            TestCase { combined_pointers: 0, expected: RingPointers::new(0, 0) },
544            TestCase {
545                combined_pointers: 0xFFFFFFFF_00004587,
546                expected: RingPointers::new(0xFFFFFFFF, 0x4587),
547            },
548            TestCase {
549                combined_pointers: 0x00004587_FFFFFFFF,
550                expected: RingPointers::new(0x4587, 0xFFFFFFFF),
551            },
552        ];
553
554        for tc in &test_cases {
555            let actual = RingPointers::from_combined(tc.combined_pointers);
556            assert_eq!(tc.expected, actual);
557        }
558    }
559
560    #[test]
561    fn test_ring_pointers_as_combined() {
562        struct TestCase {
563            pointers: RingPointers,
564            combined: u64,
565        }
566        let test_cases = [
567            TestCase {
568                pointers: RingPointers::new(0x1320087, 0x1005382),
569                combined: 0x01320087_01005382,
570            },
571            TestCase { pointers: RingPointers::new(0, 0), combined: 0 },
572            TestCase {
573                pointers: RingPointers::new(0xFFFFFFFF, 0x123),
574                combined: 0xFFFFFFFF_00000123,
575            },
576            TestCase {
577                pointers: RingPointers::new(0x123, 0xFFFFFFFF),
578                combined: 0x00000123_FFFFFFFF,
579            },
580        ];
581
582        for tc in &test_cases {
583            let actual = tc.pointers.as_combined();
584            assert_eq!(tc.combined, actual);
585        }
586    }
587
588    #[test]
589    fn test_available_space() {
590        struct TestCase {
591            pointers: RingPointers,
592            buffer_size: u32,
593            expected: u32,
594        }
595        let test_cases = [
596            TestCase { pointers: RingPointers::new(0, 0), buffer_size: 16, expected: 16 },
597            TestCase { pointers: RingPointers::new(3, 3), buffer_size: 16, expected: 16 },
598            TestCase { pointers: RingPointers::new(3, 7), buffer_size: 16, expected: 12 },
599            TestCase { pointers: RingPointers::new(0xFFFFFFFC, 3), buffer_size: 16, expected: 9 },
600            TestCase { pointers: RingPointers::new(0, 16), buffer_size: 16, expected: 0 },
601        ];
602
603        for tc in &test_cases {
604            let buffer = Buffer::try_new(tc.buffer_size).unwrap();
605            let actual = buffer.available_space(tc.pointers);
606            assert_eq!(tc.expected, actual);
607        }
608    }
609
610    #[test]
611    fn test_ring_pointers_available_data() {
612        struct TestCase {
613            pointers: RingPointers,
614            expected: u32,
615        }
616        let test_cases = [
617            TestCase { pointers: RingPointers::new(0, 0), expected: 0 },
618            TestCase { pointers: RingPointers::new(3, 3), expected: 0 },
619            TestCase { pointers: RingPointers::new(3, 7), expected: 4 },
620            TestCase { pointers: RingPointers::new(0, 16), expected: 16 },
621            TestCase { pointers: RingPointers::new(0xFFFFFFFC, 3), expected: 7 },
622        ];
623
624        for tc in &test_cases {
625            let actual = tc.pointers.available_data();
626            assert_eq!(tc.expected, actual);
627        }
628    }
629
630    #[test]
631    fn test_advance_read_pointer() {
632        struct TestCase {
633            initial_pointers: RingPointers,
634            buffer_size: u32,
635            delta: u32,
636            expected: u64,
637        }
638        let test_cases = [
639            TestCase {
640                initial_pointers: RingPointers::new(1, 6),
641                buffer_size: 16,
642                delta: 4,
643                expected: 0x5_00000006,
644            },
645            TestCase {
646                initial_pointers: RingPointers::new(0, 16),
647                buffer_size: 16,
648                delta: 16,
649                expected: 0x10_00000010,
650            },
651            TestCase {
652                initial_pointers: RingPointers::new(0xFFFFFFFC, 12),
653                buffer_size: 16,
654                delta: 5,
655                expected: 0x1_0000000C,
656            },
657        ];
658
659        for tc in &test_cases {
660            let buffer = Buffer::try_new(tc.buffer_size).unwrap();
661
662            let initial = tc.initial_pointers.as_combined();
663            buffer.combined_pointers.store(initial, Ordering::Release);
664
665            buffer.advance_read_pointer(tc.initial_pointers, tc.delta).unwrap();
666            let actual = buffer.combined_pointers.load(Ordering::Acquire);
667            assert_eq!(tc.expected, actual);
668        }
669    }
670
671    #[test]
672    fn test_advance_write_pointer() {
673        struct TestCase {
674            initial_pointers: RingPointers,
675            buffer_size: u32,
676            delta: u32,
677            expected: u64,
678        }
679        let test_cases = [
680            TestCase {
681                initial_pointers: RingPointers::new(7, 9),
682                buffer_size: 16,
683                delta: 4,
684                expected: 0x7_0000000D,
685            },
686            TestCase {
687                initial_pointers: RingPointers::new(0, 0),
688                buffer_size: 16,
689                delta: 16,
690                expected: 0x10,
691            },
692            TestCase {
693                initial_pointers: RingPointers::new(0xFFFFFFF1, 0xFFFFFFFC),
694                buffer_size: 16,
695                delta: 5,
696                expected: 0xFFFFFFF100000001,
697            },
698        ];
699
700        for tc in &test_cases {
701            let buffer = Buffer::try_new(tc.buffer_size).unwrap();
702
703            let initial = tc.initial_pointers.as_combined();
704            buffer.combined_pointers.store(initial, Ordering::Release);
705
706            advance_write_pointer(
707                &buffer.combined_pointers,
708                buffer.size as u32,
709                tc.initial_pointers,
710                tc.delta,
711            )
712            .unwrap();
713            let actual = buffer.combined_pointers.load(Ordering::Acquire);
714            assert_eq!(tc.expected, actual);
715        }
716    }
717
718    #[test]
719    fn test_try_new() {
720        // Happy case
721        {
722            assert!(Buffer::try_new(256).is_ok());
723        }
724
725        // Calling try_new with too big of a size should fail
726        {
727            assert_eq!(Buffer::try_new(u32::MAX).err().unwrap(), Status::INVALID_ARGS);
728        }
729
730        // Calling try_new with a size that is not a power of two should fail
731        {
732            assert_eq!(Buffer::try_new(100).err().unwrap(), Status::INVALID_ARGS);
733        }
734
735        // try_new should propagate allocation failures
736        {
737            assert_eq!(Buffer::try_new_in(256, NoOpAllocator).err().unwrap(), Status::NO_MEMORY);
738        }
739    }
740
741    #[test]
742    fn test_read_write_single_threaded() {
743        const STORAGE_SIZE: usize = 256;
744        let mut src = [0u8; STORAGE_SIZE];
745        for i in 0..STORAGE_SIZE {
746            src[i] = (i * 17 + 5) as u8;
747        }
748
749        struct TestCase {
750            write_size: u32,
751            read_size: u32,
752            expected_read_size: u32,
753            expected_reserve_status: Result<(), Status>,
754            expected_read_status: Result<(), Status>,
755            initial_pointers: RingPointers,
756            use_copy_out_err_fn: bool,
757        }
758
759        let test_cases = [
760            TestCase {
761                write_size: (STORAGE_SIZE / 2) as u32,
762                read_size: (STORAGE_SIZE / 2) as u32,
763                expected_read_size: (STORAGE_SIZE / 2) as u32,
764                expected_reserve_status: Ok(()),
765                expected_read_status: Ok(()),
766                initial_pointers: RingPointers::new(0, 0),
767                use_copy_out_err_fn: false,
768            },
769            TestCase {
770                write_size: (STORAGE_SIZE / 2) as u32,
771                read_size: (STORAGE_SIZE / 4) as u32,
772                expected_read_size: (STORAGE_SIZE / 4) as u32,
773                expected_reserve_status: Ok(()),
774                expected_read_status: Ok(()),
775                initial_pointers: RingPointers::new(0, 0),
776                use_copy_out_err_fn: false,
777            },
778            TestCase {
779                write_size: STORAGE_SIZE as u32,
780                read_size: STORAGE_SIZE as u32,
781                expected_read_size: STORAGE_SIZE as u32,
782                expected_reserve_status: Ok(()),
783                expected_read_status: Ok(()),
784                initial_pointers: RingPointers::new(0, 0),
785                use_copy_out_err_fn: false,
786            },
787            TestCase {
788                write_size: (STORAGE_SIZE / 4) as u32,
789                read_size: (STORAGE_SIZE / 2) as u32,
790                expected_read_size: (STORAGE_SIZE / 4) as u32,
791                expected_reserve_status: Ok(()),
792                expected_read_status: Ok(()),
793                initial_pointers: RingPointers::new(0, 0),
794                use_copy_out_err_fn: false,
795            },
796            TestCase {
797                write_size: STORAGE_SIZE as u32,
798                read_size: STORAGE_SIZE as u32,
799                expected_read_size: STORAGE_SIZE as u32,
800                expected_reserve_status: Ok(()),
801                expected_read_status: Ok(()),
802                initial_pointers: RingPointers::new(
803                    (STORAGE_SIZE / 2) as u32,
804                    (STORAGE_SIZE / 2) as u32,
805                ),
806                use_copy_out_err_fn: false,
807            },
808            TestCase {
809                write_size: STORAGE_SIZE as u32,
810                read_size: STORAGE_SIZE as u32,
811                expected_read_size: STORAGE_SIZE as u32,
812                expected_reserve_status: Ok(()),
813                expected_read_status: Ok(()),
814                initial_pointers: RingPointers::new(0xFFFFFFFA, 0xFFFFFFFA),
815                use_copy_out_err_fn: false,
816            },
817            TestCase {
818                write_size: 64,
819                read_size: 0,
820                expected_read_size: 0,
821                expected_reserve_status: Err(Status::NO_SPACE),
822                expected_read_status: Ok(()),
823                initial_pointers: RingPointers::new(0, (STORAGE_SIZE - 48) as u32),
824                use_copy_out_err_fn: false,
825            },
826            TestCase {
827                write_size: STORAGE_SIZE as u32,
828                read_size: (STORAGE_SIZE / 2) as u32,
829                expected_read_size: 0,
830                expected_reserve_status: Ok(()),
831                expected_read_status: Err(Status::BAD_STATE),
832                initial_pointers: RingPointers::new(0, 0),
833                use_copy_out_err_fn: true,
834            },
835        ];
836
837        for tc in &test_cases {
838            let mut dst = [0u8; STORAGE_SIZE];
839
840            let mut spsc = Buffer::try_new(STORAGE_SIZE as u32).unwrap();
841
842            let starting_pointers = tc.initial_pointers.as_combined();
843            spsc.combined_pointers.store(starting_pointers, Ordering::Release);
844
845            let reservation = spsc.reserve(tc.write_size);
846            if let Err(e) = &tc.expected_reserve_status {
847                match reservation {
848                    Err(actual_err) => assert_eq!(actual_err, *e),
849                    Ok(_) => panic!("expected reserve to fail with {:?}, but it succeeded", e),
850                }
851                continue;
852            }
853            let mut reservation = reservation.unwrap();
854
855            reservation.write(&src[..tc.write_size as usize]).unwrap();
856            reservation.commit().unwrap();
857
858            let copy_out_fn = |offset: u32, src_slice: &[u8]| -> Result<(), Status> {
859                let offset = offset as usize;
860                assert!(offset + src_slice.len() <= dst.len());
861                dst[offset..offset + src_slice.len()].copy_from_slice(src_slice);
862                Ok(())
863            };
864
865            let copy_out_err_fn =
866                |_offset: u32, _src_slice: &[u8]| -> Result<(), Status> { Err(Status::BAD_STATE) };
867
868            let read_result = if tc.use_copy_out_err_fn {
869                spsc.read(copy_out_err_fn, tc.read_size)
870            } else {
871                spsc.read(copy_out_fn, tc.read_size)
872            };
873
874            if let Err(e) = &tc.expected_read_status {
875                assert_eq!(read_result.unwrap_err(), *e);
876                assert_eq!(spsc.load_pointers().available_data(), tc.write_size);
877                continue;
878            }
879
880            let read_bytes = read_result.unwrap();
881            assert_eq!(read_bytes, tc.expected_read_size);
882            assert_eq!(
883                &dst[..tc.expected_read_size as usize],
884                &src[..tc.expected_read_size as usize]
885            );
886        }
887    }
888
889    #[test]
890    fn test_drain() {
891        const STORAGE_SIZE: u32 = 256;
892        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
893
894        let mut reservation = spsc.reserve(STORAGE_SIZE / 2).unwrap();
895        let write_data = [b'f'; (STORAGE_SIZE / 2) as usize];
896        reservation.write(&write_data).unwrap();
897        reservation.commit().unwrap();
898
899        assert_eq!(spsc.load_pointers().available_data(), STORAGE_SIZE / 2);
900
901        spsc.drain().unwrap();
902        assert_eq!(spsc.load_pointers().available_data(), 0);
903    }
904
905    #[test]
906    fn test_commit_error() {
907        const STORAGE_SIZE: u32 = 256;
908        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
909
910        let mut reservation = spsc.reserve(STORAGE_SIZE / 2).unwrap();
911        // Write fewer bytes than reserved.
912        let write_data = [b'f'; (STORAGE_SIZE / 2) as usize - 1];
913        reservation.write(&write_data).unwrap();
914        assert_eq!(reservation.commit(), Err(Status::BAD_STATE));
915    }
916
917    #[test]
918    fn test_write_error() {
919        const STORAGE_SIZE: u32 = 256;
920        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
921
922        let mut reservation = spsc.reserve(STORAGE_SIZE / 2).unwrap();
923        // Write more bytes than reserved.
924        let write_data = [b'f'; (STORAGE_SIZE / 2) as usize + 1];
925        assert_eq!(reservation.write(&write_data), Err(Status::BUFFER_TOO_SMALL));
926        reservation.committed = true;
927    }
928
929    #[test]
930    fn test_from_raw_parts() {
931        let mut mock_storage = [0u8; 256];
932
933        // Safety: Pointers are valid.
934        let mut spsc =
935            unsafe { Buffer::from_raw_parts(mock_storage.as_mut_ptr(), mock_storage.len()) };
936
937        // Verify reserve, write, commit works
938        let mut reservation = spsc.reserve(100).unwrap();
939        let write_data = [b'x'; 100];
940        reservation.write(&write_data).unwrap();
941        reservation.commit().unwrap();
942
943        assert_eq!(spsc.load_pointers().available_data(), 100);
944        assert_eq!(spsc.combined_pointers.load(Ordering::Relaxed) & 0xffffffff, 100); // write pointer is 100
945        assert_eq!(&mock_storage[..100], &write_data[..]);
946    }
947
948    #[test]
949    fn test_reserve_zero() {
950        const STORAGE_SIZE: u32 = 256;
951        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
952        match spsc.reserve(0) {
953            Err(e) => assert_eq!(e, Status::INVALID_ARGS),
954            Ok(_) => panic!("reserve(0) should fail with INVALID_ARGS"),
955        }
956    }
957
958    #[test]
959    fn test_reserve_too_large() {
960        const STORAGE_SIZE: u32 = 256;
961        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
962        match spsc.reserve(u32::MAX) {
963            Err(e) => assert_eq!(e, Status::INVALID_ARGS),
964            Ok(_) => panic!("reserve(u32::MAX) should fail with INVALID_ARGS"),
965        }
966    }
967
968    #[test]
969    fn test_reserve_and_read_null_storage() {
970        let mut spsc = Buffer::<NoOpAllocator>::empty();
971
972        assert!(!spsc.is_valid());
973        assert_eq!(spsc.reserve(16).err(), Some(Status::BAD_STATE));
974        let read_result = spsc.read(|_, _| Ok(()), 16);
975        assert_eq!(read_result.err(), Some(Status::BAD_STATE));
976    }
977
978    #[test]
979    fn test_reserve_at_break() {
980        const STORAGE_SIZE: u32 = 16;
981        // read raw = 3, write raw = 15
982        // write_offset = 15 & 15 = 15 (distance to end is exactly 1 byte)
983        // available data = 15 - 3 = 12 bytes
984        // available space = 16 - 12 = 4 bytes
985        let mut mock_storage = [0u8; STORAGE_SIZE as usize];
986
987        // Safety: Pointers are valid.
988        let mut spsc =
989            unsafe { Buffer::from_raw_parts(mock_storage.as_mut_ptr(), mock_storage.len()) };
990        spsc.combined_pointers.store((3u64 << 32) | 15u64, Ordering::Release);
991
992        let mut reservation = spsc.reserve(4).unwrap();
993        assert_eq!(reservation.region1.len(), 1);
994        assert_eq!(reservation.region2.len(), 3);
995
996        let data = [1, 2, 3, 4];
997        reservation.write(&data).unwrap();
998        reservation.commit().unwrap();
999
1000        assert_eq!(mock_storage[15], 1);
1001        assert_eq!(mock_storage[0], 2);
1002        assert_eq!(mock_storage[1], 3);
1003        assert_eq!(mock_storage[2], 4);
1004    }
1005
1006    #[test]
1007    fn test_cpp_rust_integration() {
1008        #[link(name = "c++")]
1009        unsafe extern "C" {
1010            fn cpp_spsc_allocate(size: u32) -> *mut Buffer<NoOpAllocator>;
1011            fn cpp_spsc_free(spsc: *mut Buffer<NoOpAllocator>);
1012            fn cpp_spsc_write(spsc: *mut Buffer<NoOpAllocator>, data: *const u8, len: u32) -> i32;
1013            fn cpp_spsc_read(spsc: *mut Buffer<NoOpAllocator>, dst: *mut u8, len: u32) -> i32;
1014        }
1015
1016        // 1. Allocate on the C++ side.
1017        let spsc_ptr = unsafe { cpp_spsc_allocate(256) };
1018        assert!(!spsc_ptr.is_null());
1019
1020        // Convert the raw pointer to a Rust reference to interact with it in-place.
1021        let spsc = unsafe { &mut *spsc_ptr };
1022
1023        // 2. C++ writes, Rust reads.
1024        let write_data = b"Hello from C++!";
1025        let cpp_write_status =
1026            unsafe { cpp_spsc_write(spsc_ptr, write_data.as_ptr(), write_data.len() as u32) };
1027        assert_eq!(cpp_write_status, 0); // ZX_OK
1028
1029        // Rust reads and verifies.
1030        let mut read_buf = [0u8; 100];
1031        let bytes_read = spsc
1032            .read(
1033                |_, src| {
1034                    read_buf[..src.len()].copy_from_slice(src);
1035                    Ok(())
1036                },
1037                write_data.len() as u32,
1038            )
1039            .unwrap();
1040        assert_eq!(bytes_read, write_data.len() as u32);
1041        assert_eq!(&read_buf[..bytes_read as usize], write_data);
1042
1043        // 3. Rust writes, C++ reads.
1044        let rust_write_data = b"Hello from Rust!";
1045        let mut reservation = spsc.reserve(rust_write_data.len() as u32).unwrap();
1046        reservation.write(rust_write_data).unwrap();
1047        reservation.commit().unwrap();
1048
1049        // C++ reads and verifies.
1050        let mut cpp_read_buf = [0u8; 100];
1051        let cpp_read_bytes = unsafe {
1052            cpp_spsc_read(spsc_ptr, cpp_read_buf.as_mut_ptr(), rust_write_data.len() as u32)
1053        };
1054        assert_eq!(cpp_read_bytes, rust_write_data.len() as i32);
1055        assert_eq!(&cpp_read_buf[..cpp_read_bytes as usize], rust_write_data);
1056
1057        // 4. Free on the C++ side.
1058        unsafe {
1059            cpp_spsc_free(spsc_ptr);
1060        }
1061    }
1062}