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    pub fn size(&self) -> u32 {
213        self.size as u32
214    }
215
216    /// Reserves a block of the given size in the buffer.
217    ///
218    /// Any data written into this block will not be visible to readers until `commit` is called on
219    /// the returned `Reservation`.
220    pub fn reserve(&mut self, size: u32) -> Result<Reservation<'_>, Status> {
221        if size == 0 || size > Self::MAX_STORAGE_SIZE {
222            return Err(Status::INVALID_ARGS);
223        }
224
225        if self.storage.is_null() {
226            return Err(Status::BAD_STATE);
227        }
228
229        let storage_len = self.size as u32;
230        if size > storage_len {
231            return Err(Status::NO_SPACE);
232        }
233
234        let initial_state = self.load_pointers();
235        let available_space = self.available_space(initial_state);
236        if available_space < size {
237            return Err(Status::NO_SPACE);
238        }
239
240        let write_offset = self.pointer_to_offset(initial_state.write);
241        let ring_break_distance = storage_len - write_offset;
242        let bytes_before_break = cmp::min(size, ring_break_distance);
243
244        // SAFETY: The creator of Buffer must ensure that `storage` points to a valid memory region
245        // of `size` bytes.
246        let storage_slice = unsafe { slice::from_raw_parts_mut(self.storage, self.size) };
247        let (left, right) = storage_slice.split_at_mut(write_offset as usize);
248        let region1 = &mut right[..bytes_before_break as usize];
249
250        let region2 = if bytes_before_break < size {
251            let region2_len = size - bytes_before_break;
252            &mut left[..region2_len as usize]
253        } else {
254            &mut []
255        };
256
257        Ok(Reservation {
258            combined_pointers: &self.combined_pointers,
259            storage_len,
260            initial_ring_pointers: initial_state,
261            region1,
262            region2,
263            write_offset: 0,
264            committed: false,
265        })
266    }
267
268    /// Copies `len` bytes out of the buffer using the provided `copy_fn`.
269    ///
270    /// The copy function has the signature `copy_fn(offset: u32, src: &[u8]) -> Result<(), Status>`
271    /// and may be invoked multiple times.
272    ///
273    /// Returns the number of bytes read on success. If `copy_fn` returns an error, that error is
274    /// propagated.
275    ///
276    /// Importantly, even if an error is returned, `copy_fn` might have already processed a partial
277    /// amount of data (between 0 and `len` bytes). However, these partially processed bytes are
278    /// considered *not read*. Consequently, the internal read pointer of the ring buffer will *not*
279    /// be advanced for these unread bytes, meaning that these same bytes will remain available for
280    /// reading in subsequent calls to `read`.
281    pub fn read<F>(&self, mut copy_fn: F, len: u32) -> Result<u32, Status>
282    where
283        F: FnMut(u32, &[u8]) -> Result<(), Status>,
284    {
285        if len > Self::MAX_STORAGE_SIZE {
286            return Err(Status::INVALID_ARGS);
287        }
288        if len == 0 {
289            return Ok(0);
290        }
291
292        if self.storage.is_null() {
293            return Err(Status::BAD_STATE);
294        }
295
296        let initial_state = self.load_pointers();
297        let available_data = initial_state.available_data();
298        if available_data == 0 {
299            return Ok(0);
300        }
301
302        let amount_to_copy = cmp::min(available_data, len) as usize;
303        let read_offset = self.pointer_to_offset(initial_state.read) as usize;
304        let ring_break_distance = self.size - read_offset;
305        let bytes_before_break = cmp::min(amount_to_copy, ring_break_distance);
306
307        // SAFETY: The creator of Buffer must ensure that `storage` points to a valid memory region
308        // of `size` bytes.
309        let storage_slice = unsafe { slice::from_raw_parts(self.storage, self.size) };
310        let slice1 = &storage_slice[read_offset..read_offset + bytes_before_break];
311        copy_fn(0, slice1)?;
312
313        if bytes_before_break < amount_to_copy {
314            let bytes_after_break = amount_to_copy - bytes_before_break;
315            let slice2 = &storage_slice[..bytes_after_break];
316            copy_fn(bytes_before_break as u32, slice2)?;
317        }
318
319        self.advance_read_pointer(initial_state, amount_to_copy as u32)?;
320        Ok(amount_to_copy as u32)
321    }
322
323    /// Empties the contents of the buffer.
324    ///
325    /// This is logically a read operation, so a `read` and `drain` cannot be called concurrently.
326    /// Additionally, the behavior of this method is non-deterministic if a write is in-progress.
327    pub fn drain(&self) -> Result<(), Status> {
328        let initial_state = self.load_pointers();
329        let available_data = initial_state.available_data();
330        if available_data == 0 {
331            return Ok(());
332        }
333        self.advance_read_pointer(initial_state, available_data)
334    }
335
336    // Helper function that converts read and write pointers into ring buffer offsets.
337    //
338    // This is logically performing pointer % storage.len(), but because storage.len() is guaranteed
339    // to be a power of two it is equivalent to this logical AND.
340    fn pointer_to_offset(&self, pointer: u32) -> u32 {
341        let storage_len = self.size as u32;
342        pointer & (storage_len - 1)
343    }
344
345    /// Returns the remaining available space in the buffer.
346    fn available_space(&self, pointers: RingPointers) -> u32 {
347        let storage_len = self.size as u32;
348        storage_len.wrapping_sub(pointers.available_data())
349    }
350
351    /// Loads the current values of the read and write pointers.
352    fn load_pointers(&self) -> RingPointers {
353        let combined = self.combined_pointers.load(Ordering::Acquire);
354        RingPointers::from_combined(combined)
355    }
356
357    // Adds the given delta to the read half of the combined_pointers.
358    //
359    // Because we store the pointers in a single combined atomic variable, we must update the entire
360    // combined pointer. We perform this update using a compare and exchange to ensure that
361    // concurrent operations to the write half of the combined pointers are preserved. We also check
362    // to ensure reads do not encounter concurrent reads.
363    //
364    // This is a store-release operation that synchronizes with the load-acquire in load_pointers.
365    // By using release semantics, we ensure that if the updated value is seen in load_pointers, all
366    // memory operations that occurred prior to this update are observable.
367    fn advance_read_pointer(&self, initial: RingPointers, delta: u32) -> Result<(), Status> {
368        if delta > initial.available_data() {
369            return Err(Status::INVALID_ARGS);
370        }
371
372        let target_read = initial.read.wrapping_add(delta);
373        let mut starting_pointers = initial.as_combined();
374        let mut target_pointers = RingPointers::new(target_read, initial.write).as_combined();
375
376        loop {
377            match self.combined_pointers.compare_exchange_weak(
378                starting_pointers,
379                target_pointers,
380                Ordering::Release,
381                Ordering::Relaxed,
382            ) {
383                Ok(_) => break,
384                Err(observed_combined) => {
385                    starting_pointers = observed_combined;
386                    let observed = RingPointers::from_combined(observed_combined);
387                    debug_assert_eq!(
388                        observed.read, initial.read,
389                        "potential concurrent read detected; expected read pointer {}, got {}",
390                        initial.read, observed.read
391                    );
392                    target_pointers = RingPointers::new(target_read, observed.write).as_combined();
393                }
394            }
395        }
396        Ok(())
397    }
398}
399
400// Adds the given delta to the write half of the combined_pointers.
401//
402// Because we store the pointers in a single combined atomic variable, we must update the entire
403// combined pointer. We perform this update using a compare and exchange to ensure that concurrent
404// operations to the read half of the combined pointers are preserved. We also check to ensure
405// writes do not encounter concurrent writes.
406//
407// This is a store-release operation that synchronizes with the load-acquire in load_pointers. By
408// using release semantics, we ensure that if the updated value is seen in load_pointers, all memory
409// operations that occurred prior to this update are observable.
410fn advance_write_pointer(
411    combined_pointers: &AtomicU64,
412    storage_len: u32,
413    initial: RingPointers,
414    delta: u32,
415) -> Result<(), Status> {
416    let available_data = initial.available_data();
417    if delta > storage_len.checked_sub(available_data).ok_or(Status::INVALID_ARGS)? {
418        return Err(Status::INVALID_ARGS);
419    }
420
421    let target_write = initial.write.wrapping_add(delta);
422    let mut starting_pointers = initial.as_combined();
423    let mut target_pointers = RingPointers::new(initial.read, target_write).as_combined();
424
425    loop {
426        match combined_pointers.compare_exchange_weak(
427            starting_pointers,
428            target_pointers,
429            Ordering::Release,
430            Ordering::Relaxed,
431        ) {
432            Ok(_) => break,
433            Err(observed_combined) => {
434                starting_pointers = observed_combined;
435                let observed = RingPointers::from_combined(observed_combined);
436                debug_assert_eq!(
437                    observed.write, initial.write,
438                    "potential concurrent write detected; expected write pointer {}, got {}",
439                    initial.write, observed.write
440                );
441                target_pointers = RingPointers::new(observed.read, target_write).as_combined();
442            }
443        }
444    }
445    Ok(())
446}
447
448impl Buffer<DefaultAllocator> {
449    /// Constructs a new `Buffer` with a dynamically allocated backing storage of the given size,
450    /// using the default allocator.
451    pub fn try_new(size: u32) -> Result<Self, Status> {
452        Self::try_new_in(size, DefaultAllocator)
453    }
454}
455
456impl Buffer<NoOpAllocator> {
457    /// Constructs a `Buffer` from raw pointers using a no-op allocator.
458    ///
459    /// The returned buffer does not own the memory and will not deallocate it when dropped.
460    ///
461    /// # Safety
462    ///
463    /// - `storage` must be non-null and point to a valid, initialized slice of bytes whose length
464    ///   is a power of two and does not exceed `MAX_STORAGE_SIZE`.
465    pub unsafe fn from_raw_parts(storage: *mut u8, size: usize) -> Self {
466        assert!(!storage.is_null());
467        assert!(size <= Self::MAX_STORAGE_SIZE as usize);
468        assert!(size.is_power_of_two());
469        Self {
470            combined_pointers: AtomicU64::new(0),
471            storage,
472            size,
473            _phantom: core::marker::PhantomData,
474        }
475    }
476
477    /// Constructs an empty `Buffer` with null storage and zero size (for testing).
478    pub const fn empty() -> Self {
479        Self {
480            combined_pointers: AtomicU64::new(0),
481            storage: core::ptr::null_mut(),
482            size: 0,
483            _phantom: core::marker::PhantomData,
484        }
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491
492    #[test]
493    fn test_ring_pointers_from_combined() {
494        struct TestCase {
495            combined_pointers: u64,
496            expected: RingPointers,
497        }
498        let test_cases = [
499            TestCase {
500                combined_pointers: 0x01320087_01005382,
501                expected: RingPointers::new(0x1320087, 0x1005382),
502            },
503            TestCase { combined_pointers: 0, expected: RingPointers::new(0, 0) },
504            TestCase {
505                combined_pointers: 0xFFFFFFFF_00004587,
506                expected: RingPointers::new(0xFFFFFFFF, 0x4587),
507            },
508            TestCase {
509                combined_pointers: 0x00004587_FFFFFFFF,
510                expected: RingPointers::new(0x4587, 0xFFFFFFFF),
511            },
512        ];
513
514        for tc in &test_cases {
515            let actual = RingPointers::from_combined(tc.combined_pointers);
516            assert_eq!(tc.expected, actual);
517        }
518    }
519
520    #[test]
521    fn test_ring_pointers_as_combined() {
522        struct TestCase {
523            pointers: RingPointers,
524            combined: u64,
525        }
526        let test_cases = [
527            TestCase {
528                pointers: RingPointers::new(0x1320087, 0x1005382),
529                combined: 0x01320087_01005382,
530            },
531            TestCase { pointers: RingPointers::new(0, 0), combined: 0 },
532            TestCase {
533                pointers: RingPointers::new(0xFFFFFFFF, 0x123),
534                combined: 0xFFFFFFFF_00000123,
535            },
536            TestCase {
537                pointers: RingPointers::new(0x123, 0xFFFFFFFF),
538                combined: 0x00000123_FFFFFFFF,
539            },
540        ];
541
542        for tc in &test_cases {
543            let actual = tc.pointers.as_combined();
544            assert_eq!(tc.combined, actual);
545        }
546    }
547
548    #[test]
549    fn test_available_space() {
550        struct TestCase {
551            pointers: RingPointers,
552            buffer_size: u32,
553            expected: u32,
554        }
555        let test_cases = [
556            TestCase { pointers: RingPointers::new(0, 0), buffer_size: 16, expected: 16 },
557            TestCase { pointers: RingPointers::new(3, 3), buffer_size: 16, expected: 16 },
558            TestCase { pointers: RingPointers::new(3, 7), buffer_size: 16, expected: 12 },
559            TestCase { pointers: RingPointers::new(0xFFFFFFFC, 3), buffer_size: 16, expected: 9 },
560            TestCase { pointers: RingPointers::new(0, 16), buffer_size: 16, expected: 0 },
561        ];
562
563        for tc in &test_cases {
564            let buffer = Buffer::try_new(tc.buffer_size).unwrap();
565            let actual = buffer.available_space(tc.pointers);
566            assert_eq!(tc.expected, actual);
567        }
568    }
569
570    #[test]
571    fn test_ring_pointers_available_data() {
572        struct TestCase {
573            pointers: RingPointers,
574            expected: u32,
575        }
576        let test_cases = [
577            TestCase { pointers: RingPointers::new(0, 0), expected: 0 },
578            TestCase { pointers: RingPointers::new(3, 3), expected: 0 },
579            TestCase { pointers: RingPointers::new(3, 7), expected: 4 },
580            TestCase { pointers: RingPointers::new(0, 16), expected: 16 },
581            TestCase { pointers: RingPointers::new(0xFFFFFFFC, 3), expected: 7 },
582        ];
583
584        for tc in &test_cases {
585            let actual = tc.pointers.available_data();
586            assert_eq!(tc.expected, actual);
587        }
588    }
589
590    #[test]
591    fn test_advance_read_pointer() {
592        struct TestCase {
593            initial_pointers: RingPointers,
594            buffer_size: u32,
595            delta: u32,
596            expected: u64,
597        }
598        let test_cases = [
599            TestCase {
600                initial_pointers: RingPointers::new(1, 6),
601                buffer_size: 16,
602                delta: 4,
603                expected: 0x5_00000006,
604            },
605            TestCase {
606                initial_pointers: RingPointers::new(0, 16),
607                buffer_size: 16,
608                delta: 16,
609                expected: 0x10_00000010,
610            },
611            TestCase {
612                initial_pointers: RingPointers::new(0xFFFFFFFC, 12),
613                buffer_size: 16,
614                delta: 5,
615                expected: 0x1_0000000C,
616            },
617        ];
618
619        for tc in &test_cases {
620            let buffer = Buffer::try_new(tc.buffer_size).unwrap();
621
622            let initial = tc.initial_pointers.as_combined();
623            buffer.combined_pointers.store(initial, Ordering::Release);
624
625            buffer.advance_read_pointer(tc.initial_pointers, tc.delta).unwrap();
626            let actual = buffer.combined_pointers.load(Ordering::Acquire);
627            assert_eq!(tc.expected, actual);
628        }
629    }
630
631    #[test]
632    fn test_advance_write_pointer() {
633        struct TestCase {
634            initial_pointers: RingPointers,
635            buffer_size: u32,
636            delta: u32,
637            expected: u64,
638        }
639        let test_cases = [
640            TestCase {
641                initial_pointers: RingPointers::new(7, 9),
642                buffer_size: 16,
643                delta: 4,
644                expected: 0x7_0000000D,
645            },
646            TestCase {
647                initial_pointers: RingPointers::new(0, 0),
648                buffer_size: 16,
649                delta: 16,
650                expected: 0x10,
651            },
652            TestCase {
653                initial_pointers: RingPointers::new(0xFFFFFFF1, 0xFFFFFFFC),
654                buffer_size: 16,
655                delta: 5,
656                expected: 0xFFFFFFF100000001,
657            },
658        ];
659
660        for tc in &test_cases {
661            let buffer = Buffer::try_new(tc.buffer_size).unwrap();
662
663            let initial = tc.initial_pointers.as_combined();
664            buffer.combined_pointers.store(initial, Ordering::Release);
665
666            advance_write_pointer(
667                &buffer.combined_pointers,
668                buffer.size as u32,
669                tc.initial_pointers,
670                tc.delta,
671            )
672            .unwrap();
673            let actual = buffer.combined_pointers.load(Ordering::Acquire);
674            assert_eq!(tc.expected, actual);
675        }
676    }
677
678    #[test]
679    fn test_try_new() {
680        // Happy case
681        {
682            assert!(Buffer::try_new(256).is_ok());
683        }
684
685        // Calling try_new with too big of a size should fail
686        {
687            assert_eq!(Buffer::try_new(u32::MAX).err().unwrap(), Status::INVALID_ARGS);
688        }
689
690        // Calling try_new with a size that is not a power of two should fail
691        {
692            assert_eq!(Buffer::try_new(100).err().unwrap(), Status::INVALID_ARGS);
693        }
694
695        // try_new should propagate allocation failures
696        {
697            assert_eq!(Buffer::try_new_in(256, NoOpAllocator).err().unwrap(), Status::NO_MEMORY);
698        }
699    }
700
701    #[test]
702    fn test_read_write_single_threaded() {
703        const STORAGE_SIZE: usize = 256;
704        let mut src = [0u8; STORAGE_SIZE];
705        for i in 0..STORAGE_SIZE {
706            src[i] = (i * 17 + 5) as u8;
707        }
708
709        struct TestCase {
710            write_size: u32,
711            read_size: u32,
712            expected_read_size: u32,
713            expected_reserve_status: Result<(), Status>,
714            expected_read_status: Result<(), Status>,
715            initial_pointers: RingPointers,
716            use_copy_out_err_fn: bool,
717        }
718
719        let test_cases = [
720            TestCase {
721                write_size: (STORAGE_SIZE / 2) as u32,
722                read_size: (STORAGE_SIZE / 2) as u32,
723                expected_read_size: (STORAGE_SIZE / 2) as u32,
724                expected_reserve_status: Ok(()),
725                expected_read_status: Ok(()),
726                initial_pointers: RingPointers::new(0, 0),
727                use_copy_out_err_fn: false,
728            },
729            TestCase {
730                write_size: (STORAGE_SIZE / 2) as u32,
731                read_size: (STORAGE_SIZE / 4) as u32,
732                expected_read_size: (STORAGE_SIZE / 4) as u32,
733                expected_reserve_status: Ok(()),
734                expected_read_status: Ok(()),
735                initial_pointers: RingPointers::new(0, 0),
736                use_copy_out_err_fn: false,
737            },
738            TestCase {
739                write_size: STORAGE_SIZE as u32,
740                read_size: STORAGE_SIZE as u32,
741                expected_read_size: STORAGE_SIZE as u32,
742                expected_reserve_status: Ok(()),
743                expected_read_status: Ok(()),
744                initial_pointers: RingPointers::new(0, 0),
745                use_copy_out_err_fn: false,
746            },
747            TestCase {
748                write_size: (STORAGE_SIZE / 4) as u32,
749                read_size: (STORAGE_SIZE / 2) as u32,
750                expected_read_size: (STORAGE_SIZE / 4) as u32,
751                expected_reserve_status: Ok(()),
752                expected_read_status: Ok(()),
753                initial_pointers: RingPointers::new(0, 0),
754                use_copy_out_err_fn: false,
755            },
756            TestCase {
757                write_size: STORAGE_SIZE as u32,
758                read_size: STORAGE_SIZE as u32,
759                expected_read_size: STORAGE_SIZE as u32,
760                expected_reserve_status: Ok(()),
761                expected_read_status: Ok(()),
762                initial_pointers: RingPointers::new(
763                    (STORAGE_SIZE / 2) as u32,
764                    (STORAGE_SIZE / 2) as u32,
765                ),
766                use_copy_out_err_fn: false,
767            },
768            TestCase {
769                write_size: STORAGE_SIZE as u32,
770                read_size: STORAGE_SIZE as u32,
771                expected_read_size: STORAGE_SIZE as u32,
772                expected_reserve_status: Ok(()),
773                expected_read_status: Ok(()),
774                initial_pointers: RingPointers::new(0xFFFFFFFA, 0xFFFFFFFA),
775                use_copy_out_err_fn: false,
776            },
777            TestCase {
778                write_size: 64,
779                read_size: 0,
780                expected_read_size: 0,
781                expected_reserve_status: Err(Status::NO_SPACE),
782                expected_read_status: Ok(()),
783                initial_pointers: RingPointers::new(0, (STORAGE_SIZE - 48) as u32),
784                use_copy_out_err_fn: false,
785            },
786            TestCase {
787                write_size: STORAGE_SIZE as u32,
788                read_size: (STORAGE_SIZE / 2) as u32,
789                expected_read_size: 0,
790                expected_reserve_status: Ok(()),
791                expected_read_status: Err(Status::BAD_STATE),
792                initial_pointers: RingPointers::new(0, 0),
793                use_copy_out_err_fn: true,
794            },
795        ];
796
797        for tc in &test_cases {
798            let mut dst = [0u8; STORAGE_SIZE];
799
800            let mut spsc = Buffer::try_new(STORAGE_SIZE as u32).unwrap();
801
802            let starting_pointers = tc.initial_pointers.as_combined();
803            spsc.combined_pointers.store(starting_pointers, Ordering::Release);
804
805            let reservation = spsc.reserve(tc.write_size);
806            if let Err(e) = &tc.expected_reserve_status {
807                match reservation {
808                    Err(actual_err) => assert_eq!(actual_err, *e),
809                    Ok(_) => panic!("expected reserve to fail with {:?}, but it succeeded", e),
810                }
811                continue;
812            }
813            let mut reservation = reservation.unwrap();
814
815            reservation.write(&src[..tc.write_size as usize]).unwrap();
816            reservation.commit().unwrap();
817
818            let copy_out_fn = |offset: u32, src_slice: &[u8]| -> Result<(), Status> {
819                let offset = offset as usize;
820                assert!(offset + src_slice.len() <= dst.len());
821                dst[offset..offset + src_slice.len()].copy_from_slice(src_slice);
822                Ok(())
823            };
824
825            let copy_out_err_fn =
826                |_offset: u32, _src_slice: &[u8]| -> Result<(), Status> { Err(Status::BAD_STATE) };
827
828            let read_result = if tc.use_copy_out_err_fn {
829                spsc.read(copy_out_err_fn, tc.read_size)
830            } else {
831                spsc.read(copy_out_fn, tc.read_size)
832            };
833
834            if let Err(e) = &tc.expected_read_status {
835                assert_eq!(read_result.unwrap_err(), *e);
836                assert_eq!(spsc.load_pointers().available_data(), tc.write_size);
837                continue;
838            }
839
840            let read_bytes = read_result.unwrap();
841            assert_eq!(read_bytes, tc.expected_read_size);
842            assert_eq!(
843                &dst[..tc.expected_read_size as usize],
844                &src[..tc.expected_read_size as usize]
845            );
846        }
847    }
848
849    #[test]
850    fn test_drain() {
851        const STORAGE_SIZE: u32 = 256;
852        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
853
854        let mut reservation = spsc.reserve(STORAGE_SIZE / 2).unwrap();
855        let write_data = [b'f'; (STORAGE_SIZE / 2) as usize];
856        reservation.write(&write_data).unwrap();
857        reservation.commit().unwrap();
858
859        assert_eq!(spsc.load_pointers().available_data(), STORAGE_SIZE / 2);
860
861        spsc.drain().unwrap();
862        assert_eq!(spsc.load_pointers().available_data(), 0);
863    }
864
865    #[test]
866    fn test_commit_error() {
867        const STORAGE_SIZE: u32 = 256;
868        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
869
870        let mut reservation = spsc.reserve(STORAGE_SIZE / 2).unwrap();
871        // Write fewer bytes than reserved.
872        let write_data = [b'f'; (STORAGE_SIZE / 2) as usize - 1];
873        reservation.write(&write_data).unwrap();
874        assert_eq!(reservation.commit(), Err(Status::BAD_STATE));
875    }
876
877    #[test]
878    fn test_write_error() {
879        const STORAGE_SIZE: u32 = 256;
880        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
881
882        let mut reservation = spsc.reserve(STORAGE_SIZE / 2).unwrap();
883        // Write more bytes than reserved.
884        let write_data = [b'f'; (STORAGE_SIZE / 2) as usize + 1];
885        assert_eq!(reservation.write(&write_data), Err(Status::BUFFER_TOO_SMALL));
886        reservation.committed = true;
887    }
888
889    #[test]
890    fn test_from_raw_parts() {
891        let mut mock_storage = [0u8; 256];
892
893        // Safety: Pointers are valid.
894        let mut spsc =
895            unsafe { Buffer::from_raw_parts(mock_storage.as_mut_ptr(), mock_storage.len()) };
896
897        // Verify reserve, write, commit works
898        let mut reservation = spsc.reserve(100).unwrap();
899        let write_data = [b'x'; 100];
900        reservation.write(&write_data).unwrap();
901        reservation.commit().unwrap();
902
903        assert_eq!(spsc.load_pointers().available_data(), 100);
904        assert_eq!(spsc.combined_pointers.load(Ordering::Relaxed) & 0xffffffff, 100); // write pointer is 100
905        assert_eq!(&mock_storage[..100], &write_data[..]);
906    }
907
908    #[test]
909    fn test_reserve_zero() {
910        const STORAGE_SIZE: u32 = 256;
911        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
912        match spsc.reserve(0) {
913            Err(e) => assert_eq!(e, Status::INVALID_ARGS),
914            Ok(_) => panic!("reserve(0) should fail with INVALID_ARGS"),
915        }
916    }
917
918    #[test]
919    fn test_reserve_too_large() {
920        const STORAGE_SIZE: u32 = 256;
921        let mut spsc = Buffer::try_new(STORAGE_SIZE).unwrap();
922        match spsc.reserve(u32::MAX) {
923            Err(e) => assert_eq!(e, Status::INVALID_ARGS),
924            Ok(_) => panic!("reserve(u32::MAX) should fail with INVALID_ARGS"),
925        }
926    }
927
928    #[test]
929    fn test_reserve_and_read_null_storage() {
930        let mut spsc = Buffer::<NoOpAllocator>::empty();
931
932        assert!(!spsc.is_valid());
933        assert_eq!(spsc.reserve(16).err(), Some(Status::BAD_STATE));
934        let read_result = spsc.read(|_, _| Ok(()), 16);
935        assert_eq!(read_result.err(), Some(Status::BAD_STATE));
936    }
937
938    #[test]
939    fn test_reserve_at_break() {
940        const STORAGE_SIZE: u32 = 16;
941        // read raw = 3, write raw = 15
942        // write_offset = 15 & 15 = 15 (distance to end is exactly 1 byte)
943        // available data = 15 - 3 = 12 bytes
944        // available space = 16 - 12 = 4 bytes
945        let mut mock_storage = [0u8; STORAGE_SIZE as usize];
946
947        // Safety: Pointers are valid.
948        let mut spsc =
949            unsafe { Buffer::from_raw_parts(mock_storage.as_mut_ptr(), mock_storage.len()) };
950        spsc.combined_pointers.store((3u64 << 32) | 15u64, Ordering::Release);
951
952        let mut reservation = spsc.reserve(4).unwrap();
953        assert_eq!(reservation.region1.len(), 1);
954        assert_eq!(reservation.region2.len(), 3);
955
956        let data = [1, 2, 3, 4];
957        reservation.write(&data).unwrap();
958        reservation.commit().unwrap();
959
960        assert_eq!(mock_storage[15], 1);
961        assert_eq!(mock_storage[0], 2);
962        assert_eq!(mock_storage[1], 3);
963        assert_eq!(mock_storage[2], 4);
964    }
965
966    #[test]
967    fn test_cpp_rust_integration() {
968        #[link(name = "c++")]
969        unsafe extern "C" {
970            fn cpp_spsc_allocate(size: u32) -> *mut Buffer<NoOpAllocator>;
971            fn cpp_spsc_free(spsc: *mut Buffer<NoOpAllocator>);
972            fn cpp_spsc_write(spsc: *mut Buffer<NoOpAllocator>, data: *const u8, len: u32) -> i32;
973            fn cpp_spsc_read(spsc: *mut Buffer<NoOpAllocator>, dst: *mut u8, len: u32) -> i32;
974        }
975
976        // 1. Allocate on the C++ side.
977        let spsc_ptr = unsafe { cpp_spsc_allocate(256) };
978        assert!(!spsc_ptr.is_null());
979
980        // Convert the raw pointer to a Rust reference to interact with it in-place.
981        let spsc = unsafe { &mut *spsc_ptr };
982
983        // 2. C++ writes, Rust reads.
984        let write_data = b"Hello from C++!";
985        let cpp_write_status =
986            unsafe { cpp_spsc_write(spsc_ptr, write_data.as_ptr(), write_data.len() as u32) };
987        assert_eq!(cpp_write_status, 0); // ZX_OK
988
989        // Rust reads and verifies.
990        let mut read_buf = [0u8; 100];
991        let bytes_read = spsc
992            .read(
993                |_, src| {
994                    read_buf[..src.len()].copy_from_slice(src);
995                    Ok(())
996                },
997                write_data.len() as u32,
998            )
999            .unwrap();
1000        assert_eq!(bytes_read, write_data.len() as u32);
1001        assert_eq!(&read_buf[..bytes_read as usize], write_data);
1002
1003        // 3. Rust writes, C++ reads.
1004        let rust_write_data = b"Hello from Rust!";
1005        let mut reservation = spsc.reserve(rust_write_data.len() as u32).unwrap();
1006        reservation.write(rust_write_data).unwrap();
1007        reservation.commit().unwrap();
1008
1009        // C++ reads and verifies.
1010        let mut cpp_read_buf = [0u8; 100];
1011        let cpp_read_bytes = unsafe {
1012            cpp_spsc_read(spsc_ptr, cpp_read_buf.as_mut_ptr(), rust_write_data.len() as u32)
1013        };
1014        assert_eq!(cpp_read_bytes, rust_write_data.len() as i32);
1015        assert_eq!(&cpp_read_buf[..cpp_read_bytes as usize], rust_write_data);
1016
1017        // 4. Free on the C++ side.
1018        unsafe {
1019            cpp_spsc_free(spsc_ptr);
1020        }
1021    }
1022}