Skip to main content

cbuf_rs_tests/
kernel.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 cbuf::Cbuf;
10use core::ffi::{c_char, c_void};
11use core::sync::atomic::{AtomicU32, Ordering};
12use pin_init::stack_pin_init;
13use zx_status::Status;
14use zx_types::ZX_TIME_INFINITE;
15
16const ZX_ERR_INTERNAL_INTR_KILLED: i32 = -502;
17
18#[unsafe(no_mangle)]
19pub extern "C" fn test_cbuf_constructor() -> bool {
20    stack_pin_init!(let cbuf = Cbuf::init());
21    if !cbuf.full() {
22        return false;
23    }
24
25    let mut buf = [0u8; 4];
26    // SAFETY: `buf` is valid for cbuf lifetime.
27    unsafe {
28        if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
29            return false;
30        }
31    }
32    if cbuf.full() {
33        return false;
34    }
35
36    true
37}
38
39#[unsafe(no_mangle)]
40pub extern "C" fn test_cbuf_read_write() -> bool {
41    stack_pin_init!(let cbuf = Cbuf::init());
42
43    let mut buf = [0u8; 4];
44    // SAFETY: `buf` is valid for cbuf lifetime.
45    unsafe {
46        if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
47            return false;
48        }
49    }
50
51    if cbuf.full() {
52        return false;
53    }
54
55    // Nothing to read, don't wait.
56    if cbuf.read_char(false) != Err(Status::SHOULD_WAIT) {
57        return false;
58    }
59
60    // Write some characters.
61    let data = b"ABC";
62    for &c in data {
63        if cbuf.write_char(c) != 1 {
64            return false;
65        }
66    }
67    if !cbuf.full() {
68        return false;
69    }
70
71    // Writing when full should return 0.
72    if cbuf.write_char(b'D') != 0 {
73        return false;
74    }
75
76    // Read them back.
77    for (i, &expected) in data.iter().enumerate() {
78        match cbuf.read_char_with_context(true) {
79            Ok(res) => {
80                if res.transitioned_from_full != (i == 0) {
81                    return false;
82                }
83                if res.c != expected {
84                    return false;
85                }
86            }
87            Err(_) => return false,
88        }
89    }
90    if cbuf.full() {
91        return false;
92    }
93
94    true
95}
96
97extern "C" fn reader_thread_entry(arg: *mut c_void) -> i32 {
98    // SAFETY: arg is a valid pointer to a Cbuf pinned on the parent thread's stack.
99    let cbuf = unsafe { &*(arg as *const Cbuf) };
100    loop {
101        match cbuf.read_char(true) {
102            Ok(_) => {}
103            Err(status) => return status.into_raw(),
104        }
105    }
106}
107
108#[unsafe(no_mangle)]
109pub extern "C" fn test_cbuf_read_write_race() -> bool {
110    stack_pin_init!(let cbuf = Cbuf::init());
111
112    let mut buf = [0u8; 4];
113    // SAFETY: `buf` is valid for cbuf lifetime.
114    unsafe {
115        if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
116            return false;
117        }
118    }
119
120    let thread_name = b"cbuf_rust_race\0".as_ptr() as *const c_char;
121    let cbuf_ptr = &*cbuf as *const Cbuf as *mut c_void;
122
123    // SAFETY: we pass reader_thread_entry and valid pointers. The thread is joined
124    // before `cbuf` (and `buf`) goes out of scope.
125    unsafe {
126        let thread = match kernel::thread::spawn(thread_name, reader_thread_entry, cbuf_ptr) {
127            Ok(t) => t,
128            Err(_) => return false,
129        };
130
131        for _ in 0..1000 {
132            while cbuf.write_char(b'A') == 0 {
133                kernel::thread::r#yield();
134            }
135        }
136
137        thread.kill();
138
139        let ret = match thread.join(ZX_TIME_INFINITE) {
140            Ok(r) => r,
141            Err(_) => return false,
142        };
143        if ret != ZX_ERR_INTERNAL_INTR_KILLED {
144            return false;
145        }
146    }
147
148    true
149}
150
151#[unsafe(no_mangle)]
152pub extern "C" fn test_cbuf_init_limits() -> bool {
153    stack_pin_init!(let cbuf = Cbuf::init());
154    let mut buf = [0u8; 4];
155
156    // Size 0 should fail.
157    unsafe {
158        if cbuf.initialize(0, buf.as_mut_ptr()) != Err(Status::INVALID_ARGS) {
159            return false;
160        }
161    }
162
163    // Non-power of two should fail.
164    unsafe {
165        if cbuf.initialize(3, buf.as_mut_ptr()) != Err(Status::INVALID_ARGS) {
166            return false;
167        }
168        if cbuf.initialize(5, buf.as_mut_ptr()) != Err(Status::INVALID_ARGS) {
169            return false;
170        }
171    }
172
173    // Power of two should succeed.
174    unsafe {
175        if cbuf.initialize(4, buf.as_mut_ptr()).is_err() {
176            return false;
177        }
178    }
179
180    true
181}
182
183#[unsafe(no_mangle)]
184pub extern "C" fn test_cbuf_uninitialized() -> bool {
185    stack_pin_init!(let cbuf = Cbuf::init());
186
187    if !cbuf.full() {
188        return false;
189    }
190
191    if cbuf.write_char(b'A') != 0 {
192        return false;
193    }
194
195    if cbuf.read_char(false) != Err(Status::SHOULD_WAIT) {
196        return false;
197    }
198
199    true
200}
201
202#[unsafe(no_mangle)]
203pub extern "C" fn test_cbuf_wrap_around() -> bool {
204    stack_pin_init!(let cbuf = Cbuf::init());
205    let mut buf = [0u8; 4];
206
207    unsafe {
208        if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
209            return false;
210        }
211    }
212
213    // Write 3 chars (capacity is 3)
214    if cbuf.write_char(b'A') != 1 {
215        return false;
216    }
217    if cbuf.write_char(b'B') != 1 {
218        return false;
219    }
220    if cbuf.write_char(b'C') != 1 {
221        return false;
222    }
223
224    if !cbuf.full() {
225        return false;
226    }
227
228    // Read 3 chars
229    if cbuf.read_char(false) != Ok(b'A') {
230        return false;
231    }
232    if cbuf.read_char(false) != Ok(b'B') {
233        return false;
234    }
235    if cbuf.read_char(false) != Ok(b'C') {
236        return false;
237    }
238
239    if cbuf.full() {
240        return false;
241    }
242
243    // Write 2 chars (wraps pointers)
244    if cbuf.write_char(b'D') != 1 {
245        return false;
246    }
247    if cbuf.write_char(b'E') != 1 {
248        return false;
249    }
250
251    // Read 2 chars (wraps pointers)
252    if cbuf.read_char(false) != Ok(b'D') {
253        return false;
254    }
255    if cbuf.read_char(false) != Ok(b'E') {
256        return false;
257    }
258
259    // Should be empty
260    if cbuf.read_char(false) != Err(Status::SHOULD_WAIT) {
261        return false;
262    }
263
264    true
265}
266
267struct BlockingReadContext {
268    cbuf: *mut Cbuf,
269    state: *const AtomicU32, // 0: init, 1: about to read, 2: read done, 3: error
270    read_char: *mut u8,
271}
272
273// SAFETY: We only pass valid pointers and don't share mutability unsafely.
274unsafe impl Send for BlockingReadContext {}
275
276extern "C" fn blocking_reader_entry(arg: *mut c_void) -> i32 {
277    let ctx = unsafe { &*(arg as *const BlockingReadContext) };
278    let cbuf = unsafe { &*ctx.cbuf };
279    let state = unsafe { &*ctx.state };
280
281    state.store(1, Ordering::SeqCst);
282    let c = cbuf.read_char(true); // Should block until written.
283
284    match c {
285        Ok(val) => {
286            unsafe { *ctx.read_char = val };
287            state.store(2, Ordering::SeqCst);
288            0
289        }
290        Err(status) => {
291            state.store(3, Ordering::SeqCst); // error
292            status.into_raw()
293        }
294    }
295}
296
297#[unsafe(no_mangle)]
298pub extern "C" fn test_cbuf_blocking_read() -> bool {
299    stack_pin_init!(let cbuf = Cbuf::init());
300
301    let mut buf = [0u8; 4];
302    // SAFETY: `buf` is valid for cbuf lifetime.
303    unsafe {
304        if cbuf.initialize(buf.len(), buf.as_mut_ptr()).is_err() {
305            return false;
306        }
307    }
308
309    let state = AtomicU32::new(0);
310    let mut read_char = 0u8;
311
312    let mut ctx = BlockingReadContext {
313        cbuf: &*cbuf as *const Cbuf as *mut Cbuf,
314        state: &state,
315        read_char: &mut read_char,
316    };
317
318    let thread_name = b"cbuf_blocking_read\0".as_ptr() as *const c_char;
319    let ctx_ptr = &mut ctx as *mut BlockingReadContext as *mut c_void;
320
321    unsafe {
322        let thread = match kernel::thread::spawn(thread_name, blocking_reader_entry, ctx_ptr) {
323            Ok(t) => t,
324            Err(_) => return false,
325        };
326
327        // Wait until the reader thread is about to read.
328        while state.load(Ordering::SeqCst) < 1 {
329            kernel::thread::r#yield();
330        }
331
332        // Wait until the reader thread is actually blocked.
333        while !thread.is_blocked() {
334            kernel::thread::r#yield();
335            // If it failed and exited, break.
336            if state.load(Ordering::SeqCst) == 3 {
337                break;
338            }
339        }
340
341        if state.load(Ordering::SeqCst) == 3 {
342            thread.join(ZX_TIME_INFINITE).ok();
343            return false;
344        }
345
346        // Double check it is indeed blocked and state is 1.
347        if !thread.is_blocked() || state.load(Ordering::SeqCst) != 1 {
348            thread.join(ZX_TIME_INFINITE).ok();
349            return false;
350        }
351
352        // Now write a char. This should wake it up.
353        if cbuf.write_char(b'X') != 1 {
354            thread.join(ZX_TIME_INFINITE).ok();
355            return false;
356        }
357
358        // Wait for reader thread to complete.
359        let ret = match thread.join(ZX_TIME_INFINITE) {
360            Ok(r) => r,
361            Err(_) => return false,
362        };
363
364        if ret != 0 {
365            return false;
366        }
367
368        if state.load(Ordering::SeqCst) != 2 {
369            return false;
370        }
371
372        if read_char != b'X' {
373            return false;
374        }
375    }
376
377    true
378}