blob: 4a6dbcb502eb8da5c435fd57832e31dc796366e9 [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
use crate::user_copy::{UserInPtr, UserOutPtr};
use crate::vm::page::VmPagePtr;
use crate::vm::page_state::VmPageState;
use crate::vm::{physmap, pmm};
use core::cmp::min;
use core::convert::Infallible;
use core::ffi::c_char;
use core::mem::{ManuallyDrop, MaybeUninit, align_of, size_of};
use core::pin::Pin;
use core::ptr::drop_in_place;
use core::slice::{from_raw_parts, from_raw_parts_mut};
use counters_rs::define_kcounter;
use fbl::{DoublyLinkedList, DoublyLinkedListContainable, DoublyLinkedListNode};
use page;
use page_bindings::vm_page_state;
use pin_init::{PinInit, pin_data, pin_init, pinned_drop, stack_pin_init};
use zr::static_assert;
use zx_status::Status;
// Total amount of memory occupied by MBuf objects.
define_kcounter!(MBUF_TOTAL_BYTES_COUNT, "mbuf.total_bytes", Sum);
/// An MBuf is a small fixed-size chainable memory buffer.
#[repr(C)]
#[derive(DoublyLinkedListContainable)]
struct MBuf {
#[dll_node]
node: ManuallyDrop<DoublyLinkedListNode<MBuf>>,
/// Length of the valid `data` in this buffer. Writes can append more to `data` and increment
/// this length.
len: u32,
/// `pkt_len` is set to the total number of bytes in a packet when a socket is in
/// `ZX_SOCKET_DATAGRAM` mode. A `pkt_len` of 0 means this `MBuf` is part of the body of a
/// packet.
///
/// Always 0 in `ZX_SOCKET_STREAM` mode.
pkt_len: u32,
/// Back-pointer to the `vm_page_t` this `MBuf` was allocated from. Recording this is just an
/// optimization as it should always be the case that:
/// `Pmm::Node().PaddrToPage(physmap_to_paddr(this)) == page_`
page: VmPagePtr,
/// The data field is left uninitialized as the caller is going to immediately overwrite with
/// the payload, and is trusted to not access any uninitialized portions.
/// TODO: maybe union data with char* blocks for large messages
data: MaybeUninit<[u8; MBuf::PAYLOAD_SIZE]>,
}
impl MBuf {
// 16 for the linked list 16 for the explicit fields.
pub const HEADER_SIZE: usize = 32;
pub const PAYLOAD_SIZE: usize = page::SIZE - Self::HEADER_SIZE;
/// Calculate the number of MBuf objects needed to store a payload of the given size.
pub const fn num_buffers_for_payload(payload: usize) -> usize {
payload.div_ceil(Self::PAYLOAD_SIZE)
}
/// Allocates and initializes a single `MBuf` page from PMM.
pub fn new() -> Result<*mut MBuf, Status> {
let (page, paddr) = pmm::alloc_page(0)?;
MBUF_TOTAL_BYTES_COUNT.add(size_of::<MBuf>() as i64);
// SAFETY: `page` was just allocated from `pmm::alloc_page` and is mapped in the physmap.
// `buf_ptr.write(...)` initializes the memory without dropping previous contents.
let buf_ptr = physmap::paddr_to_physmap(paddr).0 as *mut MBuf;
unsafe {
page.set_state(VmPageState(vm_page_state::IPC));
buf_ptr.write(MBuf {
node: ManuallyDrop::new(DoublyLinkedListNode::new()),
len: 0,
pkt_len: 0,
page,
data: MaybeUninit::uninit(),
});
}
Ok(buf_ptr)
}
/// Returns number of bytes of free space in this MBuf.
pub fn available_space(&self) -> usize {
Self::PAYLOAD_SIZE - (self.len as usize)
}
/// Returns a slice of valid initialized data starting at `offset` up to `self.len`,
/// typed as `c_char` for user memory copy operations.
pub fn read(&self, offset: usize) -> &[c_char] {
let len = self.len as usize;
if offset >= len {
return &[];
}
let valid_len = len - offset;
// SAFETY: `self.data` has length `PAYLOAD_SIZE >= len >= offset + valid_len`.
// The memory from `0..len` has been initialized by prior writes.
// `c_char` and `u8` have identical size (1 byte), alignment (1 byte), and all bit patterns
// are valid.
unsafe {
let ptr = self.data.as_ptr().cast::<c_char>().add(offset);
from_raw_parts(ptr, valid_len)
}
}
/// Returns a mutable slice of uninitialized capacity starting after current `len` up to
/// `max_len` (capped by `available_space()`), typed as `MaybeUninit<c_char>` for user copies.
pub fn extend(&mut self, max_len: usize) -> &mut [MaybeUninit<c_char>] {
let copy_len = min(self.available_space(), max_len);
let offset = self.len as usize;
// SAFETY: `offset + copy_len <= PAYLOAD_SIZE`.
// `MaybeUninit<c_char>` and `u8` have identical size (1 byte) and alignment (1 byte).
unsafe {
let ptr = self.data.as_mut_ptr().cast::<MaybeUninit<c_char>>().add(offset);
from_raw_parts_mut(ptr, copy_len)
}
}
/// Copies up to `len - *pos` bytes from user pointer `src` at `*pos` into this `MBuf`,
/// advancing `*pos` and `self.len` by the number of bytes copied.
pub fn write_from_user(
&mut self,
src: UserInPtr<c_char>,
pos: &mut usize,
len: usize,
) -> Result<(), Status> {
let dst_slice = self.extend(len - *pos);
let copy_len = dst_slice.len();
src.byte_offset(*pos as isize).copy_slice_from_user(dst_slice)?;
*pos += copy_len;
self.len += copy_len as u32;
Ok(())
}
}
impl Drop for MBuf {
fn drop(&mut self) {
MBUF_TOTAL_BYTES_COUNT.add(-(size_of::<MBuf>() as i64));
// SAFETY: We explicitly drop `self.node` while the page memory is still valid.
// Because `self.node` is `ManuallyDrop`, it will not be dropped again after `drop` returns.
// We then return the backing page to PMM.
unsafe {
ManuallyDrop::drop(&mut self.node);
pmm::free_page(self.page);
}
}
}
static_assert!(size_of::<MBuf>() == page::SIZE);
static_assert!(align_of::<MBuf>() == 8);
/// Helper function to allocate `num` `MBuf` buffers into a `DoublyLinkedList`.
///
/// If allocation of any buffer fails, all buffers in `bufs` are freed and the error is returned.
fn alloc_mbufs(num: usize, bufs: &mut DoublyLinkedList<*mut MBuf>) -> Result<(), Status> {
for _ in 0..num {
let buf_ptr = match MBuf::new() {
Ok(ptr) => ptr,
Err(err) => {
free_mbufs(bufs);
return Err(err);
}
};
// SAFETY: `buf_ptr` was allocated by `MBuf::new` and is valid and unaliased.
unsafe {
bufs.push_back_raw(buf_ptr);
}
}
Ok(())
}
/// Helper function to free all `MBuf` buffers in a `DoublyLinkedList`.
fn free_mbufs(bufs: &mut DoublyLinkedList<*mut MBuf>) {
while let Some(buf) = bufs.pop_front() {
// SAFETY: `buf` was popped from `bufs` and points to a valid, initialized, and unaliased
// `MBuf` allocated from PMM. Dropping in place invokes `MBuf::drop`, returning the page to
// PMM.
unsafe {
drop_in_place(buf);
}
}
}
/// Helper function to free the front `MBuf` buffer in a `DoublyLinkedList`.
fn free_front_mbuf(bufs: &mut DoublyLinkedList<*mut MBuf>) {
if let Some(buf) = bufs.pop_front() {
// SAFETY: `buf` was popped from `bufs` and points to a valid, initialized, and unaliased
// `MBuf` allocated from PMM. Dropping in place invokes `MBuf::drop`, returning the page to
// PMM.
unsafe {
drop_in_place(buf);
}
}
}
/// MBufChain is a container for storing a stream of bytes or a sequence of datagrams.
///
/// It's designed to back sockets and channels. Don't simultaneously store stream data and datagrams
/// in a single instance.
#[pin_data(PinnedDrop)]
pub struct MBufChain {
/// The MBufs are placed in a doubly linked list so that both the front and back of the list can
/// be manipulated and to allow for efficiently splicing lists into each other.
///
/// The active buffers that make up this chain. buffers.front() + read_cursor_off is the read
/// cursor. buffers.back() is the write cursor.
#[pin]
buffers: DoublyLinkedList<*mut MBuf>,
/// The byte offset of the read cursor in next MBuf.
read_cursor_off: usize,
size: usize,
}
impl MBufChain {
/// Although the maximum size of the data in an MBuf is a kernel implementation detail, it is
/// visible to user space. To avoid unintentionally changing it when modifying other data
/// structures round up the currently chosen size (256KiB) to the next multiple of the MBuf
/// payload size.
pub const MAX_SIZE: usize = (256usize * 1024).div_ceil(MBuf::PAYLOAD_SIZE) * MBuf::PAYLOAD_SIZE;
/// Constructs a new `MBufChain`.
pub fn new() -> impl PinInit<Self, Infallible> {
pin_init!(Self {
buffers <- DoublyLinkedList::<*mut MBuf>::new(),
read_cursor_off: 0,
size: 0,
})
}
/// Writes `len` bytes of stream data from `src`.
///
/// Returns an error on failure, although some data may still have been written, in which case
/// `written` is set with the amount.
///
/// Returns `(res, written)` indicating the operation status and the number of bytes written.
pub fn write_stream(
self: Pin<&mut Self>,
src: UserInPtr<c_char>,
mut len: usize,
) -> (Result<(), Status>, usize) {
// SAFETY: We hold `Pin<&mut Self>` and obtain `&mut Self` to perform buffer writes without
// moving `Self`.
let this = unsafe { self.get_unchecked_mut() };
// Cap len by the max we are allowed to write.
len = min(Self::MAX_SIZE - this.size, len);
if len == 0 {
return (Err(Status::SHOULD_WAIT), 0);
}
let avail = this.buffers.back().map_or(0, |b| b.available_space());
let num_buffers = MBuf::num_buffers_for_payload(len.saturating_sub(avail));
stack_pin_init!(let bufs = DoublyLinkedList::<*mut MBuf>::new());
// SAFETY: `bufs` is pinned on stack; obtaining mutable reference to the list is safe.
let bufs_list = unsafe { bufs.get_unchecked_mut() };
if alloc_mbufs(num_buffers, bufs_list).is_err() {
return (Err(Status::SHOULD_WAIT), 0);
}
let mut pos = 0usize;
let mut tail_written = 0usize;
let tail = this.buffers.back_mut().filter(|b| b.available_space() > 0);
let bufs =
tail.into_iter().map(|b| (b, true)).chain(bufs_list.iter_mut().map(|b| (b, false)));
for (buf, is_tail) in bufs {
let res = buf.write_from_user(src, &mut pos, len);
if is_tail {
this.size += pos;
tail_written = pos;
}
if let Err(err) = res {
// TODO(https://fxbug.dev/42109418): Note that although we set |written| for the
// benefit of the socket dispatcher updating signals, ultimately we're not
// indicating to the caller that data added so far in previous copies was written
// successfully. This means the caller may try to re-send the same data again,
// leading to duplicate data. Consider changing the socket dispatcher to forward
// this partial write information to the caller, or consider not committing any of
// the new data until we can ensure success, or consider putting the socket in a
// state where it can't succeed a subsequent write.
free_mbufs(bufs_list);
return (Err(err), tail_written);
}
}
this.buffers.splice(bufs_list);
this.size += pos - tail_written;
(Ok(()), pos)
}
/// Writes a datagram of `len` bytes from `src`.
///
/// This operation is atomic in that either the entire datagram is written successfully or the
/// chain is unmodified.
///
/// Writing a zero-length datagram is an error.
///
/// Returns an error on failure, although some data may still have been written, in which case
/// `written` is set with the amount.
///
/// Returns `(res, written)` indicating the operation status and the number of bytes written.
pub fn write_datagram(
self: Pin<&mut Self>,
src: UserInPtr<c_char>,
len: usize,
) -> (Result<(), Status>, usize) {
// SAFETY: We hold `Pin<&mut Self>` and obtain `&mut Self` to perform buffer writes without
// moving `Self`.
let this = unsafe { self.get_unchecked_mut() };
if len == 0 {
return (Err(Status::INVALID_ARGS), 0);
}
if len > Self::MAX_SIZE {
return (Err(Status::OUT_OF_RANGE), 0);
}
if Self::MAX_SIZE - this.size < len {
return (Err(Status::SHOULD_WAIT), 0);
}
let num_buffers = MBuf::num_buffers_for_payload(len);
stack_pin_init!(let bufs = DoublyLinkedList::<*mut MBuf>::new());
// SAFETY: `bufs` is pinned on stack; obtaining mutable reference to the list is safe.
let bufs_list = unsafe { bufs.get_unchecked_mut() };
if alloc_mbufs(num_buffers, bufs_list).is_err() {
return (Err(Status::SHOULD_WAIT), 0);
}
let mut pos = 0usize;
for buf in bufs_list.iter_mut() {
if let Err(err) = buf.write_from_user(src, &mut pos, len) {
free_mbufs(bufs_list);
return (Err(err), 0);
}
}
bufs_list.front_mut().unwrap().pkt_len = len as u32;
// Successfully built the packet mbufs. Splice into this.buffers.
this.buffers.splice(bufs_list);
this.size += len;
(Ok(()), len)
}
/// Reads up to `len` bytes of stream data from the chain into `dst` (no boundaries).
///
/// The actual number of bytes read is returned in `actual`, and this can be non-zero even if
/// the read itself is an error.
///
/// Returns `(res, actual)` indicating the operation status and number of bytes read.
pub fn read_stream(
self: Pin<&mut Self>,
dst: UserOutPtr<c_char>,
len: usize,
) -> (Result<(), Status>, usize) {
// SAFETY: We hold `Pin<&mut Self>` and obtain `&mut Self` to read and remove buffers from
// `Self`.
let this = unsafe { self.get_unchecked_mut() };
if this.size == 0 || len == 0 {
return (Ok(()), 0);
}
let mut pos = 0usize;
let mut read_off = this.read_cursor_off;
let res = (|| {
while pos < len
&& let Some(front) = this.buffers.front()
{
let slice = front.read(read_off);
let copy_len = min(slice.len(), len - pos);
dst.byte_offset(pos as isize).copy_slice_to_user(&slice[..copy_len])?;
pos += copy_len;
read_off += copy_len;
this.size -= copy_len;
if read_off == front.len as usize {
free_front_mbuf(&mut this.buffers);
read_off = 0;
}
}
Ok(())
})();
// Record the fact that some data might have been read, even if the overall operation is
// considered a failure.
this.read_cursor_off = read_off;
(res, pos)
}
/// Reads at most one datagram from the chain into `dst`.
///
/// If `len` is too small to read a complete datagram, a partial datagram is returned and its
/// remaining bytes are discarded.
///
/// The actual number of bytes read is returned in `actual`, and this can be non-zero even if
/// the read itself is an error.
///
/// Returns an error on failure. If an error occurs while copying a datagram to `dst`, the
/// datagram is dropped.
///
/// Returns `(res, actual)` indicating the operation status and number of bytes read.
pub fn read_datagram(
self: Pin<&mut Self>,
dst: UserOutPtr<c_char>,
mut len: usize,
) -> (Result<(), Status>, usize) {
// SAFETY: We hold `Pin<&mut Self>` and obtain `&mut Self` to read and remove buffers from
// `Self`.
let this = unsafe { self.get_unchecked_mut() };
if this.size == 0 || len == 0 {
return (Ok(()), 0);
}
len = min(len, this.buffers.front().unwrap().pkt_len as usize);
let mut pos = 0usize;
let res = (|| {
while pos < len
&& let Some(front) = this.buffers.front()
{
let slice = front.read(0);
let copy_len = min(slice.len(), len - pos);
let copy_res = dst.byte_offset(pos as isize).copy_slice_to_user(&slice[..copy_len]);
// In datagram mode, each visited buffer is popped and discarded completely.
this.size -= front.len as usize;
free_front_mbuf(&mut this.buffers);
copy_res?;
pos += copy_len;
}
Ok(())
})();
// Drain any leftover mbufs in the datagram packet if we're consuming data, even
// if we fail to read bytes.
while let Some(front) = this.buffers.front()
&& front.pkt_len == 0
{
this.size -= front.len as usize;
free_front_mbuf(&mut this.buffers);
}
(res, pos)
}
/// Same as `read_stream()`/`read_datagram()` but leaves the bytes in the chain instead of
/// consuming them, even if an error occurs.
///
/// Peeks up to `len` bytes of stream data from the chain into `dst` without consuming them.
///
/// Returns `(res, actual)` indicating the operation status and number of bytes peeked.
pub fn peek_stream(&self, dst: UserOutPtr<c_char>, len: usize) -> (Result<(), Status>, usize) {
if self.size == 0 || len == 0 {
return (Ok(()), 0);
}
let mut pos = 0usize;
let mut read_off = self.read_cursor_off;
let res = (|| {
for buf in self.buffers.iter() {
if pos >= len {
break;
}
let slice = buf.read(read_off);
let copy_len = min(slice.len(), len - pos);
dst.byte_offset(pos as isize).copy_slice_to_user(&slice[..copy_len])?;
pos += copy_len;
read_off = 0;
}
Ok(())
})();
(res, pos)
}
/// Peeks at most one datagram from the chain into `dst` without consuming it.
///
/// Returns `(res, actual)` indicating the operation status and number of bytes peeked.
pub fn peek_datagram(
&self,
dst: UserOutPtr<c_char>,
len: usize,
) -> (Result<(), Status>, usize) {
if self.size == 0 || len == 0 {
return (Ok(()), 0);
}
let len = min(len, self.buffers.front().unwrap().pkt_len as usize);
self.peek_stream(dst, len)
}
/// Returns number of bytes stored in the chain.
pub fn stream_size(&self) -> usize {
self.size
}
/// Returns number of bytes stored in the first datagram, or 0 if empty.
pub fn datagram_size(&self) -> usize {
if let Some(front) = self.buffers.front() { front.pkt_len as usize } else { 0 }
}
/// Returns true if chain is full.
pub fn is_full(&self) -> bool {
self.size >= Self::MAX_SIZE
}
/// Returns true if chain is empty.
pub fn is_empty(&self) -> bool {
self.size == 0
}
}
#[pinned_drop]
impl PinnedDrop for MBufChain {
fn drop(self: Pin<&mut Self>) {
// SAFETY: `self` is being dropped and will not be accessed again. Obtaining `&mut Self`
// allows freeing remaining buffers.
let this = unsafe { self.get_unchecked_mut() };
free_mbufs(&mut this.buffers);
}
}
/// In-tree kernel unit tests for `MBufChain`.
#[cfg(ktest)]
#[unittest::suite(name = "mbuf_rust")]
mod tests {
use super::{MBuf, MBufChain, alloc_mbufs, free_mbufs};
use crate::user_copy::{UserInPtr, UserOutPtr};
use crate::user_memory::UserMemory;
use core::ffi::c_char;
use core::mem::MaybeUninit;
use core::pin::Pin;
use pin_init::stack_pin_init;
use unittest::{expect_eq, expect_false, expect_ok, expect_true, unwrap_ok};
use zx_status::Status;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum MessageType {
Stream,
Datagram,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum ReadType {
Read,
Peek,
}
fn make_user_in_pattern(
size: usize,
pattern: impl Fn(usize) -> u8,
) -> Option<(UserMemory, UserInPtr<c_char>)> {
let alloc_size = if size == 0 { 1 } else { size };
let mem = UserMemory::create(alloc_size)?;
mem.commit_and_map(0..alloc_size).ok()?;
let mut chunk = [0u8; 512];
let mut offset = 0;
while offset < size {
let to_write = core::cmp::min(chunk.len(), size - offset);
for (i, b) in chunk[..to_write].iter_mut().enumerate() {
*b = pattern(offset + i);
}
mem.vmo_write(&chunk[..to_write], offset as u64).ok()?;
offset += to_write;
}
let ptr = UserInPtr::new(mem.base() as *const c_char);
Some((mem, ptr))
}
fn make_user_in_byte(size: usize, val: u8) -> Option<(UserMemory, UserInPtr<c_char>)> {
make_user_in_pattern(size, |_| val)
}
fn make_user_in(data: &[u8]) -> Option<(UserMemory, UserInPtr<c_char>)> {
make_user_in_pattern(data.len(), |i| data[i])
}
fn make_user_out(size: usize) -> Option<(UserMemory, UserOutPtr<c_char>)> {
let alloc_size = if size == 0 { 1 } else { size };
let mem = UserMemory::create(alloc_size)?;
mem.commit_and_map(0..alloc_size).ok()?;
let ptr = UserOutPtr::new(mem.base() as *mut c_char);
Some((mem, ptr))
}
fn verify_user_mem(mem: &UserMemory, size: usize, pattern: impl Fn(usize) -> u8) -> bool {
let mut chunk = [MaybeUninit::<u8>::uninit(); 512];
let mut offset = 0;
while offset < size {
let to_read = core::cmp::min(chunk.len(), size - offset);
let Ok(read_bytes) = mem.vmo_read(&mut chunk[..to_read], offset as u64) else {
return false;
};
for (i, &b) in read_bytes.iter().enumerate() {
if b != pattern(offset + i) {
return false;
}
}
offset += to_read;
}
true
}
/// Writes a string slice into `chain`.
///
/// Helps eliminate boilerplate code dealing with copying in and out of user memory to make the
/// test logic more obvious.
fn write_helper(chain: Pin<&mut MBufChain>, str_data: &str, msg_type: MessageType) -> bool {
let len = str_data.len();
let Some((_mem, src)) = make_user_in(str_data.as_bytes()) else { return false };
let (res, written) = match msg_type {
MessageType::Datagram => chain.write_datagram(src, len),
MessageType::Stream => chain.write_stream(src, len),
};
res.is_ok() && written == len
}
/// Reads or peeks data from `chain`.
fn read_helper<'a>(
chain: &mut Pin<&mut MBufChain>,
buf: &'a mut [MaybeUninit<u8>],
len: usize,
msg_type: MessageType,
read_type: ReadType,
actual: &mut usize,
) -> Result<&'a mut [u8], Status> {
let (mem, dst) = make_user_out(len).ok_or(Status::NO_MEMORY)?;
let (res, nread) = match (read_type, msg_type) {
(ReadType::Read, MessageType::Datagram) => chain.as_mut().read_datagram(dst, len),
(ReadType::Read, MessageType::Stream) => chain.as_mut().read_stream(dst, len),
(ReadType::Peek, MessageType::Datagram) => chain.peek_datagram(dst, len),
(ReadType::Peek, MessageType::Stream) => chain.peek_stream(dst, len),
};
*actual = nread;
res?;
if nread > 0 {
let copy_len = core::cmp::min(nread, buf.len());
mem.vmo_read(&mut buf[..copy_len], 0)
} else {
Ok(&mut [])
}
}
/// Tests initial state of MBufChain.
#[test]
fn test_initial_state() {
stack_pin_init!(let chain_pin = MBufChain::new());
let chain = chain_pin.as_ref();
expect_true!(chain.is_empty());
expect_false!(chain.is_full());
expect_eq!(chain.stream_size(), 0);
}
/// Tests reading stream when empty.
#[test]
fn test_stream_read_empty() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let mut buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut buf,
1,
MessageType::Stream,
ReadType::Read,
&mut actual
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
}
/// Tests reading stream with zero length.
#[test]
fn test_stream_read_zero() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "x", MessageType::Stream));
let mut buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut buf,
0,
MessageType::Stream,
ReadType::Read,
&mut actual
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
}
/// Tests basic stream writing and reading.
#[test]
fn test_stream_write_basic() {
const WRITE_LEN: usize = 1024;
const NUM_WRITES: usize = 5;
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
// Call write several times with different buffer contents.
for i in 0..NUM_WRITES {
let (_mem, src) = make_user_in_byte(WRITE_LEN, b'A' + (i as u8)).unwrap();
let (res, written) = chain.as_mut().write_stream(src, WRITE_LEN);
expect_ok!(res);
expect_eq!(written, WRITE_LEN);
expect_false!(chain.is_empty());
expect_false!(chain.is_full());
expect_eq!(chain.stream_size(), (i + 1) * WRITE_LEN);
}
// Read it all back in one call.
const TOTAL_LEN: usize = WRITE_LEN * NUM_WRITES;
expect_eq!(chain.stream_size(), TOTAL_LEN);
let (mem_out, dst) = make_user_out(TOTAL_LEN).unwrap();
let (res, actual) = chain.as_mut().read_stream(dst, TOTAL_LEN);
expect_ok!(res);
expect_eq!(actual, TOTAL_LEN);
expect_true!(chain.is_empty());
expect_false!(chain.is_full());
expect_eq!(chain.stream_size(), 0);
// Verify result.
expect_true!(verify_user_mem(&mem_out, TOTAL_LEN, |offset| {
b'A' + ((offset / WRITE_LEN) as u8)
}));
}
/// Tests stream writing zero length.
#[test]
fn test_stream_write_zero() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let (_mem, src) = make_user_in_byte(1, 0).unwrap();
let (res, written) = chain.as_mut().write_stream(src, 0);
// TODO(maniscalco): Is ZX_ERR_SHOULD_WAIT really the right error here in this case?
expect_true!(res == Err(Status::SHOULD_WAIT));
expect_eq!(written, 0);
expect_true!(chain.is_empty());
expect_false!(chain.is_full());
expect_eq!(chain.stream_size(), 0);
}
/// Tests stream writing beyond capacity.
#[test]
fn test_stream_write_too_much() {
const WRITE_LEN: usize = 65536;
let (_mem_in, src) = make_user_in_byte(WRITE_LEN, 0).unwrap();
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
// Fill the chain until it refuses to take any more.
let mut total_written = 0usize;
while !chain.is_full() {
let (res, written) = chain.as_mut().write_stream(src, WRITE_LEN);
if res.is_err() {
break;
}
total_written += written;
}
expect_false!(chain.is_empty());
expect_true!(chain.is_full());
expect_eq!(total_written, chain.stream_size());
// Read it all back out and see we get back the same number of bytes we wrote.
let (_mem_out, dst) = make_user_out(WRITE_LEN).unwrap();
let mut total_read = 0usize;
while !chain.is_empty() {
let (res, bytes_read) = chain.as_mut().read_stream(dst, WRITE_LEN);
if res.is_err() || bytes_read == 0 {
break;
}
total_read += bytes_read;
}
expect_true!(chain.is_empty());
expect_eq!(chain.stream_size(), 0);
expect_eq!(total_written, total_read);
}
/// Tests stream peeking data.
#[test]
fn test_stream_peek() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "abc", MessageType::Stream));
expect_true!(write_helper(chain.as_mut(), "123", MessageType::Stream));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 10];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"a");
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
3,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc");
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
4,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc1");
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
6,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc123");
expect_eq!(chain.stream_size(), 6);
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
6,
MessageType::Stream,
ReadType::Read,
&mut actual,
));
expect_true!(bytes == b"abc123");
}
/// Tests stream peeking when empty.
#[test]
fn test_stream_peek_empty() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
}
/// Tests stream peeking zero length.
#[test]
fn test_stream_peek_zero() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "a", MessageType::Stream));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
0,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
}
/// Tests stream peeking with underflow.
#[test]
fn test_stream_peek_underflow() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "abc", MessageType::Stream));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 10];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
10,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc");
expect_true!(write_helper(chain.as_mut(), "123", MessageType::Stream));
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
10,
MessageType::Stream,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc123");
}
/// Tests datagram reading when empty.
#[test]
fn test_datagram_read_empty() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
expect_true!(chain.is_empty());
}
/// Tests datagram reading zero length.
#[test]
fn test_datagram_read_zero() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "x", MessageType::Datagram));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
0,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
expect_false!(chain.is_empty());
}
/// Tests datagram reading with a small buffer.
#[test]
fn test_datagram_read_buffer_too_small() {
const WRITE_LEN: usize = 32;
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let (_mem_a, src_a) = make_user_in_byte(WRITE_LEN, b'A').unwrap();
let (res_a, written_a) = chain.as_mut().write_datagram(src_a, WRITE_LEN);
expect_ok!(res_a);
expect_eq!(written_a, WRITE_LEN);
expect_eq!(chain.stream_size(), WRITE_LEN);
expect_false!(chain.is_empty());
let (_mem_b, src_b) = make_user_in_byte(WRITE_LEN, b'B').unwrap();
let (res_b, written_b) = chain.as_mut().write_datagram(src_b, WRITE_LEN);
expect_ok!(res_b);
expect_eq!(written_b, WRITE_LEN);
expect_eq!(chain.stream_size(), 2 * WRITE_LEN);
expect_false!(chain.is_empty());
let mut read_buf = [MaybeUninit::<u8>::uninit(); WRITE_LEN];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(actual, 1);
expect_eq!(bytes[0], b'A');
expect_false!(chain.is_empty());
expect_eq!(chain.stream_size(), WRITE_LEN);
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
WRITE_LEN,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(actual, WRITE_LEN);
expect_true!(chain.is_empty());
expect_eq!(chain.stream_size(), 0);
expect_true!(bytes.iter().all(|&b| b == b'B'));
}
/// Tests basic datagram writing and reading.
#[test]
fn test_datagram_write_basic() {
const NUM_DATAGRAMS: usize = 100;
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let mut total_written = 0;
// Write a series of datagrams with different sizes.
for i in 1..=NUM_DATAGRAMS {
let (_mem, src) = make_user_in_byte(i, i as u8).unwrap();
let (res, written) = chain.as_mut().write_datagram(src, i);
expect_ok!(res);
expect_eq!(written, i);
total_written += written;
expect_false!(chain.is_empty());
expect_false!(chain.is_full());
}
// Verify size() returns correctly
expect_eq!(chain.datagram_size(), 1);
expect_eq!(chain.stream_size(), total_written);
// Read them back and verify their contents.
for i in 1..=NUM_DATAGRAMS {
expect_eq!(chain.datagram_size(), i);
let mut read_buf = [MaybeUninit::<u8>::uninit(); 100];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf[..i],
i,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(actual, i);
expect_true!(bytes.iter().all(|&b| b == (i as u8)));
}
expect_true!(chain.is_empty());
expect_eq!(chain.stream_size(), 0);
}
/// Tests datagram writing zero length.
#[test]
fn test_datagram_write_zero() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let (_mem, src) = make_user_in_byte(1, 0).unwrap();
let (res, written) = chain.as_mut().write_datagram(src, 0);
expect_true!(res == Err(Status::INVALID_ARGS));
expect_eq!(written, 0);
expect_true!(chain.is_empty());
expect_false!(chain.is_full());
expect_eq!(chain.datagram_size(), 0);
expect_eq!(chain.stream_size(), 0);
}
/// Tests datagram writing beyond capacity.
#[test]
fn test_datagram_write_too_much() {
const WRITE_LEN: usize = 65536;
let (_mem_in, src) = make_user_in_byte(WRITE_LEN, 0).unwrap();
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
// Fill the chain until it refuses to take any more.
let mut num_datagrams_written = 0;
while !chain.is_full() {
let (res, written) = chain.as_mut().write_datagram(src, WRITE_LEN);
if res.is_err() {
break;
}
num_datagrams_written += 1;
expect_eq!(written, WRITE_LEN);
}
expect_false!(chain.is_empty());
expect_eq!(chain.stream_size(), WRITE_LEN * num_datagrams_written);
// Read it all back out and see that there's none left over.
let (_mem_out, dst) = make_user_out(WRITE_LEN).unwrap();
let mut num_datagrams_read = 0;
while !chain.is_empty() {
let (res, actual) = chain.as_mut().read_datagram(dst, WRITE_LEN);
if res.is_err() || actual == 0 {
break;
}
num_datagrams_read += 1;
}
expect_true!(chain.is_empty());
expect_eq!(chain.stream_size(), 0);
expect_eq!(num_datagrams_written, num_datagrams_read);
}
/// Tests datagram buffer reuse.
#[test]
fn test_datagram_reuse_mbuf() {
let large_write = MBuf::PAYLOAD_SIZE + 10;
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
// Write two datagrams.
let (_mem_a, a_src) = make_user_in_byte(1, b'a').unwrap();
let (res_a, written_a) = chain.as_mut().write_datagram(a_src, 1);
expect_ok!(res_a);
expect_eq!(written_a, 1);
let (_mem_b, b_src) = make_user_in_byte(1, b'b').unwrap();
let (res_b, written_b) = chain.as_mut().write_datagram(b_src, 1);
expect_ok!(res_b);
expect_eq!(written_b, 1);
// Now read them both out.
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(bytes[0], b'a');
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(bytes[0], b'b');
// Now write a large datagram that spans two buffers.
let (_mem_c, c_src) = make_user_in_byte(large_write, b'c').unwrap();
let (res_c, written_c) = chain.as_mut().write_datagram(c_src, large_write);
expect_ok!(res_c);
expect_eq!(written_c, large_write);
// Write in a second small datagram.
let (_mem_d, d_src) = make_user_in_byte(1, b'd').unwrap();
let (res_d, written_d) = chain.as_mut().write_datagram(d_src, 1);
expect_ok!(res_d);
expect_eq!(written_d, 1);
// Do a short read to consume the first datagram.
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(bytes[0], b'c');
// Reading again should give us the second datagram we wrote, as the remaining of the first
// should have been discarded.
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_eq!(bytes[0], b'd');
// At this point the socket should be empty.
expect_true!(chain.is_empty());
}
/// Tests writing a datagram packet larger than the mbuf's capacity.
#[test]
fn test_datagram_write_huge_packet() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let huge_packet_size = MBufChain::MAX_SIZE + 1;
let (_mem, src) = make_user_in_byte(1, 0).unwrap();
let (res, _written) = chain.as_mut().write_datagram(src, huge_packet_size);
expect_true!(res == Err(Status::OUT_OF_RANGE));
}
/// Tests datagram peeking.
#[test]
fn test_datagram_peek() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "abc", MessageType::Datagram));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 10];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"a");
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
3,
MessageType::Datagram,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc");
// Make sure peeking didn't affect an actual read.
expect_eq!(chain.stream_size(), 3);
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
3,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_true!(bytes == b"abc");
}
/// Tests datagram peeking empty.
#[test]
fn test_datagram_peek_empty() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
1,
MessageType::Datagram,
ReadType::Peek,
&mut actual,
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
}
/// Tests datagram peeking zero length.
#[test]
fn test_datagram_peek_zero() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "a", MessageType::Datagram));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 1];
let mut actual = 0;
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
0,
MessageType::Datagram,
ReadType::Peek,
&mut actual,
));
expect_eq!(actual, 0);
expect_true!(bytes.is_empty());
}
/// Tests datagram peeking underflow.
#[test]
fn test_datagram_peek_underflow() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
expect_true!(write_helper(chain.as_mut(), "abc", MessageType::Datagram));
expect_true!(write_helper(chain.as_mut(), "123", MessageType::Datagram));
let mut read_buf = [MaybeUninit::<u8>::uninit(); 10];
let mut actual = 0;
// Datagram peeks should not return more than a single message.
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
10,
MessageType::Datagram,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"abc");
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
3,
MessageType::Datagram,
ReadType::Read,
&mut actual,
));
expect_true!(bytes == b"abc");
let bytes = unwrap_ok!(read_helper(
&mut chain,
&mut read_buf,
10,
MessageType::Datagram,
ReadType::Peek,
&mut actual,
));
expect_true!(bytes == b"123");
}
/// Tests multi-buffer datagram partial read discarding trailing continuation buffers.
#[test]
fn test_datagram_read_multibuffer_discard() {
const LARGE_PAYLOAD: usize = MBuf::PAYLOAD_SIZE * 2 + 50;
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let (_mem_in, src) = make_user_in_byte(LARGE_PAYLOAD, b'Z').unwrap();
let (res, written) = chain.as_mut().write_datagram(src, LARGE_PAYLOAD);
expect_ok!(res);
expect_eq!(written, LARGE_PAYLOAD);
expect_eq!(chain.stream_size(), LARGE_PAYLOAD);
expect_eq!(chain.datagram_size(), LARGE_PAYLOAD);
// Read only enough to span into the second buffer, leaving the third buffer untouched.
const READ_LEN: usize = MBuf::PAYLOAD_SIZE + 20;
let (mem_out, dst) = make_user_out(READ_LEN).unwrap();
let (res_r, actual) = chain.as_mut().read_datagram(dst, READ_LEN);
expect_ok!(res_r);
expect_eq!(actual, READ_LEN);
expect_true!(verify_user_mem(&mem_out, READ_LEN, |_| b'Z'));
// All remaining bytes (including continuation buffer 3) should have been discarded.
expect_true!(chain.is_empty());
expect_eq!(chain.stream_size(), 0);
expect_eq!(chain.datagram_size(), 0);
}
/// Tests interleaved partial stream reads across MBuf page boundaries.
#[test]
fn test_stream_read_interleaved_chunks() {
const TOTAL_BYTES: usize = MBuf::PAYLOAD_SIZE * 3;
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
let (_mem_in, src) = make_user_in_pattern(TOTAL_BYTES, |i| (i % 251) as u8).unwrap();
let (res, written) = chain.as_mut().write_stream(src, TOTAL_BYTES);
expect_ok!(res);
expect_eq!(written, TOTAL_BYTES);
expect_eq!(chain.stream_size(), TOTAL_BYTES);
let mut total_read = 0usize;
let chunk_sizes = [100, MBuf::PAYLOAD_SIZE - 50, 500, MBuf::PAYLOAD_SIZE, 1000];
for &chunk in &chunk_sizes {
if total_read >= TOTAL_BYTES {
break;
}
let to_read = core::cmp::min(chunk, TOTAL_BYTES - total_read);
let (mem_out, dst) = make_user_out(to_read).unwrap();
let (res_r, actual) = chain.as_mut().read_stream(dst, to_read);
expect_ok!(res_r);
expect_eq!(actual, to_read);
expect_true!(verify_user_mem(&mem_out, actual, |i| { ((total_read + i) % 251) as u8 }));
total_read += actual;
expect_eq!(chain.stream_size(), TOTAL_BYTES - total_read);
}
// Read the remaining bytes.
if total_read < TOTAL_BYTES {
let rem = TOTAL_BYTES - total_read;
let (mem_rem, rem_dst) = make_user_out(rem).unwrap();
let (res_rem, actual) = chain.as_mut().read_stream(rem_dst, rem);
expect_ok!(res_rem);
expect_eq!(actual, rem);
expect_true!(verify_user_mem(&mem_rem, actual, |i| { ((total_read + i) % 251) as u8 }));
}
expect_true!(chain.is_empty());
expect_eq!(chain.stream_size(), 0);
}
/// Tests helper calculations for MBuf sizing and buffer requirements.
#[test]
fn test_mbuf_calculations() {
expect_eq!(MBuf::num_buffers_for_payload(0), 0);
expect_eq!(MBuf::num_buffers_for_payload(1), 1);
expect_eq!(MBuf::num_buffers_for_payload(MBuf::PAYLOAD_SIZE), 1);
expect_eq!(MBuf::num_buffers_for_payload(MBuf::PAYLOAD_SIZE + 1), 2);
expect_eq!(MBuf::num_buffers_for_payload(2 * MBuf::PAYLOAD_SIZE), 2);
expect_eq!(MBuf::num_buffers_for_payload(2 * MBuf::PAYLOAD_SIZE + 1), 3);
expect_true!(MBufChain::MAX_SIZE >= 256 * 1024);
expect_eq!(MBufChain::MAX_SIZE % MBuf::PAYLOAD_SIZE, 0);
}
/// Tests datagram write failure when exceeding chain capacity.
#[test]
fn test_datagram_write_should_wait_boundary() {
stack_pin_init!(let chain_pin = MBufChain::new());
let mut chain = chain_pin.as_mut();
// Fill close to maximum capacity.
const FILL_SIZE: usize = MBufChain::MAX_SIZE - 100;
let (_mem, src) = make_user_in_byte(FILL_SIZE, b'K').unwrap();
let (res, written) = chain.as_mut().write_datagram(src, FILL_SIZE);
expect_ok!(res);
expect_eq!(written, FILL_SIZE);
expect_eq!(chain.stream_size(), FILL_SIZE);
// Attempting to write a 101-byte datagram exceeds MAX_SIZE.
let (_overflow_mem, overflow_src) = make_user_in_byte(101, b'V').unwrap();
let (res_overflow, overflow_written) = chain.as_mut().write_datagram(overflow_src, 101);
expect_true!(res_overflow == Err(Status::SHOULD_WAIT));
expect_eq!(overflow_written, 0);
expect_eq!(chain.stream_size(), FILL_SIZE);
}
/// Tests that MBuf allocation, insertion, removal, and freeing preserves node state.
#[test]
fn test_mbuf_alloc_free_node_lifecycle() {
stack_pin_init!(let bufs = fbl::DoublyLinkedList::<*mut MBuf>::new());
let bufs = unsafe { bufs.get_unchecked_mut() };
for _ in 0..10 {
let buf_ptr = MBuf::new().expect("alloc mbuf");
unsafe {
bufs.push_back_raw(buf_ptr);
}
}
while let Some(buf) = bufs.pop_front() {
unsafe {
drop_in_place(buf);
}
}
expect_true!(bufs.is_empty());
}
/// Tests allocating and freeing MBufs using alloc_mbufs and free_mbufs helpers.
#[test]
fn test_alloc_free_mbufs() {
stack_pin_init!(let bufs = fbl::DoublyLinkedList::<*mut MBuf>::new());
let bufs = unsafe { bufs.get_unchecked_mut() };
expect_ok!(alloc_mbufs(5, bufs));
expect_false!(bufs.is_empty());
free_mbufs(bufs);
expect_true!(bufs.is_empty());
}
}