Skip to main content

ab_io_type/
metadata.rs

1mod compact;
2mod stack;
3#[cfg(test)]
4mod tests;
5mod type_details;
6mod type_name;
7
8use crate::alignment::Alignment;
9use crate::metadata::compact::compact_metadata;
10use crate::metadata::type_details::decode_type_details;
11use crate::metadata::type_name::type_name;
12
13/// Max capacity for metadata bytes used in fixed size buffers
14pub const MAX_METADATA_CAPACITY: usize = 8192;
15
16/// Concatenates metadata sources.
17///
18/// Returns both a scratch memory and number of bytes in it that correspond to metadata
19pub const fn concat_metadata_sources(sources: &[&[u8]]) -> ([u8; MAX_METADATA_CAPACITY], usize) {
20    let mut metadata_scratch = [0u8; MAX_METADATA_CAPACITY];
21    let mut remainder = metadata_scratch.as_mut_slice();
22
23    // For loops are not yet usable in const environment
24    let mut i = 0;
25    while i < sources.len() {
26        let source = sources[i];
27        let target;
28        (target, remainder) = remainder.split_at_mut(source.len());
29        target.copy_from_slice(source);
30        i += 1;
31    }
32
33    let remainder_len = remainder.len();
34    let size = metadata_scratch.len() - remainder_len;
35    (metadata_scratch, size)
36}
37
38#[derive(Debug, Copy, Clone)]
39pub struct IoTypeDetails {
40    /// Recommended capacity that must be allocated by the host.
41    ///
42    /// If actual data is larger, it will be passed down to the guest as it is, if smaller than the
43    /// host must allocate the recommended capacity for guest anyway.
44    pub recommended_capacity: u32,
45    /// Alignment of the type
46    pub alignment: Alignment,
47}
48
49impl IoTypeDetails {
50    /// Create an instance for regular bytes (alignment 1)
51    #[inline(always)]
52    pub const fn bytes(recommended_capacity: u32) -> Self {
53        Self {
54            recommended_capacity,
55            alignment: Alignment::of::<u8>(),
56        }
57    }
58}
59
60/// Metadata types contained in [`TrivialType::METADATA`] and [`IoType::METADATA`].
61///
62/// Metadata encoding consists of this enum variant treated as `u8` followed by optional metadata
63/// encoding rules specific to metadata type variant (see variant's description).
64///
65/// This metadata is enough to fully reconstruct the hierarchy of the type to generate language
66/// bindings, auto-generate UI forms, etc.
67///
68/// [`TrivialType::METADATA`]: crate::trivial_type::TrivialType::METADATA
69/// [`IoType::METADATA`]: crate::IoType::METADATA
70#[derive(Debug, Copy, Clone, Eq, PartialEq)]
71#[repr(u8)]
72pub enum IoTypeMetadataKind {
73    /// `()`
74    Unit,
75    /// `bool`
76    Bool,
77    /// `u8`
78    U8,
79    /// `u16`
80    U16,
81    /// `u32`
82    U32,
83    /// `u64`
84    U64,
85    /// `u128`
86    U128,
87    /// `i8`
88    I8,
89    /// `i16`
90    I16,
91    /// `i32`
92    I32,
93    /// `i64`
94    I64,
95    /// `i128`
96    I128,
97    /// `struct S {..}`, `struct S(..);` or `struct S;`
98    ///
99    /// Structs are encoded as follows:
100    /// * Length of struct name in bytes (u8)
101    /// * Struct name as UTF-8 bytes
102    /// * Number of fields (u8)
103    ///
104    /// Each field is encoded as follows:
105    /// * Length of the field name in bytes (u8), zero for fields of tuple structs
106    /// * Field name as UTF-8 bytes
107    /// * Recursive metadata of the field's type
108    ///
109    /// Fields are laid out like in a `#[repr(C)]` struct. Decoders reject structs that have both
110    /// fields with names and fields with zero-length names, and layouts with padding.
111    Struct,
112    /// `enum E { A, B {..}, C(..) }`
113    ///
114    /// Enums are encoded as follows:
115    /// * Length of enum name in bytes (u8)
116    /// * Enum name as UTF-8 bytes
117    /// * Number of variants (u8)
118    ///
119    /// Each variant is encoded as follows:
120    /// * Length of the variant name in bytes (u8)
121    /// * Variant name as UTF-8 bytes
122    /// * Number of fields (u8), zero for variants without fields
123    /// * Fields, each encoded like a field of [`Self::Struct`], with zero-length names for fields
124    ///   of tuple variants
125    ///
126    /// Each variant is a 1-byte discriminant followed by its fields laid out like in a
127    /// `#[repr(u8)]` enum, all variants must have the same size. Decoders reject enums without
128    /// variants, variants that have both fields with names and fields with zero-length names, and
129    /// layouts with padding.
130    Enum,
131    /// Array `[T; N]`.
132    ///
133    /// Encoded as follows:
134    /// * 4 bytes number of elements (little-endian)
135    /// * Recursive metadata of a contained type
136    Array,
137    /// [`MaybeData`](crate::maybe_data::MaybeData).
138    ///
139    /// Encoded as follows:
140    /// * Recursive metadata of a contained type
141    ///
142    /// Decoders only accept it as the outermost type.
143    MaybeData,
144    /// [`VariableElements`](crate::variable_elements::VariableElements).
145    ///
146    /// Encoded as follows:
147    /// * 4 bytes recommended allocation in elements (little-endian)
148    /// * Recursive metadata of a contained type
149    ///
150    /// Decoders only accept it as the outermost type.
151    VariableElements,
152    /// [`FixedCapacityElements`](crate::fixed_capacity_elements::FixedCapacityElements).
153    ///
154    /// Encoded as follows:
155    /// * 4 bytes capacity in elements (little-endian)
156    /// * Recursive metadata of a contained type
157    ///
158    /// Elements follow a little-endian `u32` length, padded with zeroes to the alignment of
159    /// elements if it is larger.
160    FixedCapacityElements,
161    /// [`FixedCapacityString`](crate::fixed_capacity_string::FixedCapacityString).
162    ///
163    /// This is a string only by convention, there is no runtime verification done, contents is
164    /// treated as regular bytes.
165    ///
166    /// Encoded as follows:
167    /// * 4 bytes capacity in bytes (little-endian)
168    ///
169    /// Bytes follow a little-endian `u32` length.
170    FixedCapacityString,
171    /// Unaligned wrapper over another [`TrivialType`].
172    ///
173    /// [`TrivialType`]: crate::trivial_type::TrivialType
174    ///
175    /// Encoded as follows:
176    /// * Recursive metadata of a contained type
177    Unaligned,
178}
179
180const impl TryFrom<u8> for IoTypeMetadataKind {
181    type Error = ();
182
183    #[inline]
184    fn try_from(byte: u8) -> Result<Self, Self::Error> {
185        Ok(match byte {
186            0 => Self::Unit,
187            1 => Self::Bool,
188            2 => Self::U8,
189            3 => Self::U16,
190            4 => Self::U32,
191            5 => Self::U64,
192            6 => Self::U128,
193            7 => Self::I8,
194            8 => Self::I16,
195            9 => Self::I32,
196            10 => Self::I64,
197            11 => Self::I128,
198            12 => Self::Struct,
199            13 => Self::Enum,
200            14 => Self::Array,
201            15 => Self::MaybeData,
202            16 => Self::VariableElements,
203            17 => Self::FixedCapacityElements,
204            18 => Self::FixedCapacityString,
205            19 => Self::Unaligned,
206            _ => {
207                return Err(());
208            }
209        })
210    }
211}
212
213impl IoTypeMetadataKind {
214    // TODO: Create wrapper type for metadata bytes and move this method there
215    /// Produce compact metadata.
216    ///
217    /// Compact metadata retains the layout, but throws some details. Specifically:
218    /// * Names of structs, fields, enums and enum variants are replaced with zero-length names, so
219    ///   a struct with named fields has the same compact metadata as a tuple struct with fields of
220    ///   the same types
221    /// * A struct with a single field is replaced with compact metadata of the type of the field
222    ///   and a struct without fields with `()`, so a newtype like `struct Wrapper(u64);` has the
223    ///   same compact metadata as `u64`, also when contained in other types
224    ///
225    /// Enums are retained, even with a single variant or a single field, since the discriminant is
226    /// a part of their layout.
227    ///
228    /// Compact metadata of valid metadata is valid metadata too, it decodes into the same capacity
229    /// and alignment (see [`Self::type_details()`]), and compacting it again doesn't change it.
230    ///
231    /// This is typically called by higher-level functions and doesn't need to be used directly.
232    ///
233    /// This function takes an `input` that starts with metadata defined in [`IoTypeMetadataKind`]
234    /// and `output` where compact metadata must be written. Since input might have other data past
235    /// the data structure to be processed, the remainder of input and output are returned to the
236    /// caller.
237    ///
238    /// `None` is returned for an unexpected metadata kind, metadata nested deeper than `MAX_DEPTH`
239    /// (see [`Self::type_details()`]), truncated metadata and `output` that is too small for
240    /// compact metadata. Metadata is not validated otherwise, [`Self::type_details()`] does
241    /// that.
242    #[inline]
243    pub const fn compact<'i, 'o, const MAX_DEPTH: usize>(
244        input: &'i [u8],
245        output: &'o mut [u8],
246    ) -> Option<(&'i [u8], &'o mut [u8])> {
247        compact_metadata::<MAX_DEPTH>(input, output)
248    }
249
250    // TODO: Create wrapper type for metadata bytes and move this method there
251    /// Decode type name.
252    ///
253    /// Expected to be UTF-8, but must be parsed before printed as text, which is somewhat costly.
254    #[inline]
255    pub const fn type_name(metadata: &[u8]) -> Option<&[u8]> {
256        type_name(metadata)
257    }
258
259    // TODO: Create wrapper type for metadata bytes and move this method there
260    /// Decode type, return its recommended capacity that should be allocated by the host.
261    ///
262    /// If actual data is larger, it will be passed down to the guest as it is, if smaller than host
263    /// should allocate recommended capacity for guest anyway.
264    ///
265    /// Returns type details and whatever slice of bytes from `metadata` that is left after
266    /// type decoding. Invalid metadata and metadata nested deeper than `MAX_DEPTH` result in `None`
267    /// being returned.
268    ///
269    /// `MAX_DEPTH` is the max nesting depth of compound types (structs, enums, arrays, `MaybeData`,
270    /// `VariableElements`, `FixedCapacityElements` and `Unaligned`), like 2 for
271    /// `Unaligned<[u8; 4]>`. Variants of an enum don't count, only types of their fields do. There
272    /// is no recursion, so stack usage grows with `MAX_DEPTH`, but doesn't depend on metadata.
273    #[inline]
274    pub const fn type_details<const MAX_DEPTH: usize>(
275        metadata: &[u8],
276    ) -> Option<(IoTypeDetails, &[u8])> {
277        decode_type_details::<MAX_DEPTH>(metadata)
278    }
279}