Skip to main content

cbuf_rs_tests/
kernel.rs

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