fdf/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, ArenaBox, MixedHandle};
8use core::marker::PhantomData;
9use core::mem::MaybeUninit;
10use core::ops::{Deref, DerefMut};
11use core::ptr::NonNull;
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_ref().map(ArenaBox::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_mut().map(ArenaBox::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_ref().map(ArenaBox::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_mut().map(ArenaBox::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 (
168 &self.arena,
169 self.data.as_ref().map(ArenaBox::deref),
170 self.handles.as_ref().map(ArenaBox::deref),
171 )
172 }
173
174 /// Gets a reference to the arena and mutable references to the data handles of the message
175 pub fn as_mut_refs(&mut self) -> (&Arena, Option<&mut T>, Option<&mut [Option<MixedHandle>]>) {
176 (
177 &self.arena,
178 self.data.as_mut().map(ArenaBox::deref_mut),
179 self.handles.as_mut().map(ArenaBox::deref_mut),
180 )
181 }
182
183 /// Unpacks the arena and buffers in this message to the caller.
184 ///
185 /// The `arena` argument provides a place to put the [`Arena`] from this message
186 /// in the local lifetime of the caller so that the [`ArenaBox`]es can be tied to
187 /// its lifetime.
188 pub fn into_arena_boxes<'a>(
189 self,
190 arena: &'a mut Option<Arena>,
191 ) -> (Option<ArenaBox<'a, T>>, Option<ArenaBox<'a, [Option<MixedHandle>]>>) {
192 arena.replace(self.arena);
193 // SAFETY: the lifetime we're giving these [`ArenaBox`]es is the same one
194 // as the lifetime of the place we're putting the [`Arena`] they belong to.
195 let data = self.data.map(|ptr| unsafe { ArenaBox::erase_lifetime(ptr) });
196 let handles = self.handles.map(|ptr| unsafe { ArenaBox::erase_lifetime(ptr) });
197 (data, handles)
198 }
199
200 /// Takes the `ArenaBox`es for the data and handles from this [`Message`], but leaves
201 /// the [`Arena`] in the [`Message`] to act as a holder of the arena lifetime.
202 pub fn take_arena_boxes(
203 &mut self,
204 ) -> (&Arena, Option<ArenaBox<'_, T>>, Option<ArenaBox<'_, [Option<MixedHandle>]>>) {
205 (&self.arena, self.data.take(), self.handles.take())
206 }
207
208 /// Unpacks the arena and buffers into raw pointers
209 ///
210 /// Care must be taken to ensure that the data and handle pointers are not used
211 /// if the arena is freed. If they are never reconstituted into a [`Message`]
212 /// or an [`Arena`] and [`ArenaBox`]es, they will be leaked.
213 pub fn into_raw(
214 self,
215 ) -> (NonNull<fdf_arena_t>, Option<NonNull<T>>, Option<NonNull<[Option<MixedHandle>]>>) {
216 let arena = self.arena.into_raw();
217 // SAFETY: the arena and the pointers we're returning will all have the same
218 // effectively 'static lifetime, and it is up to the caller to make sure that
219 // they free them in the correct order.
220 let data = self.data.map(|data| unsafe { ArenaBox::into_ptr(data) });
221 let handles = self.handles.map(|handles| unsafe { ArenaBox::into_ptr(handles) });
222 (arena, data, handles)
223 }
224}
225
226impl<T> Message<T> {
227 /// Takes the data from the message, dropping the [`Arena`] and handles
228 /// array in the process.
229 pub fn take_data(self) -> Option<T> {
230 self.data.map(ArenaBox::take)
231 }
232
233 /// Takes the data from the message, dropping the [`Arena`] and handles
234 /// array in the process.
235 pub fn take_data_boxed(self) -> Option<Box<T>> {
236 self.data.map(ArenaBox::take_boxed)
237 }
238}
239
240impl<T> Message<[T]> {
241 /// Takes the data from the message, dropping the [`Arena`] and handles
242 /// array in the process.
243 pub fn take_data_boxed_slice(self) -> Option<Box<[T]>> {
244 self.data.map(ArenaBox::take_boxed_slice)
245 }
246}
247
248impl<T> Message<MaybeUninit<T>> {
249 /// Assumes the contents of the data payload of this message are initialized.
250 ///
251 /// # Safety
252 ///
253 /// The caller is responsible for ensuring that the value is initialized
254 /// properly. See [`MaybeUninit::assume_init`] for more details on the
255 /// safety requirements of this.
256 pub unsafe fn assume_init(self) -> Message<T> {
257 // SAFETY: the caller is responsible for ensuring the contents
258 // of the data pointer are initialized.
259 self.map_data(|_, data_ptr| unsafe { ArenaBox::assume_init(data_ptr) })
260 }
261}
262
263impl<T> Message<[MaybeUninit<T>]> {
264 /// Assumes the contents of the data payload of this message are initialized.
265 ///
266 /// # Safety
267 ///
268 /// The caller is responsible for ensuring that the value is initialized
269 /// properly. See [`MaybeUninit::assume_init`] for more details on the
270 /// safety requirements of this.
271 pub unsafe fn assume_init(self) -> Message<[T]> {
272 // SAFETY: the caller is responsible for ensuring the contents
273 // of the data pointer are initialized.
274 self.map_data(|_, data_ptr| unsafe { ArenaBox::assume_init_slice(data_ptr) })
275 }
276}
277
278impl Message<[MaybeUninit<u8>]> {
279 /// Transforms the message body into a message of type `T`
280 ///
281 /// # Safety
282 ///
283 /// The caller is responsible for ensuring that the data portion of this
284 /// message originated from a source with a properly allocated `T` with correct
285 /// alignment
286 pub unsafe fn cast_unchecked<T>(self) -> Message<T> {
287 // SAFETY: the caller is responsible for ensuring the contents
288 // of the data pointer are an initialized value of `T` and are
289 // correctly aligned.
290 self.map_data(|_, data_ptr| unsafe { ArenaBox::cast_unchecked(data_ptr) })
291 }
292}
293
294#[cfg(test)]
295mod test {
296 use zx::HandleBased;
297
298 use super::*;
299 use crate::*;
300
301 #[test]
302 #[should_panic]
303 fn bad_data_pointer() {
304 let arena = Arena::new();
305 let other_arena = Arena::new();
306 Message::new(&arena, Some(other_arena.insert(1)), None);
307 }
308
309 #[test]
310 #[should_panic]
311 fn bad_handle_pointer() {
312 let arena = Arena::new();
313 let other_arena = Arena::new();
314 Message::<()>::new(&arena, None, Some(other_arena.insert_boxed_slice(Box::new([]))));
315 }
316
317 #[test]
318 fn round_trip_data() {
319 let arena = Arena::new();
320 let data = arena.insert(1);
321 let message = Message::new(&arena, Some(data), None);
322 let mut arena = None;
323 let (data, _) = message.into_arena_boxes(&mut arena);
324 assert_eq!(*data.unwrap(), 1);
325 }
326
327 #[test]
328 fn round_trip_handles() {
329 let arena = Arena::new();
330 let zircon_handle = MixedHandle::from_zircon_handle(zx::Port::create().into_handle());
331 let (driver_handle1, driver_handle2) = Channel::create();
332 driver_handle2
333 .write(Message::new_with_data(arena.clone(), |arena| arena.insert(1)))
334 .unwrap();
335
336 let handles = arena
337 .insert_boxed_slice(Box::new([zircon_handle, Some(MixedHandle::from(driver_handle1))]));
338 let message = Message::<()>::new(&arena, None, Some(handles));
339
340 let mut arena = None;
341 let (_, Some(mut handles)) = message.into_arena_boxes(&mut arena) else {
342 panic!("didn't get handles back");
343 };
344 assert_eq!(handles.len(), 2);
345 let MixedHandleType::Zircon(_zircon_handle) = handles[0].take().unwrap().resolve() else {
346 panic!("first handle in the handle set wasn't a zircon handle");
347 };
348 let MixedHandleType::Driver(driver_handle1) = handles[1].take().unwrap().resolve() else {
349 panic!("second handle in the handle set wasn't a driver handle");
350 };
351 let driver_handle1 = unsafe { Channel::<i32>::from_driver_handle(driver_handle1) };
352 assert_eq!(driver_handle1.try_read().unwrap().unwrap().data().unwrap(), &1);
353 }
354
355 #[test]
356 fn map_data() {
357 let arena = Arena::new();
358 let data = arena.insert(1);
359 let message = Message::new(&arena, Some(data), None);
360 let message = message.map_data(|arena, i| arena.insert(*i + 1));
361 assert_eq!(message.data().unwrap(), &2);
362 }
363}