fidl_driver/
encoding.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
5use fidl::encoding::{
6    ALLOC_ABSENT_U32, ALLOC_PRESENT_U32, Context, Decode, Decoder, DefaultFuchsiaResourceDialect,
7    Depth, Encode, Encoder, ResourceTypeMarker, TypeMarker,
8};
9
10use fdf::{Channel, MixedHandle, MixedHandleType};
11use zx::{HandleDisposition, HandleInfo, HandleOp, NullableHandle, ObjectType, Rights, Status};
12
13use std::marker::PhantomData;
14
15use crate::endpoints::DriverClientEnd;
16
17#[derive(Debug, Clone)]
18pub struct DriverEndpoint<T>(PhantomData<DriverClientEnd<T>>);
19
20unsafe impl<T: 'static> TypeMarker for DriverEndpoint<T> {
21    type Owned = DriverClientEnd<T>;
22
23    fn inline_align(_context: Context) -> usize {
24        4
25    }
26
27    fn inline_size(_context: Context) -> usize {
28        4
29    }
30}
31
32impl<T: 'static> ResourceTypeMarker for DriverEndpoint<T> {
33    type Borrowed<'a> = DriverClientEnd<T>;
34
35    fn take_or_borrow(value: &mut Self::Owned) -> Self::Borrowed<'_> {
36        DriverClientEnd(value.0.take(), PhantomData)
37    }
38}
39
40impl<T: 'static> Decode<DriverEndpoint<T>, DefaultFuchsiaResourceDialect> for DriverClientEnd<T> {
41    fn new_empty() -> Self {
42        Self(None, PhantomData)
43    }
44
45    unsafe fn decode(
46        &mut self,
47        decoder: &mut Decoder<'_, DefaultFuchsiaResourceDialect>,
48        offset: usize,
49        _depth: Depth,
50    ) -> fidl::Result<()> {
51        match decoder.read_num::<u32>(offset) {
52            ALLOC_PRESENT_U32 => {}
53            ALLOC_ABSENT_U32 => return Err(fidl::Error::NotNullable),
54            _ => return Err(fidl::Error::InvalidPresenceIndicator),
55        }
56        // Take what the decoder thinks is a NONE-type zircon handle and validate that it's
57        // a driver handle and use that.
58        let handle = decoder.take_next_handle(ObjectType::NONE, Rights::empty())?.into_raw();
59        let mixed_handle = unsafe { MixedHandle::try_from_raw(handle) };
60        match mixed_handle.map(MixedHandle::resolve) {
61            None => (),
62            Some(MixedHandleType::Driver(driver_handle)) => {
63                // SAFETY: we own this handle as the receiver of the fidl message that contained it.
64                self.0 = unsafe { Some(Channel::from_driver_handle(driver_handle)) };
65            }
66            _ => {
67                // The handle was present but not a driver handle, so is not valid.
68                return Err(fidl::Error::Invalid);
69            }
70        }
71        Ok(())
72    }
73}
74
75unsafe impl<T: 'static> Encode<DriverEndpoint<T>, DefaultFuchsiaResourceDialect>
76    for DriverClientEnd<T>
77{
78    unsafe fn encode(
79        self,
80        encoder: &mut Encoder<'_, DefaultFuchsiaResourceDialect>,
81        offset: usize,
82        _depth: Depth,
83    ) -> fidl::Result<()> {
84        let Some(channel) = self.0 else {
85            return Err(fidl::Error::NotNullable);
86        };
87        // SAFETY: we are taking the handle and converting it into what looks like a zircon
88        // handle for the benefit of the fidl encoding library. If something tries to close it,
89        // it will cause an error and leak the driver handle but it will not cause any other
90        // unsoundness.
91        let handle = unsafe {
92            fidl::NullableHandle::from_raw(channel.into_driver_handle().into_raw().get())
93        };
94
95        // SAFETY: the caller is responsible for ensuring that there's enough space for this
96        // value.
97        unsafe { encoder.write_num(ALLOC_PRESENT_U32, offset) };
98        encoder.push_next_handle(HandleDisposition::new(
99            HandleOp::Move(handle),
100            ObjectType::NONE,
101            Rights::empty(),
102            Status::OK,
103        ));
104        Ok(())
105    }
106}
107
108/// Converts the handle to a [`HandleInfo`], with [`DriverHandle`]s being represented as
109/// a valid driver handle but [`ObjectType::NONE`].
110///
111/// This uses [`Handle::basic_info`] to get the object type and rights for a zircon
112/// handle, so may return an error if that fails.
113///
114/// # Safety
115///
116/// The caller is responsible for making sure that the handle is either converted
117/// back to a [`MixedHandle`] or the correct cleanup happens, or an error or leak
118/// may happen when the [`HandleInfo`] is dropped.
119pub unsafe fn mixed_into_handle_info(this: Option<MixedHandle>) -> Result<HandleInfo, Status> {
120    use MixedHandleType::*;
121    let Some(this) = this else {
122        return Ok(HandleInfo::new(NullableHandle::invalid(), ObjectType::NONE, Rights::empty()));
123    };
124    match this.resolve() {
125        Zircon(handle) => {
126            let basic_info = handle.basic_info()?;
127            Ok(HandleInfo::new(handle, basic_info.object_type, basic_info.rights))
128        }
129        Driver(handle) => {
130            // SAFETY: we are wrapping the technically invalid handle in a `ManuallyDrop`
131            // to prevent it from being dropped.
132            Ok(HandleInfo::new(
133                unsafe { NullableHandle::from_raw(handle.into_raw().get()) },
134                ObjectType::NONE,
135                Rights::empty(),
136            ))
137        }
138    }
139}
140
141/// Converts a [`HandleDisposition`] to a [`MixedHandle`], relying on the bit pattern of
142/// the handle to correctly identify [`Handle`] vs. [`fdf::DriverHandle`].
143///
144/// # Panics
145///
146/// This panics if the handle's op is not [`zx::HandleOp::Move`]
147pub fn mixed_from_handle_disposition(
148    mut handle: HandleDisposition<'static>,
149) -> Option<MixedHandle> {
150    use zx::HandleOp::*;
151    match handle.take_op() {
152        Move(handle) => MixedHandle::from_zircon_handle(handle),
153        Duplicate(_) => {
154            panic!("tried to convert a duplicate HandleDisposition into a driver MixedHandle")
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use fdf::DriverHandle;
162    use zx::Port;
163
164    use super::*;
165
166    /// Creates a valid `DriverHandle` by creating a driver channel pair and returning one of them.
167    fn make_driver_handle() -> DriverHandle {
168        let (left, right) = fdf::Channel::<()>::create();
169        drop(right);
170        left.into_driver_handle()
171    }
172
173    #[test]
174    fn driver_handle_info() {
175        let handle = MixedHandle::from(make_driver_handle());
176        let handle_info = unsafe { mixed_into_handle_info(Some(handle)).unwrap() };
177        assert_eq!(handle_info.object_type, ObjectType::NONE);
178        assert_eq!(handle_info.rights, Rights::empty());
179        // take the handle back to a mixed handle so it gets dropped properly
180        MixedHandle::from_zircon_handle(handle_info.handle).unwrap();
181    }
182
183    #[test]
184    fn driver_handle_disposition() {
185        let handle_disposition = HandleDisposition::new(
186            HandleOp::Move(unsafe {
187                NullableHandle::from_raw(make_driver_handle().into_raw().get())
188            }),
189            ObjectType::NONE,
190            Rights::empty(),
191            Status::OK,
192        );
193        mixed_from_handle_disposition(handle_disposition).unwrap();
194    }
195
196    #[test]
197    fn zircon_handle_info() {
198        let handle = MixedHandle::from_zircon_handle(Port::create().into()).unwrap();
199        let handle_info = unsafe { mixed_into_handle_info(Some(handle)).unwrap() };
200        assert_eq!(handle_info.object_type, ObjectType::PORT);
201        assert_eq!(
202            handle_info.rights,
203            Rights::DUPLICATE | Rights::TRANSFER | Rights::READ | Rights::WRITE | Rights::INSPECT
204        );
205        // take the handle back to a mixed handle so it gets dropped properly
206        MixedHandle::from_zircon_handle(handle_info.handle).unwrap();
207    }
208
209    #[test]
210    fn zircon_handle_disposition() {
211        let handle_disposition = HandleDisposition::new(
212            HandleOp::Move(Port::create().into()),
213            ObjectType::PORT,
214            Rights::DUPLICATE | Rights::TRANSFER | Rights::READ | Rights::WRITE | Rights::INSPECT,
215            Status::OK,
216        );
217        mixed_from_handle_disposition(handle_disposition).unwrap();
218    }
219}