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}