blob: 1036d444ee9def1c771d9cb89e9ed39ef8785a16 [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 super::dispatcher::{
DispatcherOps, PeerHolder, PeerHolderMuClass, PeeredState, PeeredStateMuGuard,
impl_peered_dispatcher_facade_with_state,
};
use super::handle::KernelHandle;
use super::mbuf::MBufChain;
use super::socket_dispatcher_ffi::cpp_socket_dispatcher_create;
use crate::counters::define_kcounter;
use crate::user_copy::{UserInPtr, UserOutPtr};
use core::convert::Infallible;
use core::pin::Pin;
use debug::ltrace_entry;
use fbl::{Canary, RefPtr};
use ksync::{KMutex, PhantomMutex, RawCriticalMutex, guarded};
use object_constants_rs::{
kSocketDispatcherStateAlign, kSocketDispatcherStateOffset, kSocketDispatcherStateSize,
};
use pin_init::{PinInit, pin_data, pin_init, pinned_drop};
use zx_status::Status;
use zx_types::{
ZX_OBJ_TYPE_SOCKET, ZX_RIGHT_DUPLICATE, ZX_RIGHT_GET_PROPERTY, ZX_RIGHT_INSPECT,
ZX_RIGHT_MANAGE_SOCKET, ZX_RIGHT_READ, ZX_RIGHT_SET_PROPERTY, ZX_RIGHT_SIGNAL,
ZX_RIGHT_SIGNAL_PEER, ZX_RIGHT_TRANSFER, ZX_RIGHT_WAIT, ZX_RIGHT_WRITE, ZX_SOCKET_DATAGRAM,
ZX_SOCKET_DISPOSITION_WRITE_DISABLED, ZX_SOCKET_DISPOSITION_WRITE_ENABLED,
ZX_SOCKET_PEER_CLOSED, ZX_SOCKET_PEER_WRITE_DISABLED, ZX_SOCKET_READ_THRESHOLD,
ZX_SOCKET_READABLE, ZX_SOCKET_WRITABLE, ZX_SOCKET_WRITE_DISABLED, ZX_SOCKET_WRITE_THRESHOLD,
ZX_USER_SIGNAL_ALL, zx_info_socket_t, zx_rights_t, zx_signals_t,
};
pub const DEFAULT_RIGHTS: zx_rights_t = ZX_RIGHT_TRANSFER
| ZX_RIGHT_DUPLICATE
| ZX_RIGHT_READ
| ZX_RIGHT_WRITE
| ZX_RIGHT_WAIT
| ZX_RIGHT_INSPECT
| ZX_RIGHT_GET_PROPERTY
| ZX_RIGHT_SET_PROPERTY
| ZX_RIGHT_SIGNAL
| ZX_RIGHT_SIGNAL_PEER
| ZX_RIGHT_MANAGE_SOCKET;
pub const ALLOWED_SIGNALS: u32 = ZX_USER_SIGNAL_ALL;
pub const ZX_SOCKET_CREATE_MASK: u32 = ZX_SOCKET_DATAGRAM;
const LOCAL_TRACE: u32 = 0;
zr::static_assert_size_and_align!(
SocketDispatcherState,
kSocketDispatcherStateSize,
kSocketDispatcherStateAlign,
);
define_kcounter!(DISPATCHER_SOCKET_CREATE_COUNT, "dispatcher.socket.create", Sum);
define_kcounter!(DISPATCHER_SOCKET_DESTROY_COUNT, "dispatcher.socket.destroy", Sum);
/// Represents the disposition of a socket endpoint's write status.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Disposition {
/// No change in disposition.
None = 0,
/// Disables writes on the socket endpoint.
WriteDisabled = ZX_SOCKET_DISPOSITION_WRITE_DISABLED,
/// Enables writes on the socket endpoint.
WriteEnabled = ZX_SOCKET_DISPOSITION_WRITE_ENABLED,
}
impl TryFrom<u32> for Disposition {
type Error = Status;
/// Converts a raw uint32_t disposition into a `Disposition` enum variant.
fn try_from(disposition: u32) -> Result<Self, Status> {
match disposition {
0 => Ok(Self::None),
ZX_SOCKET_DISPOSITION_WRITE_DISABLED => Ok(Self::WriteDisabled),
ZX_SOCKET_DISPOSITION_WRITE_ENABLED => Ok(Self::WriteEnabled),
_ => Err(Status::INVALID_ARGS),
}
}
}
/// Signal masks generated by a [`Disposition`].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct SignalMasks {
/// Signals to clear on the target endpoint.
clear: zx_signals_t,
/// Signals to set on the target endpoint.
set: zx_signals_t,
/// Signals to clear on the peer endpoint.
peer_clear: zx_signals_t,
/// Signals to set on the peer endpoint.
peer_set: zx_signals_t,
}
impl Disposition {
/// Returns the signal masks corresponding to this disposition.
fn signal_masks(self) -> SignalMasks {
match self {
Self::WriteDisabled => SignalMasks {
clear: ZX_SOCKET_WRITABLE,
set: ZX_SOCKET_WRITE_DISABLED,
peer_clear: 0,
peer_set: ZX_SOCKET_PEER_WRITE_DISABLED,
},
Self::WriteEnabled => SignalMasks {
clear: ZX_SOCKET_WRITE_DISABLED,
set: ZX_SOCKET_WRITABLE,
peer_clear: ZX_SOCKET_PEER_WRITE_DISABLED,
peer_set: 0,
},
Self::None => SignalMasks::default(),
}
}
}
/// Specifies the type of read operation (consume or peek).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReadType {
/// Consumes read data from the socket.
Consume,
/// Peeks data without consuming it from the socket buffer.
Peek,
}
/// Synchronization and state layout for `SocketDispatcher`.
///
/// The shared `mu` lock protects all state members below.
#[guarded]
#[pin_data(PinnedDrop)]
#[repr(C)]
pub struct SocketDispatcherState {
canary: Canary<{ fbl::magic(b"SOCK") }>,
#[pin]
pub peered: PeeredState<SocketDispatcher>,
flags: u32,
#[guarded_by(mu)]
read_disabled: bool,
#[guarded_by(mu)]
read_threshold: usize,
#[guarded_by(mu)]
write_threshold: usize,
#[guarded_by(mu)]
#[pin]
data: MBufChain,
#[mutex(PeerHolderMuClass<SocketDispatcher>)]
pub mu: KMutex<PhantomMutex>,
}
impl SocketDispatcherState {
/// Initializes `SocketDispatcherState` with peer holder and creation flags.
pub fn init(
holder: RefPtr<PeerHolder<SocketDispatcher>>,
flags: u32,
) -> impl PinInit<Self, Infallible> {
DISPATCHER_SOCKET_CREATE_COUNT.add(1);
pin_init!(Self {
canary: Canary::new(),
peered <- PeeredState::init(holder),
flags,
read_disabled: false.into(),
read_threshold: 0.into(),
write_threshold: 0.into(),
data <- ksync::kcell_init(MBufChain::new()),
mu: KMutex::new(PhantomMutex),
})
}
}
#[pinned_drop]
impl PinnedDrop for SocketDispatcherState {
fn drop(self: Pin<&mut Self>) {
DISPATCHER_SOCKET_DESTROY_COUNT.add(1);
}
}
impl_peered_dispatcher_facade_with_state!(
pub struct SocketDispatcher,
SocketDispatcherState,
ZX_OBJ_TYPE_SOCKET,
kSocketDispatcherStateOffset,
allowed_signals: ALLOWED_SIGNALS,
);
impl SocketDispatcher {
/// Returns default rights for a SocketDispatcher.
pub fn default_rights() -> zx_rights_t {
DEFAULT_RIGHTS
}
/// Creates a new SocketDispatcher pair and returns their kernel handles and rights.
pub fn create(
flags: u32,
) -> Result<(KernelHandle<Self>, KernelHandle<Self>, zx_rights_t), Status> {
ltrace_entry!();
if (flags & !ZX_SOCKET_CREATE_MASK) != 0 {
return Err(Status::INVALID_ARGS);
}
let holder0 = PeerHolder::<Self>::create().map_err(|_| Status::NO_MEMORY)?;
let holder1 = holder0.clone();
let create_single =
|holder: RefPtr<PeerHolder<Self>>| -> Result<KernelHandle<Self>, Status> {
// SAFETY: `RefPtr::into_raw(holder)` converts the valid reference into a raw
// pointer, and `cpp_socket_dispatcher_create` initializes `handle` on success.
unsafe {
KernelHandle::create(|out| {
cpp_socket_dispatcher_create(RefPtr::into_raw(holder) as *mut _, flags, out)
})
}
};
let handle0 = create_single(holder0)?;
let handle1 = create_single(holder1)?;
handle0.dispatcher().init_peer(handle1.dispatcher().clone());
handle1.dispatcher().init_peer(handle0.dispatcher().clone());
Ok((handle0, handle1, DEFAULT_RIGHTS))
}
fn on_zero_handles_locked(&self, _token: &ksync::LockToken<'_, PeerHolderMuClass<Self>>) {
self.state().canary.assert();
}
fn on_peer_zero_handles_locked(&self, token: &ksync::LockToken<'_, PeerHolderMuClass<Self>>) {
self.state().canary.assert();
self.update_state_locked(token, ZX_SOCKET_WRITABLE, ZX_SOCKET_PEER_CLOSED);
}
fn update_read_status(
&self,
token: &mut ksync::LockToken<'_, PeerHolderMuClass<SocketDispatcher>>,
disposition_peer: Disposition,
) {
match disposition_peer {
Disposition::WriteDisabled => {
*self.state().guard_mu_mut(token).read_disabled_mut() = true;
}
Disposition::WriteEnabled => {
*self.state().guard_mu_mut(token).read_disabled_mut() = false;
}
Disposition::None => {}
}
}
fn is_disposition_state_valid(
&self,
token: &mut ksync::LockToken<'_, PeerHolderMuClass<SocketDispatcher>>,
disposition_peer: Disposition,
) -> bool {
// All the data written by an endpoint must be read by the other endpoint before writes can
// be re-enabled, as per /docs/reference/syscalls/socket_set_disposition.md.
!(disposition_peer == Disposition::WriteEnabled
&& !self.state().guard_mu(token).data().is_empty()
&& (self.signals_state_locked(token) & ZX_SOCKET_PEER_WRITE_DISABLED) != 0)
}
/// Sets the disposition of socket endpoints (write enabled / write disabled).
pub fn set_disposition(
&self,
disposition: Disposition,
disposition_peer: Disposition,
) -> Result<(), Status> {
self.state().canary.assert();
ltrace_entry!();
if disposition == Disposition::None && disposition_peer == Disposition::None {
// Nothing to do, return early.
return Ok(());
}
let self_masks = disposition.signal_masks();
ksync::lock!(let mut guard = self.state().peered.lock());
let peer = guard.peer().as_ref().cloned();
let token = guard.as_mut().token_mut();
if !self.is_disposition_state_valid(token, disposition_peer) {
return Err(Status::BAD_STATE);
}
let mut clear_mask = self_masks.clear;
let mut set_mask = self_masks.set;
if let Some(peer) = peer.as_ref() {
if !peer.is_disposition_state_valid(token, disposition) {
return Err(Status::BAD_STATE);
}
let peer_masks = disposition_peer.signal_masks();
let clear_mask_peer = self_masks.peer_clear | peer_masks.clear;
let set_mask_peer = self_masks.peer_set | peer_masks.set;
clear_mask |= peer_masks.peer_clear;
set_mask |= peer_masks.peer_set;
peer.update_read_status(token, disposition);
peer.update_state_locked(token, clear_mask_peer, set_mask_peer);
}
self.update_read_status(token, disposition_peer);
self.update_state_locked(token, clear_mask, set_mask);
Ok(())
}
/// Writes data from the buffer pointer into the peer socket endpoint.
pub fn write(&self, src: UserInPtr<core::ffi::c_char>, len: usize) -> Result<usize, Status> {
self.state().canary.assert();
ltrace_entry!();
ksync::lock!(let mut guard = self.state().peered.lock());
let peer = guard.peer().clone().ok_or(Status::PEER_CLOSED)?;
if (self.signals_state_locked(guard.token()) & ZX_SOCKET_WRITE_DISABLED) != 0 {
return Err(Status::BAD_STATE);
}
if len == 0 {
return Ok(0);
}
if u32::try_from(len).is_err() {
return Err(Status::INVALID_ARGS);
}
peer.write_self_locked(self, guard.as_mut(), src, len)
}
fn write_self_locked(
&self,
peer: &Self,
mut guard: Pin<&mut PeeredStateMuGuard<'_, SocketDispatcher, RawCriticalMutex>>,
src: UserInPtr<core::ffi::c_char>,
len: usize,
) -> Result<usize, Status> {
self.state().canary.assert();
let was_empty = {
let state = self.state().guard_mu(guard.token());
if state.data().is_full() {
return Err(Status::SHOULD_WAIT);
}
state.data().is_empty()
};
// TODO(https://fxbug.dev/42182048): Perform user copying while holding the dispatcher lock
// is generally not allowed, but is exempted here while a fix for sockets is developed.
// Performing the MBufChain operations (which do the actual user copy) with tracking
// disabled will allow the user copy to go through, with side effect of reducing the
// effectiveness of any other lockdep detections that might involve this lock for the
// duration of the operation.
let (status, written) = guard.as_mut().call_untracked(|token| {
let is_datagram = (self.state().flags & ZX_SOCKET_DATAGRAM) != 0;
let mut state = self.state().guard_mu_mut(token);
if is_datagram {
state.data_mut().write_datagram(src, len)
} else {
state.data_mut().write_stream(src, len)
}
});
// Regardless of the status, data may have been added, and so we need to update the signals.
let mut peer_clear: zx_signals_t = 0;
let state = self.state().guard_mu(guard.token());
if written > 0 {
let mut set: zx_signals_t = 0;
if was_empty {
set |= ZX_SOCKET_READABLE;
}
// Assert signal if we go above the read threshold
let read_threshold = *state.read_threshold();
let size = state.data().stream_size();
if read_threshold > 0 && size >= read_threshold {
set |= ZX_SOCKET_READ_THRESHOLD;
}
if set != 0 {
self.update_state_locked(guard.token(), 0, set);
}
let peer_write_threshold = *peer.state().guard_mu(guard.token()).write_threshold();
let free_space = MBufChain::MAX_SIZE - size;
// If free space falls below threshold, de-signal
if peer_write_threshold > 0 && free_space < peer_write_threshold {
peer_clear |= ZX_SOCKET_WRITE_THRESHOLD;
}
}
if state.data().is_full() {
peer_clear |= ZX_SOCKET_WRITABLE;
}
if peer_clear != 0 {
peer.update_state_locked(guard.token(), peer_clear, 0);
}
status?;
Ok(written)
}
/// Reads data from this socket endpoint into the destination buffer pointer.
pub fn read(
&self,
read_type: ReadType,
dst: UserOutPtr<core::ffi::c_char>,
len: usize,
) -> Result<usize, Status> {
self.state().canary.assert();
ltrace_entry!();
ksync::lock!(let mut guard = self.state().peered.lock());
if u32::try_from(len).is_err() {
return Err(Status::INVALID_ARGS);
}
let was_full = {
let state = self.state().guard_mu(guard.token());
if state.data().is_empty() {
if guard.peer().is_none() {
return Err(Status::PEER_CLOSED);
}
// If reading is disabled on our end and we're empty, we'll never become readable
// again. Return a different error to let the caller know.
if *state.read_disabled() {
return Err(Status::BAD_STATE);
}
return Err(Status::SHOULD_WAIT);
}
state.data().is_full()
};
// TODO(https://fxbug.dev/42182048): See comment in write_self_locked on why we use
// call_untracked.
let (status, actual) = guard.as_mut().call_untracked(|token| {
let is_datagram = (self.state().flags & ZX_SOCKET_DATAGRAM) != 0;
let mut state = self.state().guard_mu_mut(token);
match (read_type, is_datagram) {
(ReadType::Peek, true) => state.data().peek_datagram(dst, len),
(ReadType::Peek, false) => state.data().peek_stream(dst, len),
(ReadType::Consume, true) => state.data_mut().read_datagram(dst, len),
(ReadType::Consume, false) => state.data_mut().read_stream(dst, len),
}
});
if read_type == ReadType::Peek {
status?;
return Ok(actual);
}
// Regardless of the status, data may have been consumed, and so we need to update the
// signals.
let mut clear: zx_signals_t = 0;
let state = self.state().guard_mu(guard.token());
// Deassert signal if we fell below the read threshold
let read_threshold = *state.read_threshold();
let size = state.data().stream_size();
if read_threshold > 0 && size < read_threshold {
clear |= ZX_SOCKET_READ_THRESHOLD;
}
if state.data().is_empty() {
clear |= ZX_SOCKET_READABLE;
}
if clear != 0 {
self.update_state_locked(guard.token(), clear, 0);
}
if let Some(peer) = guard.peer().as_ref() {
let peer_write_threshold = *peer.state().guard_mu(guard.token()).write_threshold();
let free_space = MBufChain::MAX_SIZE - size;
let mut peer_set: zx_signals_t = 0;
// Assert (write threshold) signal if space available is above threshold.
if peer_write_threshold > 0 && free_space >= peer_write_threshold {
peer_set |= ZX_SOCKET_WRITE_THRESHOLD;
}
if was_full && actual > 0 {
peer_set |= ZX_SOCKET_WRITABLE;
}
if peer_set != 0 {
peer.update_state_locked(guard.token(), 0, peer_set);
}
}
status?;
Ok(actual)
}
/// Returns socket buffer info for this endpoint and its peer.
pub fn get_info(&self) -> zx_info_socket_t {
self.state().canary.assert();
ksync::lock!(let mut guard = self.state().peered.lock());
let peer = guard.peer().as_ref().cloned();
let token = guard.as_mut().token_mut();
let is_datagram = (self.state().flags & ZX_SOCKET_DATAGRAM) != 0;
let guard_mu = self.state().guard_mu(token);
let data = guard_mu.data();
let mut info = zx_info_socket_t {
options: self.state().flags,
rx_buf_max: MBufChain::MAX_SIZE,
rx_buf_size: data.stream_size(),
rx_buf_available: if is_datagram { data.datagram_size() } else { data.stream_size() },
tx_buf_max: 0,
tx_buf_size: 0,
..Default::default()
};
if let Some(peer) = peer {
// Peer shares the same lock.
let peer_guard_mu = peer.state().guard_mu(token);
info.tx_buf_max = MBufChain::MAX_SIZE;
info.tx_buf_size = peer_guard_mu.data().stream_size();
}
info
}
/// Returns the read threshold value.
pub fn get_read_threshold(&self) -> usize {
self.state().canary.assert();
ksync::lock!(let guard = self.state().peered.lock());
*self.state().guard_mu(guard.token()).read_threshold()
}
/// Returns the write threshold value.
pub fn get_write_threshold(&self) -> usize {
self.state().canary.assert();
ksync::lock!(let guard = self.state().peered.lock());
*self.state().guard_mu(guard.token()).write_threshold()
}
/// Sets the read threshold value.
pub fn set_read_threshold(&self, value: usize) -> Result<(), Status> {
self.state().canary.assert();
ksync::lock!(let mut guard = self.state().peered.lock());
if value > MBufChain::MAX_SIZE {
return Err(Status::INVALID_ARGS);
}
let token = guard.as_mut().token_mut();
let mut state = self.state().guard_mu_mut(token);
let size = state.data().stream_size();
*state.read_threshold_mut() = value;
// Setting 0 disables thresholding. Deassert signal unconditionally.
// Assert signal if we have queued data above the read threshold.
// De-assert signal if we upped threshold and queued data drops below.
let (clear, set) = if value > 0 && size >= value {
(0, ZX_SOCKET_READ_THRESHOLD)
} else {
(ZX_SOCKET_READ_THRESHOLD, 0)
};
self.update_state_locked(token, clear, set);
Ok(())
}
/// Sets the write threshold value.
pub fn set_write_threshold(&self, value: usize) -> Result<(), Status> {
self.state().canary.assert();
ksync::lock!(let mut guard = self.state().peered.lock());
if value > MBufChain::MAX_SIZE {
return Err(Status::INVALID_ARGS);
}
let peer_available = {
let peer = guard.peer().as_ref().ok_or(Status::PEER_CLOSED)?;
MBufChain::MAX_SIZE - peer.state().guard_mu(guard.token()).data().stream_size()
};
let token = guard.as_mut().token_mut();
*self.state().guard_mu_mut(token).write_threshold_mut() = value;
// Setting 0 disables thresholding. Deassert signal unconditionally.
// Assert signal if we have available space above the write threshold.
// De-assert signal if we upped threshold and available space drops below.
let (clear, set) = if value > 0 && peer_available >= value {
(0, ZX_SOCKET_WRITE_THRESHOLD)
} else {
(ZX_SOCKET_WRITE_THRESHOLD, 0)
};
self.update_state_locked(token, clear, set);
Ok(())
}
}
/// In-tree kernel unit tests for `SocketDispatcher`.
#[cfg(ktest)]
#[unittest::suite(name = "socket_dispatcher_rust_tests")]
mod tests {
use super::{DEFAULT_RIGHTS, Disposition, ReadType, SocketDispatcher};
use crate::object::dispatcher::DispatcherOps;
use crate::user_copy::{UserInPtr, UserOutPtr};
use crate::user_memory::UserMemory;
use core::ffi::c_char;
use core::mem::MaybeUninit;
use kalloc::Box;
use unittest::{expect_eq, expect_ok, expect_true};
use zx_status::Status;
use zx_types::{
ZX_SOCKET_DATAGRAM, ZX_SOCKET_DISPOSITION_WRITE_DISABLED,
ZX_SOCKET_DISPOSITION_WRITE_ENABLED, ZX_SOCKET_PEER_CLOSED, ZX_SOCKET_PEER_WRITE_DISABLED,
ZX_SOCKET_READ_THRESHOLD, ZX_SOCKET_READABLE, ZX_SOCKET_WRITABLE, ZX_SOCKET_WRITE_DISABLED,
ZX_SOCKET_WRITE_THRESHOLD,
};
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(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 read_user_mem(mem: &UserMemory, size: usize) -> Option<Box<[u8]>> {
let mut buf = Box::try_new_uninit_slice(size).ok()?;
mem.vmo_read(&mut buf, 0).ok()?;
// SAFETY: We initialize the buffer immediately by reading `size` bytes from the VMO.
Some(unsafe { buf.assume_init() })
}
/// Allocate/destroy many sockets. Ad hoc resource leak check.
#[test]
fn test_create_destroy_many_sockets() {
const MANY: usize = 100_000;
for flags in [0, ZX_SOCKET_DATAGRAM] {
for _ in 0..MANY {
let (h0, h1, _rights) =
SocketDispatcher::create(flags).expect("failed to create socket");
drop(h0);
drop(h1);
}
}
}
/// Stream socket write/read test matching C++ TestCreateWriteReadClose.
#[test]
fn test_create_write_read_close() {
const SIZE: usize = 3357;
let (h0, h1, rights) = SocketDispatcher::create(0).expect("failed to create stream socket");
expect_eq!(rights, DEFAULT_RIGHTS);
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
let info0 = d0.get_info();
expect_eq!(info0.rx_buf_available, 0); // No bytes written yet.
// Write a test pattern, read it back one byte at a time.
let (_in_mem, in_ptr) = make_user_in_pattern(SIZE, |i| (i & 0xff) as u8).unwrap();
let written = d0.write(in_ptr, SIZE).expect("failed to write");
expect_eq!(written, SIZE);
// Expect to not be able to read on the dispatcher side you just wrote to
let info0 = d0.get_info();
expect_eq!(info0.rx_buf_available, 0);
// Expect to be able to read from the paired dispatcher.
let info1 = d1.get_info();
expect_eq!(info1.rx_buf_available, SIZE);
// Read out data from the peer byte-at-a-time; this is a stream socket, allowing that.
let (out_mem, out_ptr) = make_user_out(1).unwrap();
let mut read_buffer = [0u8; SIZE];
for (i, slot) in read_buffer.iter_mut().enumerate() {
let bytes_read = d1.read(ReadType::Consume, out_ptr, 1).expect("failed to read");
expect_eq!(bytes_read, 1);
let mut byte_buf = [MaybeUninit::<u8>::uninit(); 1];
let read = out_mem.vmo_read(&mut byte_buf, 0).expect("vmo read");
*slot = read[0];
// Expect consuming 1-byte reads to reduce rx_buf_available.
let info1 = d1.get_info();
expect_eq!(info1.rx_buf_available, SIZE - (i + 1));
}
for (i, &val) in read_buffer.iter().enumerate() {
expect_eq!(val, (i & 0xff) as u8);
}
// Test that shutting down a socket for writes still allows reads from the paired
// dispatcher.
let byte = [0u8; 1];
let (_in_mem, in_ptr) = make_user_in(&byte).expect("make user in");
let written = d0.write(in_ptr, 1).expect("failed to write");
expect_eq!(written, 1);
let res = d0.set_disposition(Disposition::WriteDisabled, Disposition::None);
expect_ok!(res);
let res = d0.write(in_ptr, 1);
// |written| is not updated if Write() fails.
expect_true!(res == Err(Status::BAD_STATE));
let info1 = d1.get_info();
// Not 2 - the second write must have failed.
expect_eq!(info1.rx_buf_available, 1);
}
/// Proves that Disposition match is exhaustive in Rust.
#[test]
fn test_disposition_switch_must_be_exhaustive() {
let check_exhaustive = |disposition: Disposition| match disposition {
Disposition::None => true,
Disposition::WriteDisabled => true,
Disposition::WriteEnabled => true,
// This proves that exhaustive checking is done by the match - otherwise we would get
// a compilation error because we would not return a value in all code paths.
};
expect_true!(check_exhaustive(Disposition::None));
expect_true!(check_exhaustive(Disposition::WriteDisabled));
expect_true!(check_exhaustive(Disposition::WriteEnabled));
}
/// Tests Disposition::try_from conversions and error validation.
#[test]
fn test_disposition_try_from() {
expect_true!(Disposition::try_from(0) == Ok(Disposition::None));
expect_true!(
Disposition::try_from(ZX_SOCKET_DISPOSITION_WRITE_DISABLED)
== Ok(Disposition::WriteDisabled)
);
expect_true!(
Disposition::try_from(ZX_SOCKET_DISPOSITION_WRITE_ENABLED)
== Ok(Disposition::WriteEnabled)
);
expect_true!(Disposition::try_from(0xffff) == Err(Status::INVALID_ARGS));
}
/// Datagram socket write, peek, truncate on short read, and full read.
#[test]
fn test_datagram_write_read_peek_truncate() {
let (h0, h1, rights) =
SocketDispatcher::create(ZX_SOCKET_DATAGRAM).expect("failed to create datagram socket");
expect_eq!(rights, DEFAULT_RIGHTS);
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
let msg1 = b"12345";
let msg2 = b"abcdef";
let (_m1_mem, m1_ptr) = make_user_in(msg1).unwrap();
let (_m2_mem, m2_ptr) = make_user_in(msg2).unwrap();
let (_m3_mem, m3_ptr) = make_user_in_pattern(100, |i| (i & 0xff) as u8).unwrap();
// Write three distinct datagrams into d1 via d0.
let w1 = d0.write(m1_ptr, msg1.len()).expect("write datagram 1");
expect_eq!(w1, msg1.len());
let w2 = d0.write(m2_ptr, msg2.len()).expect("write datagram 2");
expect_eq!(w2, msg2.len());
let w3 = d0.write(m3_ptr, 100).expect("write datagram 3");
expect_eq!(w3, 100);
// Available bytes for datagram sockets should report the head datagram size.
let info = d1.get_info();
expect_eq!(info.rx_buf_available, msg1.len());
expect_eq!(info.rx_buf_size, msg1.len() + msg2.len() + 100);
// Short peek on the first datagram: only peek part of msg1.
let (peek_mem, peek_ptr) = make_user_out(4).unwrap();
let npeek = d1.read(ReadType::Peek, peek_ptr, 4).expect("peek short");
expect_eq!(npeek, 4);
let b = read_user_mem(&peek_mem, 4).unwrap();
expect_true!(*b == msg1[..4]);
// Buffer info must remain unchanged after peek.
let info = d1.get_info();
expect_eq!(info.rx_buf_available, msg1.len());
expect_eq!(info.rx_buf_size, msg1.len() + msg2.len() + 100);
// Full peek with larger buffer should only return the first datagram.
let (peek_large_mem, peek_large_ptr) = make_user_out(16).unwrap();
let npeek_large = d1.read(ReadType::Peek, peek_large_ptr, 16).expect("peek large");
expect_eq!(npeek_large, msg1.len());
let b = read_user_mem(&peek_large_mem, msg1.len()).unwrap();
expect_true!(*b == *msg1);
// Short consuming read should truncate and discard the rest of msg1.
let (read_trunc_mem, read_trunc_ptr) = make_user_out(3).unwrap();
let nread_trunc = d1.read(ReadType::Consume, read_trunc_ptr, 3).expect("read truncate");
expect_eq!(nread_trunc, 3);
let b = read_user_mem(&read_trunc_mem, 3).unwrap();
expect_true!(*b == msg1[..3]);
// Next available datagram is msg2.
let info = d1.get_info();
expect_eq!(info.rx_buf_available, msg2.len());
expect_eq!(info.rx_buf_size, msg2.len() + 100);
// Consume read msg2 completely.
let (read_msg2_mem, read_msg2_ptr) = make_user_out(6).unwrap();
let nread2 = d1.read(ReadType::Consume, read_msg2_ptr, 6).expect("read msg2");
expect_eq!(nread2, msg2.len());
let b = read_user_mem(&read_msg2_mem, 6).unwrap();
expect_true!(&*b == msg2);
// Consume read msg3 with an oversized buffer.
let (read_msg3_mem, read_msg3_ptr) = make_user_out(128).unwrap();
let nread3 = d1.read(ReadType::Consume, read_msg3_ptr, 128).expect("read msg3");
expect_eq!(nread3, 100);
let b = read_user_mem(&read_msg3_mem, 100).unwrap();
for (i, &val) in b.iter().enumerate() {
expect_eq!(val, (i & 0xff) as u8);
}
// Socket is now empty; reading should return SHOULD_WAIT.
let (_empty_mem, empty_ptr) = make_user_out(1).unwrap();
let res = d1.read(ReadType::Consume, empty_ptr, 1);
expect_true!(res == Err(Status::SHOULD_WAIT));
let info = d1.get_info();
expect_eq!(info.rx_buf_available, 0);
expect_eq!(info.rx_buf_size, 0);
}
/// Tests read and write threshold setting, retrieval, and signal transitions.
#[test]
fn test_read_write_thresholds_and_signals() {
let (h0, h1, _) = SocketDispatcher::create(0).expect("failed to create stream socket");
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
// Initial threshold values default to 0 (disabled).
expect_eq!(d0.get_read_threshold(), 0);
expect_eq!(d0.get_write_threshold(), 0);
let info0 = d0.get_info();
// Setting threshold beyond capacity must fail with INVALID_ARGS.
expect_true!(d0.set_read_threshold(info0.rx_buf_max + 1) == Err(Status::INVALID_ARGS));
expect_true!(d0.set_write_threshold(info0.rx_buf_max + 1) == Err(Status::INVALID_ARGS));
// Setting a valid read threshold of 50 bytes.
expect_ok!(d0.set_read_threshold(50));
expect_eq!(d0.get_read_threshold(), 50);
// Signal should not be active since 0 < 50.
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_eq!(sigs0 & ZX_SOCKET_READ_THRESHOLD, 0);
}
// Write 30 bytes into d0 from d1 (below read threshold).
let (_b30_mem, b30_ptr) = make_user_in(&[0xaa_u8; 30]).unwrap();
let w = d1.write(b30_ptr, 30).expect("write 30");
expect_eq!(w, 30);
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_true!((sigs0 & ZX_SOCKET_READABLE) != 0);
expect_eq!(sigs0 & ZX_SOCKET_READ_THRESHOLD, 0);
}
// Write 25 more bytes (total 55 >= 50 threshold).
let (_b25_mem, b25_ptr) = make_user_in(&[0xbb_u8; 25]).unwrap();
let w = d1.write(b25_ptr, 25).expect("write 25");
expect_eq!(w, 25);
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_true!((sigs0 & ZX_SOCKET_READ_THRESHOLD) != 0);
}
// Read 10 bytes from d0 (leaving 45 bytes, falling below the 50 threshold).
let (_r10_mem, r10_ptr) = make_user_out(10).unwrap();
let nread = d0.read(ReadType::Consume, r10_ptr, 10).expect("read 10");
expect_eq!(nread, 10);
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_eq!(sigs0 & ZX_SOCKET_READ_THRESHOLD, 0);
expect_true!((sigs0 & ZX_SOCKET_READABLE) != 0);
}
// Setting read threshold to 0 disables it and clears signal unconditionally.
expect_ok!(d0.set_read_threshold(0));
expect_eq!(d0.get_read_threshold(), 0);
// Test write threshold on d1 (which tracks available space in d0).
expect_ok!(d1.set_write_threshold(100));
expect_eq!(d1.get_write_threshold(), 100);
{
ksync::lock!(let guard1 = d1.state().peered.lock());
let sigs1 = d1.signals_state_locked(guard1.token());
// Since d0 has ample space (> 100 bytes available), write threshold signal is asserted.
expect_true!((sigs1 & ZX_SOCKET_WRITE_THRESHOLD) != 0);
}
// Fill d0 almost to capacity so available space drops below 100 bytes.
let to_fill = info0.rx_buf_max - 50;
let (_fill_mem, fill_ptr) = make_user_in_pattern(to_fill, |_| 0xcc).unwrap();
let w = d1.write(fill_ptr, to_fill).expect("write fill");
expect_eq!(w, to_fill);
{
ksync::lock!(let guard1 = d1.state().peered.lock());
let sigs1 = d1.signals_state_locked(guard1.token());
// Available space in d0 is now 50 (< 100 write threshold), so signal should be de-asserted.
expect_eq!(sigs1 & ZX_SOCKET_WRITE_THRESHOLD, 0);
expect_true!((sigs1 & ZX_SOCKET_WRITABLE) != 0);
}
// Disabling write threshold by setting it to 0.
expect_ok!(d1.set_write_threshold(0));
expect_eq!(d1.get_write_threshold(), 0);
{
ksync::lock!(let guard1 = d1.state().peered.lock());
let sigs1 = d1.signals_state_locked(guard1.token());
expect_eq!(sigs1 & ZX_SOCKET_WRITE_THRESHOLD, 0);
}
}
/// Tests socket operations and signal handling when the peer endpoint is closed.
#[test]
fn test_closed_peer_operations() {
let (h0, h1, _) = SocketDispatcher::create(0).expect("failed to create stream socket");
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
// Write some data from d0 to d1 before closing d0.
let (_test_mem, test_ptr) = make_user_in(&[42u8; 16]).unwrap();
let written = d0.write(test_ptr, 16).expect("write test data");
expect_eq!(written, 16);
// Close peer endpoint d0.
drop(h0);
// Peer closing asserts ZX_SOCKET_PEER_CLOSED and de-asserts ZX_SOCKET_WRITABLE on d1.
{
ksync::lock!(let guard1 = d1.state().peered.lock());
let sigs1 = d1.signals_state_locked(guard1.token());
expect_true!((sigs1 & ZX_SOCKET_PEER_CLOSED) != 0);
expect_eq!(sigs1 & ZX_SOCKET_WRITABLE, 0);
}
// Writing to d1 when peer is closed must return PEER_CLOSED (even for 0 bytes).
let res_w = d1.write(test_ptr, 16);
expect_true!(res_w == Err(Status::PEER_CLOSED));
let res_w0 = d1.write(test_ptr, 0);
expect_true!(res_w0 == Err(Status::PEER_CLOSED));
// Setting write threshold when peer is closed must return PEER_CLOSED.
let res_wt = d1.set_write_threshold(50);
expect_true!(res_wt == Err(Status::PEER_CLOSED));
// Getting write threshold still succeeds.
expect_eq!(d1.get_write_threshold(), 0);
// Setting read threshold still works on closed peer.
expect_ok!(d1.set_read_threshold(10));
expect_eq!(d1.get_read_threshold(), 10);
// Getting socket info on closed peer returns rx info and zeroed tx capacity.
let info = d1.get_info();
expect_eq!(info.rx_buf_size, 16);
expect_eq!(info.tx_buf_max, 0);
expect_eq!(info.tx_buf_size, 0);
// Buffered data can still be read out completely.
let (r_mem, r_ptr) = make_user_out(16).unwrap();
let nread = d1.read(ReadType::Consume, r_ptr, 16).expect("read remaining data");
expect_eq!(nread, 16);
let b = read_user_mem(&r_mem, 16).unwrap();
expect_true!(*b == [42u8; 16]);
// Once empty and peer is closed, reading returns PEER_CLOSED.
let res_r = d1.read(ReadType::Consume, r_ptr, 16);
expect_true!(res_r == Err(Status::PEER_CLOSED));
// Setting disposition on closed peer: no-op disposition succeeds.
expect_ok!(d1.set_disposition(Disposition::None, Disposition::None));
expect_ok!(d1.set_disposition(Disposition::WriteDisabled, Disposition::None));
}
/// Tests creating socket with invalid flags returns INVALID_ARGS.
#[test]
fn test_invalid_flags_creation() {
let res = SocketDispatcher::create(0xffff_0000);
expect_true!(matches!(res, Err(Status::INVALID_ARGS)));
}
/// Tests filling stream socket to capacity, WRITABLE signal deassertion, and recovery on read.
#[test]
fn test_stream_socket_full_and_writable_signals() {
let (h0, h1, _) = SocketDispatcher::create(0).expect("failed to create stream socket");
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
let info = d0.get_info();
let capacity = info.rx_buf_max;
expect_true!(capacity > 0);
// Fill socket almost completely.
const CHUNK: usize = 32 * 1024;
let (_in_mem, in_ptr) = make_user_in_pattern(CHUNK, |i| (i & 0xff) as u8).unwrap();
let mut total_written = 0usize;
while total_written + CHUNK <= capacity {
let w = d0.write(in_ptr, CHUNK).expect("write chunk");
expect_eq!(w, CHUNK);
total_written += w;
}
// Top off with exact remaining bytes to reach full capacity.
let rem = capacity - total_written;
if rem > 0 {
let (_rem_mem, rem_ptr) = make_user_in_pattern(rem, |_| 0xaa).unwrap();
let w = d0.write(rem_ptr, rem).expect("write remaining");
expect_eq!(w, rem);
total_written += w;
}
expect_eq!(total_written, capacity);
let info1 = d1.get_info();
expect_eq!(info1.rx_buf_size, capacity);
// Socket is now full; writer (d0) should have ZX_SOCKET_WRITABLE deasserted.
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_eq!(sigs0 & ZX_SOCKET_WRITABLE, 0);
}
// Writing when full returns SHOULD_WAIT.
let res_overflow = d0.write(in_ptr, 1);
expect_true!(res_overflow == Err(Status::SHOULD_WAIT));
// Reading even 1 byte from receiver (d1) should immediately re-assert ZX_SOCKET_WRITABLE.
let (_out_mem, out_ptr) = make_user_out(1).unwrap();
let nread = d1.read(ReadType::Consume, out_ptr, 1).expect("read 1 byte");
expect_eq!(nread, 1);
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_true!((sigs0 & ZX_SOCKET_WRITABLE) != 0);
}
// Test short write: attempt to write 100 bytes when only 1 byte is available.
let (_short_mem, short_ptr) = make_user_in_pattern(100, |_| 0xbb).unwrap();
let w_short = d0.write(short_ptr, 100).expect("short write");
expect_eq!(w_short, 1);
// Socket is full again.
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_eq!(sigs0 & ZX_SOCKET_WRITABLE, 0);
}
}
/// Tests disposition state transitions, write-disabled errors, and re-enabling restrictions.
#[test]
fn test_disposition_state_transitions() {
let (h0, h1, _) = SocketDispatcher::create(0).expect("failed to create stream socket");
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
let (_m_mem, m_ptr) = make_user_in(b"hello").unwrap();
let w = d0.write(m_ptr, 5).expect("write initial data");
expect_eq!(w, 5);
// Disable writes on d0.
expect_ok!(d0.set_disposition(Disposition::WriteDisabled, Disposition::None));
// d0 should have ZX_SOCKET_WRITE_DISABLED asserted and ZX_SOCKET_WRITABLE cleared.
// d1 should have ZX_SOCKET_PEER_WRITE_DISABLED asserted.
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_true!((sigs0 & ZX_SOCKET_WRITE_DISABLED) != 0);
expect_eq!(sigs0 & ZX_SOCKET_WRITABLE, 0);
}
{
ksync::lock!(let guard1 = d1.state().peered.lock());
let sigs1 = d1.signals_state_locked(guard1.token());
expect_true!((sigs1 & ZX_SOCKET_PEER_WRITE_DISABLED) != 0);
}
// Writing on write-disabled socket fails with BAD_STATE.
let res_w = d0.write(m_ptr, 5);
expect_true!(res_w == Err(Status::BAD_STATE));
// Re-enabling writes while unread buffered data is present must fail with BAD_STATE.
let res_re_enable = d0.set_disposition(Disposition::WriteEnabled, Disposition::None);
expect_true!(res_re_enable == Err(Status::BAD_STATE));
// Read out the 5 bytes from d1.
let (_out_mem, out_ptr) = make_user_out(5).unwrap();
let nread = d1.read(ReadType::Consume, out_ptr, 5).expect("read buffered data");
expect_eq!(nread, 5);
// Now that the buffer is empty, reading again with peer write disabled returns BAD_STATE.
let res_empty_read = d1.read(ReadType::Consume, out_ptr, 1);
expect_true!(res_empty_read == Err(Status::BAD_STATE));
// Now that buffer is empty, re-enabling writes on d0 succeeds.
expect_ok!(d0.set_disposition(Disposition::WriteEnabled, Disposition::None));
// d0 should now be writable again.
{
ksync::lock!(let guard0 = d0.state().peered.lock());
let sigs0 = d0.signals_state_locked(guard0.token());
expect_true!((sigs0 & ZX_SOCKET_WRITABLE) != 0);
expect_eq!(sigs0 & ZX_SOCKET_WRITE_DISABLED, 0);
}
// Writing to d0 succeeds again.
let w_again = d0.write(m_ptr, 5).expect("write again");
expect_eq!(w_again, 5);
}
/// Tests datagram zero-length, oversize, and capacity limits.
#[test]
fn test_datagram_atomic_and_capacity_limits() {
let (h0, h1, _) =
SocketDispatcher::create(ZX_SOCKET_DATAGRAM).expect("failed to create datagram socket");
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
let info = d0.get_info();
let max_capacity = info.rx_buf_max;
// Writing zero-length datagram via SocketDispatcher::write returns Ok(0).
let (_zero_mem, zero_ptr) = make_user_in(&[]).unwrap();
let res_zero = d0.write(zero_ptr, 0);
expect_true!(matches!(res_zero, Ok(0)));
// Writing datagram larger than max capacity returns OUT_OF_RANGE.
let huge_size = max_capacity + 1;
let (_huge_mem, huge_ptr) = make_user_in_pattern(1, |_| 0).unwrap();
let res_huge = d0.write(huge_ptr, huge_size);
expect_true!(res_huge == Err(Status::OUT_OF_RANGE));
// Test multi-buffer datagram (larger than a single MBuf page).
const MULTI_PAGE_PAYLOAD: usize = 8192;
let (_multi_mem, multi_ptr) =
make_user_in_pattern(MULTI_PAGE_PAYLOAD, |i| (i % 251) as u8).unwrap();
let w_multi = d0.write(multi_ptr, MULTI_PAGE_PAYLOAD).expect("write multi-page datagram");
expect_eq!(w_multi, MULTI_PAGE_PAYLOAD);
let info1 = d1.get_info();
expect_eq!(info1.rx_buf_available, MULTI_PAGE_PAYLOAD);
expect_eq!(info1.rx_buf_size, MULTI_PAGE_PAYLOAD);
let (out_mem, out_ptr) = make_user_out(MULTI_PAGE_PAYLOAD).unwrap();
let nread =
d1.read(ReadType::Consume, out_ptr, MULTI_PAGE_PAYLOAD).expect("read multi-page");
expect_eq!(nread, MULTI_PAGE_PAYLOAD);
let b = read_user_mem(&out_mem, MULTI_PAGE_PAYLOAD).unwrap();
for (i, &val) in b.iter().enumerate() {
expect_eq!(val, (i % 251) as u8);
}
}
/// Tests repeated multi-buffer stream writes and partial reads that drop MBuf pages during read.
#[test]
fn test_stream_read_multi_buffer_drop_and_reuse() {
let (h0, h1, _) = SocketDispatcher::create(0).expect("create stream socket");
let d0 = h0.dispatcher();
let d1 = h1.dispatcher();
const PAYLOAD: usize = 16 * 1024;
let (_in_mem, in_ptr) = make_user_in_pattern(PAYLOAD, |i| (i & 0xff) as u8).unwrap();
let (out_mem, out_ptr) = make_user_out(PAYLOAD).unwrap();
for _ in 0..5 {
let w = d0.write(in_ptr, PAYLOAD).expect("write stream");
expect_eq!(w, PAYLOAD);
let mut total_read = 0usize;
while total_read < PAYLOAD {
let chunk = core::cmp::min(1024, PAYLOAD - total_read);
let r = d1
.read(ReadType::Consume, out_ptr.byte_offset(total_read as isize), chunk)
.expect("read stream chunk");
expect_eq!(r, chunk);
total_read += r;
}
let b = read_user_mem(&out_mem, PAYLOAD).unwrap();
for (i, &val) in b.iter().enumerate() {
expect_eq!(val, (i & 0xff) as u8);
}
}
}
}