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