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, Decode, DecodeError, Decoded, Slot, CHUNK_SIZE};
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>>(
120        self: &mut &'de mut Self,
121    ) -> Result<T::Decoded<'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>,
130        Self: Sized;
131}
132
133impl<D: Decoder + ?Sized> DecoderExt for D {
134    fn take_chunks<'de>(
135        self: &mut &'de mut Self,
136        count: usize,
137    ) -> Result<&'de mut [Chunk], DecodeError> {
138        self.take_chunks_raw(count).map(|p| unsafe { slice::from_raw_parts_mut(p.as_ptr(), count) })
139    }
140
141    fn take_slot<'de, T>(self: &mut &'de mut Self) -> Result<Slot<'de, T>, DecodeError> {
142        // TODO: might be able to move this into a const for guaranteed const
143        // eval
144        assert!(
145            align_of::<T>() <= CHUNK_SIZE,
146            "attempted to take a slot for a type with an alignment higher \
147             than {CHUNK_SIZE}",
148        );
149
150        let count = size_of::<T>().div_ceil(CHUNK_SIZE);
151        let chunks = self.take_chunks(count)?;
152        // SAFETY: `result` is at least 8-aligned and points to at least enough
153        // bytes for a `T`.
154        unsafe { Ok(Slot::new_unchecked(chunks.as_mut_ptr().cast())) }
155    }
156
157    fn take_slice_slot<'de, T>(
158        self: &mut &'de mut Self,
159        len: usize,
160    ) -> Result<Slot<'de, [T]>, DecodeError> {
161        assert!(
162            align_of::<T>() <= CHUNK_SIZE,
163            "attempted to take a slice slot for a type with an alignment \
164             higher than {CHUNK_SIZE}",
165        );
166
167        let count = (size_of::<T>() * len).div_ceil(CHUNK_SIZE);
168        let chunks = self.take_chunks(count)?;
169        // SAFETY: `result` is at least 8-aligned and points to at least enough
170        // bytes for a slice of `T` of length `len`.
171        unsafe { Ok(Slot::new_slice_unchecked(chunks.as_mut_ptr().cast(), len)) }
172    }
173
174    fn decode_owned<'de, T: Decode<Self>>(
175        self: &mut &'de mut Self,
176    ) -> Result<T::Decoded<'de>, DecodeError> {
177        let mut slot = self.take_slot::<T>()?;
178        T::decode(slot.as_mut(), self)?;
179        self.commit();
180        // SAFETY: `slot` decoded successfully and the decoder was committed. `slot` now points to a
181        // valid `T` within the decoder.
182        unsafe { Ok(slot.as_mut_ptr().cast::<T::Decoded<'de>>().read()) }
183    }
184
185    fn decode<T>(mut self) -> Result<Decoded<T, Self>, DecodeError>
186    where
187        T: Decode<Self>,
188        Self: Sized,
189    {
190        let mut decoder = &mut self;
191        let mut slot = decoder.take_slot::<T>()?;
192        T::decode(slot.as_mut(), decoder)?;
193        decoder.commit();
194        // SAFETY: `slot` decoded successfully and the decoder was committed. `slot` now points to a
195        // valid `T` within the decoder.
196        unsafe { Ok(Decoded::new_unchecked(slot.as_mut_ptr(), self)) }
197    }
198}