blob: 43331ab5aae1886949864af1c5982f1634df71c7 [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 message API.
//! Note that this is distinct from the message _pipe_ API in the `message_pipe`
//! module.
//!
//! 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::internal_options::declare_mojo_options;
use crate::handles::*;
use crate::result::*;
bitflags::bitflags! {
/// The possible flags that can be passed to MojoCreateMessage
#[derive(Clone, Copy, Default)]
pub struct CreateMessageFlags: u32 {
/// Do not enforce size restrictions on this message.
const UNLIMITED_SIZE = 1 << 0;
}
}
declare_mojo_options!(MojoCreateMessageOptions, flags: raw_ffi::MojoCreateMessageFlags);
/// Create a new message object which can be sent over a message pipe.
///
/// In its initial state the message object cannot be successfully written to a
/// message pipe, but must first have either an opaque context or
/// some serialized data attached (see `MojoSetMessageContext` and
/// `MojoAppendMessageData`).
///
/// Each message object contains a *payload* with two components: a chunk of raw
/// bytes and a list of `UntypedHandle`s. Data can be written to the payload
/// using `MojoAppendMessageData` and read using `MojoGetMessageData`.
/// Appending/reading handles transfers ownership of those handlers to/from the
/// message.
///
/// # Flags:
/// - UNLIMITED_SIZE: If passed, the message's payload may grow to an arbitrary
/// size. Otherwise, Mojo will assert-fail if its payload grows past a
/// globally configured threshold.
pub fn MojoCreateMessage(flags: CreateMessageFlags) -> MessageHandle {
let options = MojoCreateMessageOptions::new(flags.bits());
let mut message_handle: raw_ffi::MojoMessageHandle = 0;
// SAFETY: Both pointers are to stack data
// Surprisingly, according to its comments, this function will not fail
// unless the message pointer is null, which it isn't.
unsafe {
raw_ffi::MojoCreateMessage(options.as_ptr(), std::ptr::from_mut(&mut message_handle))
};
// SAFETY: we just got this handle from Mojo
unsafe { MessageHandle::wrap_raw_value(message_handle) }
}
bitflags::bitflags! {
/// The possible flags that can be passed to MojoAppendMessageData
#[derive(Clone, Copy, Default)]
pub struct AppendMessageDataFlags: u32 {
/// Indicates that the message is now complete and its size will no longer increase.
const COMMIT_SIZE = 1 << 0;
}
}
declare_mojo_options!(MojoAppendMessageDataOptions, flags: raw_ffi::MojoAppendMessageDataFlags);
/// Append data to the message's payload. Returns the number of bytes written.
///
/// This will append the contents of `bytes` and `handles` to the message's
/// internal payload buffer. Ownership of the handles is transferred to the
/// message object if this operation succeeds; otherwise, the handles are
/// returned to the caller unchanged (either all handles are appended or none).
///
/// This function will panic if you try to append more than 2^32 handles at
/// once. If you absolutely must do so, append them in chunks.
///
/// # Flags:
/// - COMMIT_SIZE: Indicate that this is the final write operation for the
/// message. This is one way of marking the message as ready to be sent.
///
/// # Possible Error Codes:
/// - `ResourceExhausted`: if the additional payload size exceeds some
/// implementation- or embedder-defined maximum.
/// - `FailedPrecondition` if the message already has a context attached
pub fn MojoAppendMessageData(
message: &MessageHandle,
flags: AppendMessageDataFlags,
bytes: &[u8],
handles: Vec<UntypedHandle>,
) -> Result<usize, (MojoError, Vec<UntypedHandle>)> {
let options = MojoAppendMessageDataOptions::new(flags.bits());
let raw_handles_ptr: *const raw_ffi::MojoHandle = UntypedHandle::slice_as_ptr(&handles);
let mut buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
let mut buffer_size: u32 = 0;
// SAFETY: All pointers are to stack data, except possibly `raw_handles_ptr`.
// `slice_as_ptr` guarantees that which is null iff `handles.len()` is 0; which
// is permitted by the function. We're passing `handles.len()` as the number
// of handles, so we won't read past the end of `handles`.
let result = MojoError::result_from_code(unsafe {
raw_ffi::MojoAppendMessageData(
message.handle_value.into(),
bytes.len().try_into().unwrap_or(u32::MAX), // Payload size
raw_handles_ptr,
handles.len().try_into().unwrap(), // Number of handles
options.as_ptr(),
std::ptr::from_mut(&mut buffer_ptr),
std::ptr::from_mut(&mut buffer_size),
)
});
match result {
Ok(()) => {
// If `MojoAppendMessageData` succeeds, ownership of all handles is
// transferred to the C object, so prevent Rust from double-free-ing them.
for handle in handles.into_iter() {
std::mem::forget(handle);
}
}
Err(err) => {
return Err((err, handles));
}
};
// `MojoAppendMessageData` copies the handles for us, but returns a pointer to
// its buffer that we need to write to ourselves.
if !bytes.is_empty() {
// Will not panic if usize has at least 32 bits, which is true for Chromium
// targets
let buffer_size: usize = buffer_size.try_into().unwrap();
// This may be unnecessary, but `MojoAppendMessageData` doesn't explicitly
// promise that the returned capacity is at most the requested amount.
let bytes_to_write = std::cmp::min(bytes.len(), buffer_size);
// SAFETY: The API call guarantees that the buffer pointer is valid
// and has at least `buffer_size` capacity, and `bytes_to_write` is
// at most `buffer_size`.
let buffer_slice: &mut [u8] =
unsafe { std::slice::from_raw_parts_mut(buffer_ptr.cast(), bytes_to_write) };
buffer_slice.copy_from_slice(&bytes[..bytes_to_write]);
return Ok(bytes_to_write);
};
Ok(0)
}
bitflags::bitflags! {
/// The possible flags that can be passed to MojoGetMessageData
/// This struct is private because it's passed implicitly based on whether
/// the `handles` argument to `MojoGetMessageData` is `Some` or `None`.
#[derive(Clone, Copy, Default)]
struct GetMessageDataFlags: u32 {
/// Only retrieve the "raw bytes" parts of the payload
const IGNORE_HANDLES = 1 << 0;
}
}
declare_mojo_options!(MojoGetMessageDataOptions, flags: raw_ffi::MojoGetMessageDataFlags);
pub enum GetMessageDataStatus<'a> {
Success { bytes: &'a [u8], num_handles_written: usize },
NotEnoughCapacity { num_handles_attached: u32 },
Error(MojoError),
}
/// Retrieve the message's payload.
///
/// If `handles` is `Some` and this function is successful, it will return a
/// reference to the message's underlying bytes buffer, copy the attached
/// handles into `handles`, and return the number of handles written. This
/// transfers ownership of any attached handles to the caller. Because of the
/// ownership transfer, this function may be (successfully) called at most once
/// per message without the `IGNORE_HANDLES` flag.
///
/// If `handles` is `Some` and does not have enough capacity to store all
/// attached handles, the function will return `NotEnoughCapacity` containing
/// the number of handles attached to the message.
///
/// If `handles` is `None`, then no handles will be copied and this function
/// will never return `NotEnoughCapacity`.
///
/// If something else goes wrong, the function will return a `MojoError`, or
/// panic if `handles` has more than 2^32 elements.
///
/// # Safety Guarantees
/// - If this function returns `Success`, then the first `num_handles_written`
/// elements of `handles` have been initialized.
///
/// # Possible Error Codes:
/// - `FailedPrecondition`: If the message is not fully serialized (use
/// `MojoSerializeMessage` or `MojoAppendMessageData` to mark it as ready for
/// transfer/reading)
/// - `NotFound`: if the message's handles have already been extracted and
/// `handles` was not `None`
/// - `Aborted`: if the message is in an unrecoverable state.
// FOR_RELEASE: Add a fully-safe equivalent that allocates memory for the
// handles instead of taking a buffer as an out-parameter, and/or one that takes
// in an initialized slice to start with.
// FOR_RELEASE: Add a mutable equivalent (takes &mut, returns &mut)
pub fn MojoGetMessageData<'a>(
message: &'a MessageHandle,
handles: Option<&mut [std::mem::MaybeUninit<UntypedHandle>]>,
) -> GetMessageDataStatus<'a> {
let flags = if handles.is_none() {
GetMessageDataFlags::IGNORE_HANDLES
} else {
GetMessageDataFlags::empty()
};
let options = MojoGetMessageDataOptions::new(flags.bits());
let mut buffer: *mut std::ffi::c_void = std::ptr::null_mut();
let mut num_bytes: u32 = 0;
let (handles_ptr, mut num_handles) = match handles {
// SAFETY: This cast is sound because `MaybeUninit<UntypedHandle>` has the
// same memory layout as `UntypedHandle`, which has the same memory layout
// as `MojoHandle`.
Some(slice) => (std::ptr::from_mut(slice).cast(), slice.len().try_into().unwrap()),
None => (std::ptr::null_mut(), 0),
};
// SAFETY: All pointers are either to stack variables, or validly null.
// If `num_handles` is nonzero, then it contains the length of `handles_ptr`,
// so this function will not write past the end of `handles`.
let result = MojoError::result_from_code(unsafe {
raw_ffi::MojoGetMessageData(
message.handle_value.into(),
options.as_ptr(),
std::ptr::from_mut(&mut buffer),
std::ptr::from_mut(&mut num_bytes),
handles_ptr,
std::ptr::from_mut(&mut num_handles),
)
});
match result {
Ok(()) => {
debug_assert!(!buffer.is_null());
// Will not panic if usize has at least 32 bits, which is true for Chromium
// targets
let buffer_size: usize = num_bytes.try_into().unwrap();
// SAFETY: `buffer` and `buffer_size` were obtained by calling
// `MojoGetMessageData`, so we trust that they are valid, aligned,
// readable, etc.
// The buffer is readable as long as the message is alive, and the type
// of this function ensures that the lifetime of the resulting reference
// is the same as that of `message`.
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer.cast(), buffer_size) };
return GetMessageDataStatus::Success {
bytes: buffer_slice,
num_handles_written: num_handles.try_into().unwrap(),
};
}
Err(MojoError::ResourceExhausted) => {
// ResourceExhausted indicates that the provided handles buffer wasn't
// large enough. In this case, `num_handles` has been filled in.
return GetMessageDataStatus::NotEnoughCapacity { num_handles_attached: num_handles };
}
Err(e) => return GetMessageDataStatus::Error(e),
}
}
/// Tell the underlying mojo system that this message was malformed, which
/// will trigger the error handler which was set up during mojo
/// initialization.
///
/// Typically this will result in the process that created the message being
/// terminated. Note that reporting a bad message does not stop the _current_
/// process from running.
pub fn MojoNotifyBadMessage(message: &MessageHandle, error_msg: &str) {
// SAFETY: The option pointer is allowed to be be null. The string parts were
// constructed from an &str, and will not be retained by C code after the
// function returns.
let ret = unsafe {
raw_ffi::MojoNotifyBadMessage(
message.handle_value.into(),
error_msg.as_ptr().cast(),
error_msg.len().try_into().unwrap(),
std::ptr::null(), // Options pointer
)
};
// This should only fail if the message handle is invalid and we guarantee that
// it's valid as part of this type.
debug_assert!(MojoError::result_from_code(ret).is_ok())
}