blob: a8ed7da1a647f6495f4a0143be175a9a927a9a3c [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This module defines (mostly) safe Rust wrappers around the Mojo trap API.
//!
//! Not all C API functions are included yet. More can be added as-needed by
//! following the example of existing wrappers.
chromium::import! {
"//mojo/public/rust/system:mojo_c_system_bindings" as raw_ffi;
}
use crate::handles::*;
use crate::result::*;
/// This module defines various auxiliary types which are used as arguments or
/// return values by the wrapper functions.
pub mod types {
use super::*;
/// An event reported by the trap; used as the argument to the trap's
/// handler.
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct TrapEvent(raw_ffi::MojoTrapEvent);
impl TrapEvent {
/// Retrieve the context value provided in `Trap::add_trigger`.
pub fn trigger_context(&self) -> usize {
self.0.trigger_context
}
/// Why the trigger fired. Possible return values:
/// - Ok(()): a specified signal occurred.
/// - Err(FailedPrecondition): a signal can no longer happen on the
/// handle.
/// - Err(Cancelled): the trigger was removed (explicitly or by closing
/// the trap or the watched handle).
pub fn result(&self) -> MojoResult<()> {
MojoError::result_from_code(self.0.result)
}
/// The handle's current and possible signals as of triggering.
pub fn signals_state(&self) -> SignalsState {
SignalsState(self.0.signals_state)
}
}
/// The EventHandler that will be called when a trigger fires.
///
/// This type is ABI-compatible with the (non-null) MojoTrapEventHandler C
/// type. Due to ABI compatibility, *const T and &T are identical, and
/// TrapEvent is #[repr(transparent)] and thus has the same layout as
/// MojoTrapEvent).
///
/// # Safety invariant
///
/// The Mojo C API guarantees that once `EventHandler` is called with
/// `MOJO_RESULT_CANCELLED`, then it won't be called again with the same
/// `context`.
pub type EventHandler = extern "C" fn(&TrapEvent);
// FOR_RELEASE(https://crbug.com/458796903): Don't hardcode these
/// An enum describing the two possible conditions for a trigger to fire:
/// either when a signal becomes satisfied, or when it become unsatisfied.
#[derive(Clone, Copy, Debug)]
pub enum TriggerCondition {
/// The condition triggers when *any* observed signal transitions from
/// satisfied to unsatisfied.
TriggerWhenUnsatisfied = 0,
/// The condition triggers when *any* observed signal transitions from
/// unsatisfied to satisfied.
TriggerWhenSatisfied = 1,
}
impl TriggerCondition {
pub(super) fn into_raw(self) -> raw_ffi::MojoTriggerCondition {
self as _
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
/// A bitfield describing the possible signals that a trap might observe.
pub struct HandleSignals: raw_ffi::MojoHandleSignals {
const READABLE = 1 << 0;
const WRITABLE = 1 << 1;
const PEER_CLOSED = 1 << 2;
const NEW_DATA_READABLE = 1 << 3;
const PEER_REMOTE = 1 << 4;
const QUOTA_EXCEEDED = 1 << 5;
}
}
impl HandleSignals {
/// Check if the readable flag is set.
pub fn is_readable(&self) -> bool {
self.contains(HandleSignals::READABLE)
}
/// Check if the writable flag is set.
pub fn is_writable(&self) -> bool {
self.contains(HandleSignals::WRITABLE)
}
/// Check if the peer-closed flag is set.
pub fn is_peer_closed(&self) -> bool {
self.contains(HandleSignals::PEER_CLOSED)
}
}
/// Represents the signals state of a handle: which signals are satisfied,
/// and which are satisfiable.
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct SignalsState(pub raw_ffi::MojoHandleSignalsState);
impl SignalsState {
/// Generates a new SignalsState
pub fn new(satisfied: HandleSignals, satisfiable: HandleSignals) -> SignalsState {
SignalsState(raw_ffi::MojoHandleSignalsState {
satisfied_signals: satisfied.bits(),
satisfiable_signals: satisfiable.bits(),
})
}
/// Returns the bitfield of the satisfied signals
pub fn satisfied(&self) -> HandleSignals {
HandleSignals::from_bits_truncate(self.0.satisfied_signals)
}
/// Returns the bitfield of the satisfiable signals
pub fn satisfiable(&self) -> HandleSignals {
HandleSignals::from_bits_truncate(self.0.satisfiable_signals)
}
/// Return the wrapped Mojo FFI struct.
pub fn into_raw(self) -> raw_ffi::MojoHandleSignalsState {
self.0
}
/// Get a pointer to the inner struct for FFI calls.
pub fn as_mut_ptr(&mut self) -> *mut raw_ffi::MojoHandleSignalsState {
&mut self.0 as *mut _
}
}
impl std::default::Default for SignalsState {
fn default() -> Self {
SignalsState(raw_ffi::MojoHandleSignalsState {
satisfied_signals: 0,
satisfiable_signals: 0,
})
}
}
/// The result of arming a trap.
pub enum ArmResult<'a> {
/// The trap was successfully armed with no blocking events.
Armed,
/// An event would have triggered immediately, blocking the arming.
/// Contains an arbitrary subset of the blocking event(s). The
/// returned slice is a reborrow of the buffer passed to
/// `MojoArmTrap`.
Blocked(&'a [TrapEvent]),
/// Arming failed due to a different Mojo error, or no buffer of
/// blocking events was provided.
Failed(MojoError),
}
}
pub use types::*;
/// Create a trap that calls `handler` for each event. The handler may run
/// on any thread. See the documentation for `MojoAddTrigger` for more
/// information about triggers.
///
/// Generally, `handler` will be only called while the trap is armed. However,
/// it will be called while disarmed upon a removing a trigger which happens
/// in two cases:
/// - The trigger is explicitly removed with `MojoRemoveTrigger`
/// - The trap's handle is closed
///
/// # Safety guarantees
/// - `handler` will *not* be called after the trap is dropped.
///
/// This guarantee may be helpful to meet the safety requirements involved
/// in reinterpreting `TrapEvent::trigger_context` as a reference:
/// the dereference will be safe if the lifetime of the referent is
/// guaranteed to be longer than the lifetime of the trap.
///
/// # Possible Error Codes:
/// - `ResourceExhausted`: If the trap handler was unable to be created (e.g.
/// because the process ran out of possible handle values)
pub fn MojoCreateTrap(handler: EventHandler) -> MojoResult<UntypedHandle> {
// First we need to transmute the passed-in handler to match the C type.
// SAFETY: The EventHandler type is ABI-compatible with MojoTrapEventHandler.
let handler_ptr = unsafe {
std::mem::transmute::<extern "C" fn(&TrapEvent), extern "C" fn(*const raw_ffi::MojoTrapEvent)>(
handler,
)
};
let mut handle: raw_ffi::MojoHandle = 0;
// SAFETY: Function pointers are always valid. The options pointer is
// permitted to be null.
let ret = MojoError::result_from_code(unsafe {
raw_ffi::MojoCreateTrap(
Some(handler_ptr),
std::ptr::null(), // This function doesn't have any options
std::ptr::from_mut(&mut handle),
)
});
// SAFETY: We just got this value from Mojo
ret.map(|_| unsafe { UntypedHandle::wrap_raw_value(handle) })
}
/// Add a trigger to the trap, which will cause the trap's handler (set when the
/// trap was created) to be invoked with the given `context` value in its
/// `TrapEvent`. Triggers are only invoked if the trap is armed (see
/// `MojoArmTrap`), except for cancellations.
///
/// `monitored_handle` must be either a data pipe or message pipe handle.
/// `signals` is a bitfield indicating which signals should be observed on that
/// handle. The handler will be invoked whenever *any* observed signal
/// becomes satisfied (if `condition` is `TriggerWhenSatisfied), or when any
/// signal becomes unsatisfied (if `condition` is `TriggerWhenUnsatisfied`).
///
/// # Safety Notes
/// `context` is a pointer-size integer that is used to tell the handler which
/// trigger invoked it. In almost all cases it will be interpreted as an actual
/// pointer.
///
/// Therefore, this function is not itself `unsafe`, the provided handler must
/// ensure that the value `context` points to is still alive when dereferenced.
///
/// ## Guarantees
/// The given `context` will not be passed to `EventHandler` after either of
/// the following happen:
/// - The trap handle is closed.
/// - `MojoRemoveTrigger` is called on this trap with `context`.
///
/// # Requirements
/// - `trap_handle` is actually a trap handle.
/// - `monitored_handle` is either a message or data pipe handle.
/// - The trap must not already have a trigger with `context`.
///
/// # Possible Error Codes:
/// - `InvalidArgument` if one of the handles had an incorrect type
/// - `AlreadyExists` if the trap already has a trigger with `context`.
pub fn MojoAddTrigger(
trap_handle: &UntypedHandle,
monitored_handle: &UntypedHandle,
signals: HandleSignals,
condition: TriggerCondition,
context: usize,
) -> MojoResult<()> {
// SAFETY: The `UntypedHandle` type guarantees its contents are alive.
// The options pointer is allowed to be null.
MojoError::result_from_code(unsafe {
raw_ffi::MojoAddTrigger(
trap_handle.handle_value.into(),
monitored_handle.handle_value.into(),
signals.bits(),
condition.into_raw(),
context,
std::ptr::null(), // This function doesn't take options
)
})
}
/// Remove the trigger associated with `context` from the trap.
///
/// After removal, this function will invoke the trigger with `context` one
/// last time, with the result `Cancelled` inside the trap event. Removal
/// may block an arbitrarily long time if the handler is already executing
/// elsewhere.
///
/// # Safety Guarantees
/// - The given `context` will not be passed to `EventHandler` after the
/// `remove_trigger` call returns.
///
/// # Requirements
/// - `trap_handle` must actually be a trap handle.
/// - `trap_handle` must have a trigger associated with `context`.
///
/// # Possible Error Codes:
/// - `InvalidArgument`: if `trap_handle` is not actually a trap handle
/// - `NotFound`: if the trap has no trigger associated with `context`.
pub fn MojoRemoveTrigger(trap_handle: &UntypedHandle, context: usize) -> MojoResult<()> {
// SAFETY: The `UntypedHandle` type guarantees the handle is valid. The options
// pointer is allowed to be null.
MojoError::result_from_code(unsafe {
raw_ffi::MojoRemoveTrigger(
trap_handle.handle_value.into(),
context,
std::ptr::null(), // This function doesn't take options
)
})
}
/// Attempt to arm the provided trap, causing its handler to be unvoked the next
/// time any trigger's condition is satisfied.
///
/// Before invoking the handler, the trap will disarm itself, and must be
/// re-armed before triggers can resume firing. Often this is done as part of
/// the handler.
///
/// The trap will refuse to arm if any trigger would fire immediately. If this
/// is the case, this function can provide the "blocking events" which are
/// would cause it to fire. These are the `TrapEvent`s with which the handler
/// would have been invoked if the trap were already armed. In order to arm the
/// trap, the user must first handle all of these events in an appropriate way.
///
/// For example, if the trap is watching for incoming messages on a pipe, the
/// blocking events will correspond to messages that are waiting to be read, and
/// the user should read all of them before re-arming the trap.
///
/// # Safety Guarantees
/// - If this function returns `Blocked(s)`, then the slice `s` is a reborrow of
/// `blocking_events`, and the first `s.len()` elements of `blocking_events`
/// are initialized.
///
/// # Requirements
/// - `trap_handle` must actually be a trap handle.
///
/// # Possible Error Codes
/// - `InvalidArgument` if `trap_handle` is not actually a trap handle.
/// - `NotFound` if the trap has no triggers.
/// - `FailedPrecondition` if `blocking_events` was None but the trap failed to
/// arm due to blocking events.
pub fn MojoArmTrap<'a>(
trap_handle: &UntypedHandle,
mut blocking_events: Option<&'a mut [std::mem::MaybeUninit<TrapEvent>]>,
) -> ArmResult<'a> {
let mut num_events = blocking_events
.as_ref()
// If the caller passes a bigger slice, populate only the first part.
.map_or(0, |b| u32::try_from(b.len()).unwrap_or(u32::MAX));
let (blocking_events_ptr, num_events_ptr) = match blocking_events {
Some(ref mut slice) => {
// For each blocking event, we must initialize `struct_size`. Otherwise,
// `MojoArmTrap` below will complain about receiving an invalid argument.
for event in slice.iter_mut() {
// A reference to the `struct_size` field of the TrapEvent.
// SAFETY: TrapEvent is a #[repr(transparent)] wrapper around `MojoTrapEvent`,
// which itself is a #[repr(C)] struct with `struct_size` as the first element.
// Therefore, the `struct_size` field begins at the same memory address as the
// `TrapEvent` as a whole.
let size_ref = unsafe {
std::mem::transmute::<
&mut std::mem::MaybeUninit<TrapEvent>,
&mut std::mem::MaybeUninit<u32>,
>(event)
};
// The size of the struct will certainly fit into 32 bits.
let size = std::mem::size_of::<raw_ffi::MojoTrapEvent>().try_into().unwrap();
size_ref.write(size);
}
(slice.as_mut_ptr().cast(), std::ptr::from_mut(&mut num_events))
}
None => (std::ptr::null_mut(), std::ptr::null_mut()),
};
// SAFETY: The `UntypedHandle` type ensures the handle is live. The options
// pointer is allowed to be null. The other pointers are either both validly
// null, or derived from references. `num_events` holds at most the length
// of `blocking_events`, so the function will not try to write past the end.
let result = MojoError::result_from_code(unsafe {
raw_ffi::MojoArmTrap(
trap_handle.handle_value.into(),
std::ptr::null(), // This function doesn't have any options
num_events_ptr,
blocking_events_ptr,
)
});
match (result, blocking_events) {
(Ok(()), _) => ArmResult::Armed,
(Err(MojoError::FailedPrecondition), Some(blocking_events)) => {
let count = num_events as usize;
debug_assert!(count > 0);
debug_assert!(count <= blocking_events.len());
// Tranmuting into &mut [TrapEvent] is safe because:
// * it is derived from blocking_events, which is already a valid Rust slice
// managing the lifetime
// * MojoArmTrap guarantees it initialized the first `num_events` elements
let initialized_slice = unsafe {
std::slice::from_raw_parts_mut(blocking_events.as_mut_ptr().cast(), count)
};
ArmResult::Blocked(initialized_slice)
}
(Err(err), _) => ArmResult::Failed(err),
}
}