blob: 6df492e9ac812d06c17d991b9d13368607363f46 [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.
chromium::import! {
"//mojo/public/rust/system:ffi_bindings" as mojo_ffi;
}
use mojo_ffi::message;
use mojo_ffi::{MessageHandle, MojoError, MojoResult, UntypedHandle};
/// An object representing a Mojo message that can be (or has been) sent through
/// a pipe. This type is not thread-safe, and must be used from a single thread
/// at a time.
///
/// Logically, the message has two components: a payload composed of raw bytes,
/// and zero or more untyped mojo handles which are sent alongside the payload.
/// These components can be accessed and manipulated using the message's
/// methods.
///
/// FOR_RELEASE: Messages have various conditions which determine which
/// operations are permitted. We can probably encapsulate this using a typestate
/// pattern. More research on the possible states is needed, but it would
/// probably look something like `WriteableMessage` -> `SendableMessage` ->
/// <pipe> -> `FullyReadableMessage` -> `BytesOnlyReadableMessage`.
///
/// But, y'know, with better names.
pub struct RawMojoMessage {
pub(crate) message_handle: MessageHandle,
}
impl From<MessageHandle> for RawMojoMessage {
fn from(message_handle: MessageHandle) -> Self {
Self { message_handle }
}
}
impl From<RawMojoMessage> for MessageHandle {
fn from(message: RawMojoMessage) -> Self {
message.message_handle
}
}
impl Default for RawMojoMessage {
fn default() -> Self {
Self::new()
}
}
impl RawMojoMessage {
/// Construct an empty RawMojoMessage
pub fn new() -> Self {
message::MojoCreateMessage(message::CreateMessageFlags::empty()).into()
}
/// Construct a new message with the specified contents. If this operation
/// succeeds, ownership of the handles is transferred to the message object;
/// otherwise, they are returned as part of the result.
pub fn new_with_data(
bytes: &[u8],
handles: Vec<UntypedHandle>,
) -> Result<Self, (MojoError, Vec<UntypedHandle>)> {
let mut msg = Self::new();
msg.append_data(bytes, handles).map(|_| msg)
}
/// Construct a new message with the given bytes as its payload, and no
/// associated handles.
pub fn new_with_bytes(bytes: &[u8]) -> MojoResult<Self> {
let mut msg = Self::new();
msg.append_bytes(bytes).map(|bytes_written| {
// FOR_RELEASE: Handle this more gracefully
assert!(bytes_written == bytes.len());
msg
})
}
/// Append the bytes in `bytes` and the handles in `handles` to the
/// message's payload. Ownership of the handles is transferred to the
/// other side of the pipe if the operation is successful. If the operation
/// fails, any handles whose ownership was not transferred are returned to
/// the caller.
///
/// If successful, returns the number of bytes successfully written.
/// FOR_RELEASE: Maybe have this write all the bytes (in a loop) instead.
///
/// # 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 append_data(
&mut self,
bytes: &[u8],
handles: Vec<UntypedHandle>,
) -> Result<usize, (MojoError, Vec<UntypedHandle>)> {
message::MojoAppendMessageData(
&self.message_handle,
message::AppendMessageDataFlags::empty(),
bytes,
handles,
)
}
/// Append bytes to the message's payload. If successful, returns the number
/// of bytes written.
///
/// See `append_data` for possible error codes.
pub fn append_bytes(&mut self, bytes: &[u8]) -> MojoResult<usize> {
self.append_data(bytes, Vec::new()).map_err(|(result, _)| result)
}
/// Append untyped handles to the message's payload. Ownership of the
/// handles is transferred to the message only if the operation succeeds.
///
/// See `append_data` for possible error codes.
pub fn append_handles(
&mut self,
handles: Vec<UntypedHandle>,
) -> Result<(), (MojoError, Vec<UntypedHandle>)> {
self.append_data(&[], handles).map(|_| ())
}
/// Retrieve the payload, if present, and attached handles from this message
/// object.
///
/// Because this function transfers ownership of the handles to the caller,
/// it may only be called *once* per message object. Any further calls will
/// return `Err(NotFound)`, unless the message never had any handles
/// attached in the first place.
///
/// If you need to read only the payload, use `read_bytes` instead,
/// which does not have this restriction and does not prevent `read_data`
/// function from being called later.
///
/// FOR_RELEASE: We might be able to avoid returning a MojoResult here if we
/// manage to handle all possible errors internally (e.g. by reporting a bad
/// message). If not, catalogue the possible errors.
pub fn read_data(&self) -> MojoResult<(&[u8], Vec<UntypedHandle>)> {
// First, query the message to see how many handles are attached.
let num_handles = match message::MojoGetMessageData(
&self.message_handle,
Some(Vec::new().spare_capacity_mut()),
) {
// If the call succeeded, then no handles were attached, and we're done!
message::GetMessageDataStatus::Success { bytes, .. } => {
return Ok((bytes, vec![]));
}
message::GetMessageDataStatus::NotEnoughCapacity { num_handles_attached } => {
num_handles_attached
}
message::GetMessageDataStatus::Error(err) => {
return Err(err);
}
};
// Copy the handles from the message into a vector. This prevents further
// reads of the handles.
let mut handles: Vec<UntypedHandle> = Vec::with_capacity(num_handles as usize);
match message::MojoGetMessageData(&self.message_handle, Some(handles.spare_capacity_mut()))
{
// If the call succeeded, then no handles were attached, and we're done!
message::GetMessageDataStatus::Success { bytes, num_handles_written } => {
// SAFETY: `MojoGetMessageData` guarantees that the first `num_handles_written`
// elements of `handles` are now initialized.
unsafe { handles.set_len(num_handles_written) };
return Ok((bytes, handles));
}
message::GetMessageDataStatus::NotEnoughCapacity { .. } => {
// We just allocated enough capacity
unreachable!()
}
message::GetMessageDataStatus::Error(err) => {
return Err(err);
}
};
}
/// Read just the bytes of this message's payload, ignoring any attached
/// handles.
///
/// Unlike `read_data`, this function can be called multiple times.
///
/// FOR_RELEASE: See if we can avoid returning a MojoError here by handling
/// error cases internally. If not, catalogue the possible errors.
pub fn read_bytes(&self) -> MojoResult<&[u8]> {
match message::MojoGetMessageData(&self.message_handle, None) {
// If the call succeeded, then no handles were attached, and we're done!
message::GetMessageDataStatus::Success { bytes, .. } => {
return Ok(bytes);
}
message::GetMessageDataStatus::NotEnoughCapacity { .. } => {
// We passed `None` above so the function will ignore capacity
unreachable!()
}
message::GetMessageDataStatus::Error(err) => {
return Err(err);
}
};
}
/// Mark the message as ready to be sent.
///
/// FOR_RELEASE: Figure out and document the implications of this
/// (presumably you can't append more data afterwards?)
///
/// FOR_RELEASE: Maybe use the type-state pattern here so we can't confuse
/// ready and unready messages.
pub fn finalize_for_sending(&self) {
let ret = message::MojoAppendMessageData(
&self.message_handle,
message::AppendMessageDataFlags::COMMIT_SIZE,
&[],
vec![],
);
debug_assert!(ret.is_ok());
}
/// 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. However, you should almost always abort whatever you're
/// doing, rather than try to continue processing the known-bad message.
/// To make it harder to forget to do so, this function will always return
/// an error result so the compiler will warn if you try to treat it like a
/// panic and ignore its return value.
pub fn report_bad_message(&self, error_msg: &str) -> Result<(), BadMessageError> {
// Ignore the MojoError; this can only fail if the message_handle is invalid,
// and we guarantee that it's valid as part of this type.
// SAFETY: We guarantee that our contained handle is alive.
message::MojoNotifyBadMessage(&self.message_handle, error_msg);
Err(BadMessageError)
}
}
/// Error that is always returned from [`report_bad_message`], to encourage
/// the caller to stop any further processing and propagate the error up.
#[derive(Debug)]
pub struct BadMessageError;
impl std::fmt::Display for BadMessageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"This error is a reminder to the programmer to stop processing \
after calling `report_bad_message`"
)?;
Ok(())
}
}
impl std::error::Error for BadMessageError {}