fdf_channel/
message.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A helper for managing a self-contained arena-allocated buffer along with its arena handle.
6
7use crate::arena::{Arena, ArenaBox};
8use core::marker::PhantomData;
9use core::mem::MaybeUninit;
10use core::ptr::NonNull;
11use fdf_core::handle::MixedHandle;
12
13use fdf_sys::*;
14
15/// A struct that holds both an arena along with a data buffer that is allocated within that arena.
16#[derive(Debug)]
17pub struct Message<T: ?Sized + 'static> {
18    data: Option<ArenaBox<'static, T>>,
19    // note: `[Option<MixedHandle>]` is byte-equivalent to a C array of `fdf_handle_t`.
20    handles: Option<ArenaBox<'static, [Option<MixedHandle>]>>,
21    // note: this must maintain its position as the last item of the struct
22    // to ensure that it is freed after the data and handle pointers.
23    arena: Arena,
24    _p: PhantomData<T>,
25}
26
27impl<T: ?Sized> Message<T> {
28    /// Consumes the given arena, data buffer, and handles buffers and returns a message that holds
29    /// all three.
30    ///
31    /// # Panics
32    ///
33    /// This function panics if either of the [`ArenaBox`]s are not allocated by [`Arena`].
34    pub fn new<'a>(
35        arena: &'a Arena,
36        data: Option<ArenaBox<'a, T>>,
37        handles: Option<ArenaBox<'a, [Option<MixedHandle>]>>,
38    ) -> Self {
39        let data = data.map(|data| {
40            assert!(
41                arena.contains(&data),
42                "Data buffer pointer is not in the arena being included in the message"
43            );
44            // SAFETY: we will store this ArenaBox with a clone of the Arena that
45            // owns it.
46            unsafe { ArenaBox::erase_lifetime(data) }
47        });
48        let handles = handles.map(|handles| {
49            assert!(
50                arena.contains(&handles),
51                "Handle buffer pointer is not in the arena being included in the message"
52            );
53            // SAFETY: we will store this ArenaBox with a clone of the Arena that
54            // owns it.
55            unsafe { ArenaBox::erase_lifetime(handles) }
56        });
57        // SAFETY: We just checked that both boxes were allocated from the arena.
58        unsafe { Self::new_unchecked(arena.clone(), data, handles) }
59    }
60
61    /// Given the [`Arena`], allocates a new [`Message`] and runs the given `f` to allow the caller
62    /// to allocate data and handles into the [`Message`] without requiring a check that the
63    /// correct [`Arena`] was used to allocate the data.
64    ///
65    /// Note that it may be possible to sneak an `ArenaBox<'static, T>` into this [`Message`]
66    /// object with this function by using an [`Arena`] with static lifetime to allocate it. This
67    /// will not cause any unsoundness, but if you try to send that message through a [`Channel`]
68    /// it will cause a runtime error when the arenas don't match.
69    pub fn new_with<F>(arena: Arena, f: F) -> Self
70    where
71        F: for<'a> FnOnce(
72            &'a Arena,
73        )
74            -> (Option<ArenaBox<'a, T>>, Option<ArenaBox<'a, [Option<MixedHandle>]>>),
75    {
76        let (data, handles) = f(&arena);
77        // SAFETY: The `for<'a>` in the callback definition makes it so that the caller must
78        // (without resorting to unsafe themselves) allocate the [`ArenaBox`] from the given
79        // [`Arena`].
80        Self {
81            data: data.map(|data| unsafe { ArenaBox::erase_lifetime(data) }),
82            handles: handles.map(|handles| unsafe { ArenaBox::erase_lifetime(handles) }),
83            arena,
84            _p: PhantomData,
85        }
86    }
87
88    /// A shorthand for [`Self::new_with`] when there's definitely a data body and nothing else.
89    pub fn new_with_data<F>(arena: Arena, f: F) -> Self
90    where
91        F: for<'a> FnOnce(&'a Arena) -> ArenaBox<'a, T>,
92    {
93        // SAFETY: The `for<'a>` in the callback definition makes it so that the caller must
94        // (without resorting to unsafe themselves) allocate the [`ArenaBox`] from the given
95        // [`Arena`].
96        let data = Some(unsafe { ArenaBox::erase_lifetime(f(&arena)) });
97        Self { data, handles: None, arena, _p: PhantomData }
98    }
99
100    /// As with [`Self::new`], this consumes the arguments to produce a message object that holds
101    /// all of them together to be extracted again later, but does not validate that the pointers
102    /// came from the same arena.
103    ///
104    /// # Safety
105    ///
106    /// The caller is responsible for:
107    /// - ensuring that the [`ArenaBox`]es came from the same arena as is being passed in to this
108    ///   function, or the erased lifetime of the arena boxes might cause use-after-free.
109    /// - the bytes in `data` are actually of type `T`, and are properly aligned for type `T`.
110    pub(crate) unsafe fn new_unchecked(
111        arena: Arena,
112        data_ptr: Option<ArenaBox<'static, T>>,
113        handles_ptr: Option<ArenaBox<'static, [Option<MixedHandle>]>>,
114    ) -> Self {
115        Self { arena, data: data_ptr, handles: handles_ptr, _p: PhantomData }
116    }
117
118    /// Gets a reference to the arena this message was allocated with.
119    pub fn arena(&self) -> &Arena {
120        &self.arena
121    }
122
123    /// Takes the arena and drops any data or handle bodies held in this message
124    pub fn take_arena(self) -> Arena {
125        self.arena
126    }
127
128    /// Gets a reference to the data in this message, if there is any
129    pub fn data(&self) -> Option<&T> {
130        self.data.as_deref()
131    }
132
133    /// Gets a mutable reference to the data in this message, if there is any
134    pub fn data_mut(&mut self) -> Option<&mut T> {
135        self.data.as_deref_mut()
136    }
137
138    /// Maps the message data to a new [`ArenaBox`] based on the arena and the old data.
139    pub fn map_data<F, R: ?Sized>(self, f: F) -> Message<R>
140    where
141        F: for<'a> FnOnce(&'a Arena, ArenaBox<'a, T>) -> ArenaBox<'a, R>,
142    {
143        let Self { arena, data: data_ptr, handles: handles_ptr, .. } = self;
144        let data_ptr = data_ptr.map(|data_ptr| {
145            // SAFETY: The `ArenaBox` being returned is tied to the lifetime
146            // of the arena we gave the closure, and we will now be moving
147            // into the new `Message`. So just like the old one,
148            // the new box is tied to the life of the message and the arena
149            // within it.
150            unsafe { ArenaBox::erase_lifetime(f(&arena, data_ptr)) }
151        });
152        Message { arena, data: data_ptr, handles: handles_ptr, _p: PhantomData }
153    }
154
155    /// Gets a reference to the handles array in this message, if there is one.
156    pub fn handles(&self) -> Option<&[Option<MixedHandle>]> {
157        self.handles.as_deref()
158    }
159
160    /// Gets a mutable reference to the handles array in this message, if there is one.
161    pub fn handles_mut(&mut self) -> Option<&mut [Option<MixedHandle>]> {
162        self.handles.as_deref_mut()
163    }
164
165    /// Gets a reference to all three of the arena, data, and handles of the message
166    pub fn as_refs(&self) -> (&Arena, Option<&T>, Option<&[Option<MixedHandle>]>) {
167        (&self.arena, self.data.as_deref(), self.handles.as_deref())
168    }
169
170    /// Gets a reference to the arena and mutable references to the data handles of the message
171    pub fn as_mut_refs(&mut self) -> (&Arena, Option<&mut T>, Option<&mut [Option<MixedHandle>]>) {
172        (&self.arena, self.data.as_deref_mut(), self.handles.as_deref_mut())
173    }
174
175    /// Unpacks the arena and buffers in this message to the caller.
176    ///
177    /// The `arena` argument provides a place to put the [`Arena`] from this message
178    /// in the local lifetime of the caller so that the [`ArenaBox`]es can be tied to
179    /// its lifetime.
180    #[expect(clippy::type_complexity)]
181    pub fn into_arena_boxes<'a>(
182        self,
183        arena: &'a mut Option<Arena>,
184    ) -> (Option<ArenaBox<'a, T>>, Option<ArenaBox<'a, [Option<MixedHandle>]>>) {
185        arena.replace(self.arena);
186        // SAFETY: the lifetime we're giving these [`ArenaBox`]es is the same one
187        // as the lifetime of the place we're putting the [`Arena`] they belong to.
188        let data = self.data.map(|ptr| unsafe { ArenaBox::erase_lifetime(ptr) });
189        let handles = self.handles.map(|ptr| unsafe { ArenaBox::erase_lifetime(ptr) });
190        (data, handles)
191    }
192
193    /// Takes the `ArenaBox`es for the data and handles from this [`Message`], but leaves
194    /// the [`Arena`] in the [`Message`] to act as a holder of the arena lifetime.
195    #[expect(clippy::type_complexity)]
196    pub fn take_arena_boxes(
197        &mut self,
198    ) -> (&'_ Arena, Option<ArenaBox<'_, T>>, Option<ArenaBox<'_, [Option<MixedHandle>]>>) {
199        (&self.arena, self.data.take(), self.handles.take())
200    }
201
202    /// Unpacks the arena and buffers into raw pointers
203    ///
204    /// Care must be taken to ensure that the data and handle pointers are not used
205    /// if the arena is freed. If they are never reconstituted into a [`Message`]
206    /// or an [`Arena`] and [`ArenaBox`]es, they will be leaked.
207    #[expect(clippy::type_complexity)]
208    pub fn into_raw(
209        self,
210    ) -> (NonNull<fdf_arena_t>, Option<NonNull<T>>, Option<NonNull<[Option<MixedHandle>]>>) {
211        let arena = self.arena.into_raw();
212        // SAFETY: the arena and the pointers we're returning will all have the same
213        // effectively 'static lifetime, and it is up to the caller to make sure that
214        // they free them in the correct order.
215        let data = self.data.map(|data| unsafe { ArenaBox::into_ptr(data) });
216        let handles = self.handles.map(|handles| unsafe { ArenaBox::into_ptr(handles) });
217        (arena, data, handles)
218    }
219}
220
221impl<T> Message<T> {
222    /// Takes the data from the message, dropping the [`Arena`] and handles
223    /// array in the process.
224    pub fn take_data(self) -> Option<T> {
225        self.data.map(ArenaBox::take)
226    }
227
228    /// Takes the data from the message, dropping the [`Arena`] and handles
229    /// array in the process.
230    pub fn take_data_boxed(self) -> Option<Box<T>> {
231        self.data.map(ArenaBox::take_boxed)
232    }
233}
234
235impl<T> Message<[T]> {
236    /// Takes the data from the message, dropping the [`Arena`] and handles
237    /// array in the process.
238    pub fn take_data_boxed_slice(self) -> Option<Box<[T]>> {
239        self.data.map(ArenaBox::take_boxed_slice)
240    }
241}
242
243impl<T> Message<MaybeUninit<T>> {
244    /// Assumes the contents of the data payload of this message are initialized.
245    ///
246    /// # Safety
247    ///
248    /// The caller is responsible for ensuring that the value is initialized
249    /// properly. See [`MaybeUninit::assume_init`] for more details on the
250    /// safety requirements of this.
251    pub unsafe fn assume_init(self) -> Message<T> {
252        // SAFETY: the caller is responsible for ensuring the contents
253        // of the data pointer are initialized.
254        self.map_data(|_, data_ptr| unsafe { ArenaBox::assume_init(data_ptr) })
255    }
256}
257
258impl<T> Message<[MaybeUninit<T>]> {
259    /// Assumes the contents of the data payload of this message are initialized.
260    ///
261    /// # Safety
262    ///
263    /// The caller is responsible for ensuring that the value is initialized
264    /// properly. See [`MaybeUninit::assume_init`] for more details on the
265    /// safety requirements of this.
266    pub unsafe fn assume_init(self) -> Message<[T]> {
267        // SAFETY: the caller is responsible for ensuring the contents
268        // of the data pointer are initialized.
269        self.map_data(|_, data_ptr| unsafe { ArenaBox::assume_init_slice(data_ptr) })
270    }
271}
272
273impl Message<[MaybeUninit<u8>]> {
274    /// Transforms the message body into a message of type `T`
275    ///
276    /// # Safety
277    ///
278    /// The caller is responsible for ensuring that the data portion of this
279    /// message originated from a source with a properly allocated `T` with correct
280    /// alignment
281    pub unsafe fn cast_unchecked<T>(self) -> Message<T> {
282        // SAFETY: the caller is responsible for ensuring the contents
283        // of the data pointer are an initialized value of `T` and are
284        // correctly aligned.
285        self.map_data(|_, data_ptr| unsafe { ArenaBox::cast_unchecked(data_ptr) })
286    }
287}
288
289#[cfg(test)]
290mod test {
291    use crate::channel::Channel;
292    use fdf_core::handle::MixedHandleType;
293    use zx::HandleBased;
294
295    use super::*;
296
297    #[test]
298    #[should_panic]
299    fn bad_data_pointer() {
300        let arena = Arena::new();
301        let other_arena = Arena::new();
302        Message::new(&arena, Some(other_arena.insert(1)), None);
303    }
304
305    #[test]
306    #[should_panic]
307    fn bad_handle_pointer() {
308        let arena = Arena::new();
309        let other_arena = Arena::new();
310        Message::<()>::new(&arena, None, Some(other_arena.insert_boxed_slice(Box::new([]))));
311    }
312
313    #[test]
314    fn round_trip_data() {
315        let arena = Arena::new();
316        let data = arena.insert(1);
317        let message = Message::new(&arena, Some(data), None);
318        let mut arena = None;
319        let (data, _) = message.into_arena_boxes(&mut arena);
320        assert_eq!(*data.unwrap(), 1);
321    }
322
323    #[test]
324    fn round_trip_handles() {
325        let arena = Arena::new();
326        let zircon_handle = MixedHandle::from_zircon_handle(zx::Port::create().into_handle());
327        let (driver_handle1, driver_handle2) = Channel::create();
328        driver_handle2
329            .write(Message::new_with_data(arena.clone(), |arena| arena.insert(1)))
330            .unwrap();
331
332        let handles = arena
333            .insert_boxed_slice(Box::new([zircon_handle, Some(MixedHandle::from(driver_handle1))]));
334        let message = Message::<()>::new(&arena, None, Some(handles));
335
336        let mut arena = None;
337        let (_, Some(mut handles)) = message.into_arena_boxes(&mut arena) else {
338            panic!("didn't get handles back");
339        };
340        assert_eq!(handles.len(), 2);
341        let MixedHandleType::Zircon(_zircon_handle) = handles[0].take().unwrap().resolve() else {
342            panic!("first handle in the handle set wasn't a zircon handle");
343        };
344        let MixedHandleType::Driver(driver_handle1) = handles[1].take().unwrap().resolve() else {
345            panic!("second handle in the handle set wasn't a driver handle");
346        };
347        let driver_handle1 = unsafe { Channel::<i32>::from_driver_handle(driver_handle1) };
348        assert_eq!(driver_handle1.try_read().unwrap().unwrap().data().unwrap(), &1);
349    }
350
351    #[test]
352    fn map_data() {
353        let arena = Arena::new();
354        let data = arena.insert(1);
355        let message = Message::new(&arena, Some(data), None);
356        let message = message.map_data(|arena, i| arena.insert(*i + 1));
357        assert_eq!(message.data().unwrap(), &2);
358    }
359}