blob: e461d615f3591d6db78a5c70cff330548b6e397a [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
/// Test suite for Rust user_copy implementation.
#[cfg(ktest)]
#[unittest::suite(name = "rust_user_copy")]
mod tests {
use unittest::{UserMemory, assert_eq, assert_nonnull, assert_null, assert_true, unwrap_ok};
use user_copy::{UserInIovec, UserInOutPtr, UserInPtr, UserOutPtr, UserStringView};
use zx_status::Status;
use zx_types::zx_iovec_t;
/// Test UserPtr offsets.
#[test]
fn offsets() {
let base = 0x1000 as *mut u32;
// UserInPtr offset tests
let in_ptr = UserInPtr::new(base as *const u32);
assert_nonnull!(in_ptr);
assert_eq!(in_ptr.byte_offset(8).as_ptr(), 0x1008 as *const u32);
assert_eq!(in_ptr.byte_offset(-4).as_ptr(), 0x0ffc as *const u32);
assert_eq!(in_ptr.element_offset(3).as_ptr(), 0x100c as *const u32);
let null_in = UserInPtr::<u32>::new(core::ptr::null());
assert_null!(null_in.byte_offset(8));
assert_null!(null_in.element_offset(3));
let def_in = UserInPtr::<u32>::default();
assert_null!(def_in);
// UserOutPtr offset tests
let out_ptr = UserOutPtr::new(base);
assert_nonnull!(out_ptr);
assert_eq!(out_ptr.byte_offset(8).as_ptr(), 0x1008 as *mut u32);
assert_eq!(out_ptr.byte_offset(-4).as_ptr(), 0x0ffc as *mut u32);
assert_eq!(out_ptr.element_offset(3).as_ptr(), 0x100c as *mut u32);
let null_out = UserOutPtr::<u32>::new(core::ptr::null_mut());
assert_null!(null_out.byte_offset(8));
assert_null!(null_out.element_offset(3));
let def_out = UserOutPtr::<u32>::default();
assert_null!(def_out);
// UserInOutPtr offset tests
let inout_ptr = UserInOutPtr::new(base);
assert_nonnull!(inout_ptr);
assert_eq!(inout_ptr.byte_offset(8).as_ptr(), 0x1008 as *mut u32);
assert_eq!(inout_ptr.byte_offset(-4).as_ptr(), 0x0ffc as *mut u32);
assert_eq!(inout_ptr.element_offset(3).as_ptr(), 0x100c as *mut u32);
let null_inout = UserInOutPtr::<u32>::new(core::ptr::null_mut());
assert_null!(null_inout.byte_offset(8));
assert_null!(null_inout.element_offset(3));
let def_inout = UserInOutPtr::<u32>::default();
assert_null!(def_inout);
// UserStringView Default test
let def_sv = UserStringView::default();
assert_null!(def_sv.data);
assert_true!(def_sv.is_empty());
}
/// Test CopyOut.
#[test]
fn copy_out() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
let out_ptr = UserOutPtr::<u32>::new(user.base() as *mut u32);
assert_nonnull!(out_ptr);
unwrap_ok!(out_ptr.write(0xDEADBEEF));
let mut temp = [0u8; 4];
unwrap_ok!(user.vmo_read(&mut temp, 0));
let val = u32::from_ne_bytes(temp);
assert_eq!(val, 0xDEADBEEF);
}
/// Test CopyIn.
#[test]
fn copy_in() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
unwrap_ok!(user.vmo_write(&0xDEADBEEF_u32.to_ne_bytes(), 0));
let in_ptr = UserInPtr::<u32>::new(user.base() as *const u32);
assert_nonnull!(in_ptr);
let val = unwrap_ok!(in_ptr.read());
assert_eq!(val, 0xDEADBEEF);
}
/// Test CopyFromUser.
#[test]
fn copy_from_user() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
unwrap_ok!(user.vmo_write(&0xDEADBEEF_u32.to_ne_bytes(), 0));
let in_ptr = UserInPtr::<u32>::new(user.base() as *const u32);
assert_nonnull!(in_ptr);
let mut temp = core::mem::MaybeUninit::uninit();
let val_ref = unwrap_ok!(in_ptr.copy_from_user(&mut temp));
assert_eq!(*val_ref, 0xDEADBEEF);
}
/// Test CopySliceFromUser.
#[test]
fn copy_slice_from_user() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
let vals = [10u32, 20u32, 30u32];
let mut bytes = [0u8; 12];
for i in 0..3 {
bytes[i * 4..(i + 1) * 4].copy_from_slice(&vals[i].to_ne_bytes());
}
unwrap_ok!(user.vmo_write(&bytes, 0));
let in_ptr = UserInPtr::<u32>::new(user.base() as *const u32);
assert_nonnull!(in_ptr);
let mut out = [core::mem::MaybeUninit::uninit(); 3];
let out_slice = unwrap_ok!(in_ptr.copy_slice_from_user(&mut out));
assert_eq!(out_slice[0], 10);
assert_eq!(out_slice[1], 20);
assert_eq!(out_slice[2], 30);
}
/// Test faults.
#[test]
fn faults() {
let out_ptr = UserOutPtr::<u32>::new(core::ptr::null_mut());
assert_null!(out_ptr);
assert_true!(out_ptr.write(0xDEADBEEF).err() == Some(Status::INVALID_ARGS));
let in_ptr = UserInPtr::<u32>::new(core::ptr::null());
assert_null!(in_ptr);
assert_true!(in_ptr.read().err() == Some(Status::INVALID_ARGS));
let mut temp = core::mem::MaybeUninit::uninit();
assert_true!(in_ptr.copy_from_user(&mut temp).err() == Some(Status::INVALID_ARGS));
let mut temp_slice = [core::mem::MaybeUninit::uninit(); 1];
assert_true!(
in_ptr.copy_slice_from_user(&mut temp_slice).err() == Some(Status::INVALID_ARGS)
);
let bad_addr = usize::MAX as *mut u32;
let out_ptr = UserOutPtr::<u32>::new(bad_addr);
assert_true!(out_ptr.write(0xDEADBEEF).err() == Some(Status::INVALID_ARGS));
let in_ptr = UserInPtr::<u32>::new(bad_addr as *const u32);
assert_true!(in_ptr.read().err() == Some(Status::INVALID_ARGS));
}
/// Test IovecCapacity.
#[test]
fn iovec_capacity() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
let vec = [
zx_iovec_t { buffer: core::ptr::null(), capacity: 348 },
zx_iovec_t { buffer: core::ptr::null(), capacity: 58 },
];
let bytes = unsafe {
core::slice::from_raw_parts(vec.as_ptr() as *const u8, core::mem::size_of_val(&vec))
};
unwrap_ok!(user.vmo_write(bytes, 0));
let in_ptr = UserInPtr::<zx_iovec_t>::new(user.base() as *const zx_iovec_t);
assert_nonnull!(in_ptr);
let iovec = UserInIovec::new(in_ptr, 2);
let total_capacity = unwrap_ok!(iovec.get_total_capacity());
assert_eq!(total_capacity, 406);
}
/// Test IovecForeach.
#[test]
fn iovec_foreach() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
let vec = [
zx_iovec_t { buffer: core::ptr::null(), capacity: 7 },
zx_iovec_t { buffer: core::ptr::null(), capacity: 11 },
zx_iovec_t { buffer: core::ptr::null(), capacity: 13 },
];
let bytes = unsafe {
core::slice::from_raw_parts(vec.as_ptr() as *const u8, core::mem::size_of_val(&vec))
};
unwrap_ok!(user.vmo_write(bytes, 0));
let in_ptr = UserInPtr::<zx_iovec_t>::new(user.base() as *const zx_iovec_t);
assert_nonnull!(in_ptr);
let iovec = UserInIovec::new(in_ptr, 3);
let mut product = 2usize;
let res = iovec.for_each(|_buf, cap| {
product = product.wrapping_mul(cap);
Ok(())
});
unwrap_ok!(res);
assert_eq!(product, 2002);
}
/// Test StringView.
#[test]
fn string_view() {
let mut user = UserMemory::create(4096).unwrap();
unwrap_ok!(user.commit_and_map(4096));
let k_string = b"Hello, Fuchsia!\0";
unwrap_ok!(user.vmo_write(k_string, 0));
let in_ptr = UserInPtr::<u8>::new(user.base() as *const u8);
assert_nonnull!(in_ptr);
let sv = UserStringView { data: in_ptr, length: k_string.len() };
let mut buf = [core::mem::MaybeUninit::uninit(); 32];
let out_slice = unwrap_ok!(sv.copy_slice_from_user(&mut buf));
assert_true!(out_slice == k_string);
// Buffer too small should return INVALID_ARGS
let mut small_buf = [core::mem::MaybeUninit::uninit(); 5];
assert_true!(sv.copy_slice_from_user(&mut small_buf).err() == Some(Status::INVALID_ARGS));
}
}