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