fidl_next_codec/
decoder.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//! The core [`Decoder`] trait.
6
7use core::mem::take;
8use core::ptr::NonNull;
9use core::slice;
10
11use crate::{CHUNK_SIZE, Chunk, Constrained, Decode, DecodeError, Decoded, Slot};
12
13/// A decoder for FIDL handles (internal).
14pub trait InternalHandleDecoder {
15    /// Takes the next `count` handles from the decoder.
16    ///
17    /// This method exposes details about Fuchsia resources that plain old FIDL shouldn't need to
18    /// know about. Do not use this method outside of this crate.
19    #[doc(hidden)]
20    fn __internal_take_handles(&mut self, count: usize) -> Result<(), DecodeError>;
21
22    /// Returns the number of handles remaining in the decoder.
23    ///
24    /// This method exposes details about Fuchsia resources that plain old FIDL shouldn't need to
25    /// know about. Do not use this method outside of this crate.
26    #[doc(hidden)]
27    fn __internal_handles_remaining(&self) -> usize;
28}
29
30/// A decoder for FIDL messages.
31///
32/// # Safety
33///
34/// Pointers returned from `take_chunks` must:
35///
36/// - Point to `count` initialized `Chunk`s
37/// - Be valid for reads and writes
38/// - Remain valid until the decoder is dropped
39///
40/// The decoder **may be moved** without invalidating the returned pointers.
41pub unsafe trait Decoder: InternalHandleDecoder {
42    /// Takes a slice of `Chunk`s from the decoder, returning a pointer to them.
43    ///
44    /// Returns `Err` if the decoder doesn't have enough chunks left.
45    fn take_chunks_raw(&mut self, count: usize) -> Result<NonNull<Chunk>, DecodeError>;
46
47    /// Commits to any decoding operations which are in progress.
48    ///
49    /// Resources like handles may be taken from a decoder during decoding. However, decoding may
50    /// fail after those resources are taken but before decoding completes. To ensure that resources
51    /// are always dropped, taken resources are still considered owned by the decoder until `commit`
52    /// is called. After `commit`, ownership of those resources is transferred to the decoded data.
53    fn commit(&mut self);
54
55    /// Verifies that decoding finished cleanly, with no leftover chunks or resources.
56    fn finish(&self) -> Result<(), DecodeError>;
57}
58
59impl InternalHandleDecoder for &mut [Chunk] {
60    #[inline]
61    fn __internal_take_handles(&mut self, _: usize) -> Result<(), DecodeError> {
62        Err(DecodeError::InsufficientHandles)
63    }
64
65    #[inline]
66    fn __internal_handles_remaining(&self) -> usize {
67        0
68    }
69}
70
71unsafe impl Decoder for &mut [Chunk] {
72    #[inline]
73    fn take_chunks_raw(&mut self, count: usize) -> Result<NonNull<Chunk>, DecodeError> {
74        if count > self.len() {
75            return Err(DecodeError::InsufficientData);
76        }
77
78        let chunks = take(self);
79        let (prefix, suffix) = unsafe { chunks.split_at_mut_unchecked(count) };
80        *self = suffix;
81        unsafe { Ok(NonNull::new_unchecked(prefix.as_mut_ptr())) }
82    }
83
84    #[inline]
85    fn commit(&mut self) {
86        // No resources to take, so commit is a no-op
87    }
88
89    #[inline]
90    fn finish(&self) -> Result<(), DecodeError> {
91        if !self.is_empty() {
92            return Err(DecodeError::ExtraBytes { num_extra: self.len() * CHUNK_SIZE });
93        }
94
95        Ok(())
96    }
97}
98
99/// Extension methods for [`Decoder`].
100pub trait DecoderExt {
101    /// Takes a slice of `Chunk`s from the decoder.
102    fn take_chunks<'de>(
103        self: &mut &'de mut Self,
104        count: usize,
105    ) -> Result<&'de mut [Chunk], DecodeError>;
106
107    /// Takes enough chunks for a `T`, returning a `Slot` of the taken value.
108    fn take_slot<'de, T>(self: &mut &'de mut Self) -> Result<Slot<'de, T>, DecodeError>;
109
110    /// Takes enough chunks for a slice of `T`, returning a `Slot` of the taken slice.
111    fn take_slice_slot<'de, T>(
112        self: &mut &'de mut Self,
113        len: usize,
114    ) -> Result<Slot<'de, [T]>, DecodeError>;
115
116    /// Decodes an `Owned` value from the decoder without finishing it.
117    ///
118    /// On success, returns `Ok` of an `Owned` value. Returns `Err` if decoding failed.
119    fn decode_owned<'de, T: Decode<Self> + Constrained<Constraint = ()>>(
120        self: &mut &'de mut Self,
121    ) -> Result<T::Owned<'de>, DecodeError>;
122
123    /// Decodes a value from the decoder and finishes it.
124    ///
125    /// On success, returns `Ok` of a `Decoded` value with the decoder. Returns `Err` if decoding
126    /// failed or the decoder finished with an error.
127    fn decode<T>(self) -> Result<Decoded<T, Self>, DecodeError>
128    where
129        T: Decode<Self> + Constrained<Constraint = ()>,
130        Self: Sized;
131
132    /// Decodes a value from the decoder and finishes it.
133    ///
134    /// On success, returns `Ok` of a `Decoded` value with the decoder. Returns `Err` if decoding
135    /// failed or the decoder finished with an error.
136    fn decode_with_constraint<T>(
137        self,
138        constraint: <T as Constrained>::Constraint,
139    ) -> Result<Decoded<T, Self>, DecodeError>
140    where
141        T: Decode<Self>,
142        Self: Sized;
143}
144
145impl<D: Decoder + ?Sized> DecoderExt for D {
146    fn take_chunks<'de>(
147        self: &mut &'de mut Self,
148        count: usize,
149    ) -> Result<&'de mut [Chunk], DecodeError> {
150        self.take_chunks_raw(count).map(|p| unsafe { slice::from_raw_parts_mut(p.as_ptr(), count) })
151    }
152
153    fn take_slot<'de, T>(self: &mut &'de mut Self) -> Result<Slot<'de, T>, DecodeError> {
154        // TODO: might be able to move this into a const for guaranteed const
155        // eval
156        assert!(
157            align_of::<T>() <= CHUNK_SIZE,
158            "attempted to take a slot for a type with an alignment higher \
159             than {CHUNK_SIZE}",
160        );
161
162        let count = size_of::<T>().div_ceil(CHUNK_SIZE);
163        let chunks = self.take_chunks(count)?;
164        // SAFETY: `result` is at least 8-aligned and points to at least enough
165        // bytes for a `T`.
166        unsafe { Ok(Slot::new_unchecked(chunks.as_mut_ptr().cast())) }
167    }
168
169    fn take_slice_slot<'de, T>(
170        self: &mut &'de mut Self,
171        len: usize,
172    ) -> Result<Slot<'de, [T]>, DecodeError> {
173        assert!(
174            align_of::<T>() <= CHUNK_SIZE,
175            "attempted to take a slice slot for a type with an alignment \
176             higher than {CHUNK_SIZE}",
177        );
178
179        let slice_byte_length = size_of::<T>() * len;
180        let chunk_count = slice_byte_length.div_ceil(CHUNK_SIZE);
181        let chunk_length = CHUNK_SIZE * chunk_count;
182        let padding_length = chunk_length - slice_byte_length;
183        let chunks_ptr = self.take_chunks(chunk_count)?.as_mut_ptr();
184        let padding: &[u8] = unsafe {
185            core::slice::from_raw_parts(
186                chunks_ptr.cast::<u8>().add(slice_byte_length),
187                padding_length,
188            )
189        };
190        if padding.iter().any(|byte| *byte != 0) {
191            return Err(DecodeError::InvalidPadding);
192        }
193
194        // SAFETY: `result` is at least 8-aligned and points to at least enough
195        // bytes for a slice of `T` of length `len`.
196        unsafe { Ok(Slot::new_slice_unchecked(chunks_ptr.cast(), len)) }
197    }
198
199    fn decode_owned<'de, T: Decode<Self> + Constrained<Constraint = ()>>(
200        self: &mut &'de mut Self,
201    ) -> Result<T::Owned<'de>, DecodeError> {
202        let mut slot = self.take_slot::<T>()?;
203        T::decode(slot.as_mut(), self, ())?;
204        self.commit();
205        // SAFETY: `slot` decoded successfully and the decoder was committed. `slot` now points to a
206        // valid `T` within the decoder.
207        unsafe { Ok(slot.as_mut_ptr().cast::<T::Owned<'de>>().read()) }
208    }
209
210    fn decode<T>(self) -> Result<Decoded<T, Self>, DecodeError>
211    where
212        T: Decode<Self> + Constrained<Constraint = ()>,
213        Self: Sized,
214    {
215        self.decode_with_constraint(())
216    }
217
218    fn decode_with_constraint<T>(
219        mut self,
220        constraint: <T as Constrained>::Constraint,
221    ) -> Result<Decoded<T, Self>, DecodeError>
222    where
223        T: Decode<Self>,
224        Self: Sized,
225    {
226        let mut decoder = &mut self;
227        let mut slot = decoder.take_slot::<T>()?;
228        T::decode(slot.as_mut(), decoder, constraint)?;
229        decoder.commit();
230        decoder.finish()?;
231        // SAFETY: `slot` decoded successfully and the decoder was committed. `slot` now points to a
232        // valid `T` within the decoder.
233        unsafe { Ok(Decoded::new_unchecked(slot.as_mut_ptr(), self)) }
234    }
235}