ab_io_type/metadata/
type_details.rs

1use crate::metadata::{IoTypeDetails, IoTypeMetadataKind};
2use core::num::NonZeroU8;
3
4/// This macro is necessary to reduce boilerplate due to lack of `?` in const environment
5macro_rules! forward_option {
6    ($expr:expr) => {{
7        let Some(result) = $expr else {
8            return None;
9        };
10        result
11    }};
12}
13
14#[inline(always)]
15pub(super) const fn decode_type_details(mut metadata: &[u8]) -> Option<(IoTypeDetails, &[u8])> {
16    if metadata.is_empty() {
17        return None;
18    }
19
20    let kind = forward_option!(IoTypeMetadataKind::try_from_u8(metadata[0]));
21    metadata = forward_option!(skip_n_bytes(metadata, 1));
22
23    match kind {
24        IoTypeMetadataKind::Unit => Some((
25            IoTypeDetails {
26                recommended_capacity: 0,
27                alignment: NonZeroU8::new(1).expect("Not zero; qed"),
28            },
29            metadata,
30        )),
31        IoTypeMetadataKind::Bool | IoTypeMetadataKind::U8 | IoTypeMetadataKind::I8 => Some((
32            IoTypeDetails {
33                recommended_capacity: 1,
34                alignment: NonZeroU8::new(1).expect("Not zero; qed"),
35            },
36            metadata,
37        )),
38        IoTypeMetadataKind::U16 | IoTypeMetadataKind::I16 => Some((
39            IoTypeDetails {
40                recommended_capacity: 2,
41                alignment: NonZeroU8::new(2).expect("Not zero; qed"),
42            },
43            metadata,
44        )),
45        IoTypeMetadataKind::U32 | IoTypeMetadataKind::I32 => Some((
46            IoTypeDetails {
47                recommended_capacity: 4,
48                alignment: NonZeroU8::new(4).expect("Not zero; qed"),
49            },
50            metadata,
51        )),
52        IoTypeMetadataKind::U64 | IoTypeMetadataKind::I64 => Some((
53            IoTypeDetails {
54                recommended_capacity: 8,
55                alignment: NonZeroU8::new(8).expect("Not zero; qed"),
56            },
57            metadata,
58        )),
59        IoTypeMetadataKind::U128 | IoTypeMetadataKind::I128 => Some((
60            IoTypeDetails {
61                recommended_capacity: 16,
62                alignment: NonZeroU8::new(16).expect("Not zero; qed"),
63            },
64            metadata,
65        )),
66        IoTypeMetadataKind::Struct => struct_type_details(metadata, None, false),
67        IoTypeMetadataKind::Struct0 => struct_type_details(metadata, Some(0), false),
68        IoTypeMetadataKind::Struct1 => struct_type_details(metadata, Some(1), false),
69        IoTypeMetadataKind::Struct2 => struct_type_details(metadata, Some(2), false),
70        IoTypeMetadataKind::Struct3 => struct_type_details(metadata, Some(3), false),
71        IoTypeMetadataKind::Struct4 => struct_type_details(metadata, Some(4), false),
72        IoTypeMetadataKind::Struct5 => struct_type_details(metadata, Some(5), false),
73        IoTypeMetadataKind::Struct6 => struct_type_details(metadata, Some(6), false),
74        IoTypeMetadataKind::Struct7 => struct_type_details(metadata, Some(7), false),
75        IoTypeMetadataKind::Struct8 => struct_type_details(metadata, Some(8), false),
76        IoTypeMetadataKind::Struct9 => struct_type_details(metadata, Some(9), false),
77        IoTypeMetadataKind::Struct10 => struct_type_details(metadata, Some(10), false),
78        IoTypeMetadataKind::TupleStruct => struct_type_details(metadata, None, true),
79        IoTypeMetadataKind::TupleStruct1 => struct_type_details(metadata, Some(1), true),
80        IoTypeMetadataKind::TupleStruct2 => struct_type_details(metadata, Some(2), true),
81        IoTypeMetadataKind::TupleStruct3 => struct_type_details(metadata, Some(3), true),
82        IoTypeMetadataKind::TupleStruct4 => struct_type_details(metadata, Some(4), true),
83        IoTypeMetadataKind::TupleStruct5 => struct_type_details(metadata, Some(5), true),
84        IoTypeMetadataKind::TupleStruct6 => struct_type_details(metadata, Some(6), true),
85        IoTypeMetadataKind::TupleStruct7 => struct_type_details(metadata, Some(7), true),
86        IoTypeMetadataKind::TupleStruct8 => struct_type_details(metadata, Some(8), true),
87        IoTypeMetadataKind::TupleStruct9 => struct_type_details(metadata, Some(9), true),
88        IoTypeMetadataKind::TupleStruct10 => struct_type_details(metadata, Some(10), true),
89        IoTypeMetadataKind::Enum => enum_capacity(metadata, None, true),
90        IoTypeMetadataKind::Enum1 => enum_capacity(metadata, Some(1), true),
91        IoTypeMetadataKind::Enum2 => enum_capacity(metadata, Some(2), true),
92        IoTypeMetadataKind::Enum3 => enum_capacity(metadata, Some(3), true),
93        IoTypeMetadataKind::Enum4 => enum_capacity(metadata, Some(4), true),
94        IoTypeMetadataKind::Enum5 => enum_capacity(metadata, Some(5), true),
95        IoTypeMetadataKind::Enum6 => enum_capacity(metadata, Some(6), true),
96        IoTypeMetadataKind::Enum7 => enum_capacity(metadata, Some(7), true),
97        IoTypeMetadataKind::Enum8 => enum_capacity(metadata, Some(8), true),
98        IoTypeMetadataKind::Enum9 => enum_capacity(metadata, Some(9), true),
99        IoTypeMetadataKind::Enum10 => enum_capacity(metadata, Some(10), true),
100        IoTypeMetadataKind::EnumNoFields => enum_capacity(metadata, None, false),
101        IoTypeMetadataKind::EnumNoFields1 => enum_capacity(metadata, Some(1), false),
102        IoTypeMetadataKind::EnumNoFields2 => enum_capacity(metadata, Some(2), false),
103        IoTypeMetadataKind::EnumNoFields3 => enum_capacity(metadata, Some(3), false),
104        IoTypeMetadataKind::EnumNoFields4 => enum_capacity(metadata, Some(4), false),
105        IoTypeMetadataKind::EnumNoFields5 => enum_capacity(metadata, Some(5), false),
106        IoTypeMetadataKind::EnumNoFields6 => enum_capacity(metadata, Some(6), false),
107        IoTypeMetadataKind::EnumNoFields7 => enum_capacity(metadata, Some(7), false),
108        IoTypeMetadataKind::EnumNoFields8 => enum_capacity(metadata, Some(8), false),
109        IoTypeMetadataKind::EnumNoFields9 => enum_capacity(metadata, Some(9), false),
110        IoTypeMetadataKind::EnumNoFields10 => enum_capacity(metadata, Some(10), false),
111        IoTypeMetadataKind::Array8b | IoTypeMetadataKind::VariableElements8b => {
112            if metadata.is_empty() {
113                return None;
114            }
115
116            let num_elements = metadata[0] as u32;
117            metadata = forward_option!(skip_n_bytes(metadata, size_of::<u8>()));
118
119            let type_details;
120            (type_details, metadata) = forward_option!(decode_type_details(metadata));
121            let recommended_capacity =
122                forward_option!(type_details.recommended_capacity.checked_mul(num_elements));
123            Some((
124                IoTypeDetails {
125                    recommended_capacity,
126                    alignment: type_details.alignment,
127                },
128                metadata,
129            ))
130        }
131        IoTypeMetadataKind::Array16b | IoTypeMetadataKind::VariableElements16b => {
132            if metadata.is_empty() {
133                return None;
134            }
135
136            let mut num_elements = [0; size_of::<u16>()];
137            (metadata, _) =
138                forward_option!(copy_n_bytes(metadata, &mut num_elements, size_of::<u16>()));
139            let num_elements = u16::from_le_bytes(num_elements) as u32;
140
141            let type_details;
142            (type_details, metadata) = forward_option!(decode_type_details(metadata));
143            let recommended_capacity =
144                forward_option!(type_details.recommended_capacity.checked_mul(num_elements));
145            Some((
146                IoTypeDetails {
147                    recommended_capacity,
148                    alignment: type_details.alignment,
149                },
150                metadata,
151            ))
152        }
153        IoTypeMetadataKind::Array32b | IoTypeMetadataKind::VariableElements32b => {
154            if metadata.is_empty() {
155                return None;
156            }
157
158            let mut num_elements = [0; size_of::<u32>()];
159            (metadata, _) =
160                forward_option!(copy_n_bytes(metadata, &mut num_elements, size_of::<u32>()));
161            let num_elements = u32::from_le_bytes(num_elements);
162
163            let type_details;
164            (type_details, metadata) = forward_option!(decode_type_details(metadata));
165            let recommended_capacity =
166                forward_option!(type_details.recommended_capacity.checked_mul(num_elements));
167            Some((
168                IoTypeDetails {
169                    recommended_capacity,
170                    alignment: type_details.alignment,
171                },
172                metadata,
173            ))
174        }
175        IoTypeMetadataKind::ArrayU8x8 => Some((IoTypeDetails::bytes(8), metadata)),
176        IoTypeMetadataKind::ArrayU8x16 => Some((IoTypeDetails::bytes(16), metadata)),
177        IoTypeMetadataKind::ArrayU8x32 => Some((IoTypeDetails::bytes(32), metadata)),
178        IoTypeMetadataKind::ArrayU8x64 => Some((IoTypeDetails::bytes(64), metadata)),
179        IoTypeMetadataKind::ArrayU8x128 => Some((IoTypeDetails::bytes(128), metadata)),
180        IoTypeMetadataKind::ArrayU8x256 => Some((IoTypeDetails::bytes(256), metadata)),
181        IoTypeMetadataKind::ArrayU8x512 => Some((IoTypeDetails::bytes(512), metadata)),
182        IoTypeMetadataKind::ArrayU8x1024 => Some((IoTypeDetails::bytes(1024), metadata)),
183        IoTypeMetadataKind::ArrayU8x2028 => Some((IoTypeDetails::bytes(2028), metadata)),
184        IoTypeMetadataKind::ArrayU8x4096 => Some((IoTypeDetails::bytes(4096), metadata)),
185        IoTypeMetadataKind::VariableBytes8b => {
186            if metadata.is_empty() {
187                return None;
188            }
189
190            let num_bytes = metadata[0] as u32;
191            metadata = forward_option!(skip_n_bytes(metadata, size_of::<u8>()));
192
193            Some((IoTypeDetails::bytes(num_bytes), metadata))
194        }
195        IoTypeMetadataKind::VariableBytes16b => {
196            if metadata.is_empty() {
197                return None;
198            }
199
200            let mut num_bytes = [0; size_of::<u16>()];
201            (metadata, _) =
202                forward_option!(copy_n_bytes(metadata, &mut num_bytes, size_of::<u16>()));
203            let num_bytes = u16::from_le_bytes(num_bytes) as u32;
204
205            Some((IoTypeDetails::bytes(num_bytes), metadata))
206        }
207        IoTypeMetadataKind::VariableBytes32b => {
208            if metadata.is_empty() {
209                return None;
210            }
211
212            let mut num_bytes = [0; size_of::<u32>()];
213            (metadata, _) =
214                forward_option!(copy_n_bytes(metadata, &mut num_bytes, size_of::<u32>()));
215            let num_bytes = u32::from_le_bytes(num_bytes);
216
217            Some((IoTypeDetails::bytes(num_bytes), metadata))
218        }
219        IoTypeMetadataKind::VariableBytes0 => Some((IoTypeDetails::bytes(0), metadata)),
220        IoTypeMetadataKind::VariableBytes512 => Some((IoTypeDetails::bytes(512), metadata)),
221        IoTypeMetadataKind::VariableBytes1024 => Some((IoTypeDetails::bytes(1024), metadata)),
222        IoTypeMetadataKind::VariableBytes2028 => Some((IoTypeDetails::bytes(2028), metadata)),
223        IoTypeMetadataKind::VariableBytes4096 => Some((IoTypeDetails::bytes(4096), metadata)),
224        IoTypeMetadataKind::VariableBytes8192 => Some((IoTypeDetails::bytes(8192), metadata)),
225        IoTypeMetadataKind::VariableBytes16384 => Some((IoTypeDetails::bytes(16384), metadata)),
226        IoTypeMetadataKind::VariableBytes32768 => Some((IoTypeDetails::bytes(32768), metadata)),
227        IoTypeMetadataKind::VariableBytes65536 => Some((IoTypeDetails::bytes(65536), metadata)),
228        IoTypeMetadataKind::VariableBytes131072 => Some((IoTypeDetails::bytes(131_072), metadata)),
229        IoTypeMetadataKind::VariableBytes262144 => Some((IoTypeDetails::bytes(262_144), metadata)),
230        IoTypeMetadataKind::VariableBytes524288 => Some((IoTypeDetails::bytes(524_288), metadata)),
231        IoTypeMetadataKind::VariableBytes1048576 => {
232            Some((IoTypeDetails::bytes(1_048_576), metadata))
233        }
234        IoTypeMetadataKind::VariableElements0 => {
235            if metadata.is_empty() {
236                return None;
237            }
238
239            (_, metadata) = forward_option!(decode_type_details(metadata));
240            Some((
241                IoTypeDetails {
242                    recommended_capacity: 0,
243                    alignment: NonZeroU8::new(1).expect("Not zero; qed"),
244                },
245                metadata,
246            ))
247        }
248        IoTypeMetadataKind::FixedCapacityBytes8b | IoTypeMetadataKind::FixedCapacityString8b => {
249            if metadata.is_empty() {
250                return None;
251            }
252
253            let num_bytes = metadata[0] as u32;
254            metadata = forward_option!(skip_n_bytes(metadata, size_of::<u8>()));
255
256            Some((
257                IoTypeDetails::bytes(num_bytes + size_of::<u8>() as u32),
258                metadata,
259            ))
260        }
261        IoTypeMetadataKind::FixedCapacityBytes16b | IoTypeMetadataKind::FixedCapacityString16b => {
262            if metadata.is_empty() {
263                return None;
264            }
265
266            let mut num_bytes = [0; size_of::<u16>()];
267            (metadata, _) =
268                forward_option!(copy_n_bytes(metadata, &mut num_bytes, size_of::<u16>()));
269            let num_bytes = u16::from_le_bytes(num_bytes) as u32;
270
271            Some((
272                IoTypeDetails {
273                    recommended_capacity: num_bytes + size_of::<u16>() as u32,
274                    alignment: NonZeroU8::new(2).expect("Not zero; qed"),
275                },
276                metadata,
277            ))
278        }
279        IoTypeMetadataKind::Address | IoTypeMetadataKind::Balance => Some((
280            IoTypeDetails {
281                recommended_capacity: 16,
282                alignment: NonZeroU8::new(8).expect("Not zero; qed"),
283            },
284            metadata,
285        )),
286    }
287}
288
289#[inline(always)]
290const fn struct_type_details(
291    mut input: &[u8],
292    field_count: Option<u8>,
293    tuple: bool,
294) -> Option<(IoTypeDetails, &[u8])> {
295    if input.is_empty() {
296        return None;
297    }
298
299    // Skip struct name
300    let struct_name_length = input[0] as usize;
301    input = forward_option!(skip_n_bytes(input, 1 + struct_name_length));
302
303    let mut field_count = if let Some(field_count) = field_count {
304        field_count
305    } else {
306        if input.is_empty() {
307            return None;
308        }
309
310        let field_count = input[0];
311        input = forward_option!(skip_n_bytes(input, 1));
312
313        field_count
314    };
315
316    // Capacity of arguments
317    let mut capacity = 0u32;
318    let mut alignment = 1u8;
319    while field_count > 0 {
320        if input.is_empty() {
321            return None;
322        }
323
324        // Skip field name if needed
325        if !tuple {
326            let field_name_length = input[0] as usize;
327            input = forward_option!(skip_n_bytes(input, 1 + field_name_length));
328        }
329
330        // Capacity of argument's type
331        let type_details;
332        (type_details, input) = forward_option!(decode_type_details(input));
333        capacity = forward_option!(capacity.checked_add(type_details.recommended_capacity));
334        // TODO: `core::cmp::max()` isn't const yet due to trait bounds
335        alignment = if type_details.alignment.get() > alignment {
336            type_details.alignment.get()
337        } else {
338            alignment
339        };
340
341        field_count -= 1;
342    }
343
344    Some((
345        IoTypeDetails {
346            recommended_capacity: capacity,
347            alignment: NonZeroU8::new(alignment).expect("At least zero; qed"),
348        },
349        input,
350    ))
351}
352
353#[inline(always)]
354const fn enum_capacity(
355    mut input: &[u8],
356    variant_count: Option<u8>,
357    has_fields: bool,
358) -> Option<(IoTypeDetails, &[u8])> {
359    if input.is_empty() {
360        return None;
361    }
362
363    // Skip enum name
364    let enum_name_length = input[0] as usize;
365    input = forward_option!(skip_n_bytes(input, 1 + enum_name_length));
366
367    let mut variant_count = if let Some(variant_count) = variant_count {
368        variant_count
369    } else {
370        if input.is_empty() {
371            return None;
372        }
373
374        let variant_count = input[0];
375        input = forward_option!(skip_n_bytes(input, 1));
376
377        variant_count
378    };
379
380    // Capacity of variants
381    let mut enum_capacity = None;
382    let mut alignment = 1u8;
383    while variant_count > 0 {
384        if input.is_empty() {
385            return None;
386        }
387
388        let variant_type_details;
389
390        // Variant capacity as if it was a struct
391        (variant_type_details, input) = forward_option!(struct_type_details(
392            input,
393            if has_fields { None } else { Some(0) },
394            false
395        ));
396        // `+ 1` is for the discriminant
397        let variant_capacity = variant_type_details.recommended_capacity + 1;
398        // TODO: `core::cmp::max()` isn't const yet due to trait bounds
399        alignment = if variant_type_details.alignment.get() > alignment {
400            variant_type_details.alignment.get()
401        } else {
402            alignment
403        };
404
405        match enum_capacity {
406            Some(capacity) => {
407                if capacity != variant_capacity {
408                    return None;
409                }
410            }
411            None => {
412                enum_capacity.replace(variant_capacity);
413            }
414        }
415
416        variant_count -= 1;
417    }
418
419    // `.unwrap_or_default()` is not const
420    let enum_capacity = match enum_capacity {
421        Some(enum_capacity) => enum_capacity,
422        None => 0,
423    };
424
425    Some((
426        IoTypeDetails {
427            recommended_capacity: enum_capacity,
428            alignment: NonZeroU8::new(alignment).expect("At least zero; qed"),
429        },
430        input,
431    ))
432}
433
434/// Copies `n` bytes from input to output and returns both input and output after `n` bytes offset
435#[inline(always)]
436const fn copy_n_bytes<'i, 'o>(
437    mut input: &'i [u8],
438    mut output: &'o mut [u8],
439    n: usize,
440) -> Option<(&'i [u8], &'o mut [u8])> {
441    if n > input.len() || n > output.len() {
442        return None;
443    }
444
445    let source;
446    let target;
447    (source, input) = input.split_at(n);
448    (target, output) = output.split_at_mut(n);
449    target.copy_from_slice(source);
450
451    Some((input, output))
452}
453
454/// Skips `n` bytes and return remainder
455#[inline(always)]
456const fn skip_n_bytes(input: &[u8], n: usize) -> Option<&[u8]> {
457    if n > input.len() {
458        return None;
459    }
460
461    // `&input[n..]` not supported in const yet
462    Some(input.split_at(n).1)
463}