blob: fa063115b41a3f027b2c1240dfa9f829ce4ff11e [file] [log] [blame] [edit]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! A helper for managing a self-contained arena-allocated buffer along with its arena handle.
use crate::arena::{Arena, ArenaBox};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use fdf_core::handle::MixedHandle;
use fdf_sys::*;
/// A struct that holds both an arena along with a data buffer that is allocated within that arena.
#[derive(Debug)]
pub struct Message<T: ?Sized + 'static> {
data: Option<ArenaBox<'static, T>>,
// note: `[Option<MixedHandle>]` is byte-equivalent to a C array of `fdf_handle_t`.
handles: Option<ArenaBox<'static, [Option<MixedHandle>]>>,
// note: this must maintain its position as the last item of the struct
// to ensure that it is freed after the data and handle pointers.
arena: Arena,
_p: PhantomData<T>,
}
impl<T: ?Sized> Message<T> {
/// Consumes the given arena, data buffer, and handles buffers and returns a message that holds
/// all three.
///
/// # Panics
///
/// This function panics if either of the [`ArenaBox`]s are not allocated by [`Arena`].
pub fn new<'a>(
arena: &'a Arena,
data: Option<ArenaBox<'a, T>>,
handles: Option<ArenaBox<'a, [Option<MixedHandle>]>>,
) -> Self {
let data = data.map(|data| {
assert!(
arena.contains(&data),
"Data buffer pointer is not in the arena being included in the message"
);
// SAFETY: we will store this ArenaBox with a clone of the Arena that
// owns it.
unsafe { ArenaBox::erase_lifetime(data) }
});
let handles = handles.map(|handles| {
assert!(
arena.contains(&handles),
"Handle buffer pointer is not in the arena being included in the message"
);
// SAFETY: we will store this ArenaBox with a clone of the Arena that
// owns it.
unsafe { ArenaBox::erase_lifetime(handles) }
});
// SAFETY: We just checked that both boxes were allocated from the arena.
unsafe { Self::new_unchecked(arena.clone(), data, handles) }
}
/// Given the [`Arena`], allocates a new [`Message`] and runs the given `f` to allow the caller
/// to allocate data and handles into the [`Message`] without requiring a check that the
/// correct [`Arena`] was used to allocate the data.
///
/// Note that it may be possible to sneak an `ArenaBox<'static, T>` into this [`Message`]
/// object with this function by using an [`Arena`] with static lifetime to allocate it. This
/// will not cause any unsoundness, but if you try to send that message through a [`Channel`]
/// it will cause a runtime error when the arenas don't match.
pub fn new_with<F>(arena: Arena, f: F) -> Self
where
F: for<'a> FnOnce(
&'a Arena,
)
-> (Option<ArenaBox<'a, T>>, Option<ArenaBox<'a, [Option<MixedHandle>]>>),
{
let (data, handles) = f(&arena);
// SAFETY: The `for<'a>` in the callback definition makes it so that the caller must
// (without resorting to unsafe themselves) allocate the [`ArenaBox`] from the given
// [`Arena`].
Self {
data: data.map(|data| unsafe { ArenaBox::erase_lifetime(data) }),
handles: handles.map(|handles| unsafe { ArenaBox::erase_lifetime(handles) }),
arena,
_p: PhantomData,
}
}
/// A shorthand for [`Self::new_with`] when there's definitely a data body and nothing else.
pub fn new_with_data<F>(arena: Arena, f: F) -> Self
where
F: for<'a> FnOnce(&'a Arena) -> ArenaBox<'a, T>,
{
// SAFETY: The `for<'a>` in the callback definition makes it so that the caller must
// (without resorting to unsafe themselves) allocate the [`ArenaBox`] from the given
// [`Arena`].
let data = Some(unsafe { ArenaBox::erase_lifetime(f(&arena)) });
Self { data, handles: None, arena, _p: PhantomData }
}
/// As with [`Self::new`], this consumes the arguments to produce a message object that holds
/// all of them together to be extracted again later, but does not validate that the pointers
/// came from the same arena.
///
/// # Safety
///
/// The caller is responsible for:
/// - ensuring that the [`ArenaBox`]es came from the same arena as is being passed in to this
/// function, or the erased lifetime of the arena boxes might cause use-after-free.
/// - the bytes in `data` are actually of type `T`, and are properly aligned for type `T`.
pub(crate) unsafe fn new_unchecked(
arena: Arena,
data_ptr: Option<ArenaBox<'static, T>>,
handles_ptr: Option<ArenaBox<'static, [Option<MixedHandle>]>>,
) -> Self {
Self { arena, data: data_ptr, handles: handles_ptr, _p: PhantomData }
}
/// Gets a reference to the arena this message was allocated with.
pub fn arena(&self) -> &Arena {
&self.arena
}
/// Takes the arena and drops any data or handle bodies held in this message
pub fn take_arena(self) -> Arena {
self.arena
}
/// Gets a reference to the data in this message, if there is any
pub fn data(&self) -> Option<&T> {
self.data.as_deref()
}
/// Gets a mutable reference to the data in this message, if there is any
pub fn data_mut(&mut self) -> Option<&mut T> {
self.data.as_deref_mut()
}
/// Maps the message data to a new [`ArenaBox`] based on the arena and the old data.
pub fn map_data<F, R: ?Sized>(self, f: F) -> Message<R>
where
F: for<'a> FnOnce(&'a Arena, ArenaBox<'a, T>) -> ArenaBox<'a, R>,
{
let Self { arena, data: data_ptr, handles: handles_ptr, .. } = self;
let data_ptr = data_ptr.map(|data_ptr| {
// SAFETY: The `ArenaBox` being returned is tied to the lifetime
// of the arena we gave the closure, and we will now be moving
// into the new `Message`. So just like the old one,
// the new box is tied to the life of the message and the arena
// within it.
unsafe { ArenaBox::erase_lifetime(f(&arena, data_ptr)) }
});
Message { arena, data: data_ptr, handles: handles_ptr, _p: PhantomData }
}
/// Gets a reference to the handles array in this message, if there is one.
pub fn handles(&self) -> Option<&[Option<MixedHandle>]> {
self.handles.as_deref()
}
/// Gets a mutable reference to the handles array in this message, if there is one.
pub fn handles_mut(&mut self) -> Option<&mut [Option<MixedHandle>]> {
self.handles.as_deref_mut()
}
/// Gets a reference to all three of the arena, data, and handles of the message
pub fn as_refs(&self) -> (&Arena, Option<&T>, Option<&[Option<MixedHandle>]>) {
(&self.arena, self.data.as_deref(), self.handles.as_deref())
}
/// Gets a reference to the arena and mutable references to the data handles of the message
pub fn as_mut_refs(&mut self) -> (&Arena, Option<&mut T>, Option<&mut [Option<MixedHandle>]>) {
(&self.arena, self.data.as_deref_mut(), self.handles.as_deref_mut())
}
/// Unpacks the arena and buffers in this message to the caller.
///
/// The `arena` argument provides a place to put the [`Arena`] from this message
/// in the local lifetime of the caller so that the [`ArenaBox`]es can be tied to
/// its lifetime.
#[expect(clippy::type_complexity)]
pub fn into_arena_boxes<'a>(
self,
arena: &'a mut Option<Arena>,
) -> (Option<ArenaBox<'a, T>>, Option<ArenaBox<'a, [Option<MixedHandle>]>>) {
arena.replace(self.arena);
// SAFETY: the lifetime we're giving these [`ArenaBox`]es is the same one
// as the lifetime of the place we're putting the [`Arena`] they belong to.
let data = self.data.map(|ptr| unsafe { ArenaBox::erase_lifetime(ptr) });
let handles = self.handles.map(|ptr| unsafe { ArenaBox::erase_lifetime(ptr) });
(data, handles)
}
/// Takes the `ArenaBox`es for the data and handles from this [`Message`], but leaves
/// the [`Arena`] in the [`Message`] to act as a holder of the arena lifetime.
#[expect(clippy::type_complexity)]
pub fn take_arena_boxes(
&mut self,
) -> (&'_ Arena, Option<ArenaBox<'_, T>>, Option<ArenaBox<'_, [Option<MixedHandle>]>>) {
(&self.arena, self.data.take(), self.handles.take())
}
/// Unpacks the arena and buffers into raw pointers
///
/// Care must be taken to ensure that the data and handle pointers are not used
/// if the arena is freed. If they are never reconstituted into a [`Message`]
/// or an [`Arena`] and [`ArenaBox`]es, they will be leaked.
#[expect(clippy::type_complexity)]
pub fn into_raw(
self,
) -> (NonNull<fdf_arena_t>, Option<NonNull<T>>, Option<NonNull<[Option<MixedHandle>]>>) {
let arena = self.arena.into_raw();
// SAFETY: the arena and the pointers we're returning will all have the same
// effectively 'static lifetime, and it is up to the caller to make sure that
// they free them in the correct order.
let data = self.data.map(|data| unsafe { ArenaBox::into_ptr(data) });
let handles = self.handles.map(|handles| unsafe { ArenaBox::into_ptr(handles) });
(arena, data, handles)
}
}
impl<T> Message<T> {
/// Takes the data from the message, dropping the [`Arena`] and handles
/// array in the process.
pub fn take_data(self) -> Option<T> {
self.data.map(ArenaBox::take)
}
/// Takes the data from the message, dropping the [`Arena`] and handles
/// array in the process.
pub fn take_data_boxed(self) -> Option<Box<T>> {
self.data.map(ArenaBox::take_boxed)
}
}
impl<T> Message<[T]> {
/// Takes the data from the message, dropping the [`Arena`] and handles
/// array in the process.
pub fn take_data_boxed_slice(self) -> Option<Box<[T]>> {
self.data.map(ArenaBox::take_boxed_slice)
}
}
impl<T> Message<MaybeUninit<T>> {
/// Assumes the contents of the data payload of this message are initialized.
///
/// # Safety
///
/// The caller is responsible for ensuring that the value is initialized
/// properly. See [`MaybeUninit::assume_init`] for more details on the
/// safety requirements of this.
pub unsafe fn assume_init(self) -> Message<T> {
// SAFETY: the caller is responsible for ensuring the contents
// of the data pointer are initialized.
self.map_data(|_, data_ptr| unsafe { ArenaBox::assume_init(data_ptr) })
}
}
impl<T> Message<[MaybeUninit<T>]> {
/// Assumes the contents of the data payload of this message are initialized.
///
/// # Safety
///
/// The caller is responsible for ensuring that the value is initialized
/// properly. See [`MaybeUninit::assume_init`] for more details on the
/// safety requirements of this.
pub unsafe fn assume_init(self) -> Message<[T]> {
// SAFETY: the caller is responsible for ensuring the contents
// of the data pointer are initialized.
self.map_data(|_, data_ptr| unsafe { ArenaBox::assume_init_slice(data_ptr) })
}
}
impl Message<[MaybeUninit<u8>]> {
/// Transforms the message body into a message of type `T`
///
/// # Safety
///
/// The caller is responsible for ensuring that the data portion of this
/// message originated from a source with a properly allocated `T` with correct
/// alignment
pub unsafe fn cast_unchecked<T>(self) -> Message<T> {
// SAFETY: the caller is responsible for ensuring the contents
// of the data pointer are an initialized value of `T` and are
// correctly aligned.
self.map_data(|_, data_ptr| unsafe { ArenaBox::cast_unchecked(data_ptr) })
}
}
#[cfg(test)]
mod test {
use crate::channel::Channel;
use fdf_core::handle::MixedHandleType;
use zx::HandleBased;
use super::*;
#[test]
#[should_panic]
fn bad_data_pointer() {
let arena = Arena::new();
let other_arena = Arena::new();
Message::new(&arena, Some(other_arena.insert(1)), None);
}
#[test]
#[should_panic]
fn bad_handle_pointer() {
let arena = Arena::new();
let other_arena = Arena::new();
Message::<()>::new(&arena, None, Some(other_arena.insert_boxed_slice(Box::new([]))));
}
#[test]
fn round_trip_data() {
let arena = Arena::new();
let data = arena.insert(1);
let message = Message::new(&arena, Some(data), None);
let mut arena = None;
let (data, _) = message.into_arena_boxes(&mut arena);
assert_eq!(*data.unwrap(), 1);
}
#[test]
fn round_trip_handles() {
let arena = Arena::new();
let zircon_handle = MixedHandle::from_zircon_handle(zx::Port::create().into_handle());
let (driver_handle1, driver_handle2) = Channel::create();
driver_handle2
.write(Message::new_with_data(arena.clone(), |arena| arena.insert(1)))
.unwrap();
let handles = arena
.insert_boxed_slice(Box::new([zircon_handle, Some(MixedHandle::from(driver_handle1))]));
let message = Message::<()>::new(&arena, None, Some(handles));
let mut arena = None;
let (_, Some(mut handles)) = message.into_arena_boxes(&mut arena) else {
panic!("didn't get handles back");
};
assert_eq!(handles.len(), 2);
let MixedHandleType::Zircon(_zircon_handle) = handles[0].take().unwrap().resolve() else {
panic!("first handle in the handle set wasn't a zircon handle");
};
let MixedHandleType::Driver(driver_handle1) = handles[1].take().unwrap().resolve() else {
panic!("second handle in the handle set wasn't a driver handle");
};
let driver_handle1 = unsafe { Channel::<i32>::from_driver_handle(driver_handle1) };
assert_eq!(driver_handle1.try_read().unwrap().unwrap().data().unwrap(), &1);
}
#[test]
fn map_data() {
let arena = Arena::new();
let data = arena.insert(1);
let message = Message::new(&arena, Some(data), None);
let message = message.map_data(|arena, i| arena.insert(*i + 1));
assert_eq!(message.data().unwrap(), &2);
}
}