gel_protogen/
structs.rs

1use std::mem::MaybeUninit;
2
3use crate::prelude::*;
4
5/// A trait for enums that describes some information.
6pub trait EnumMeta {
7    const VALUES: &'static [(&'static str, usize)];
8}
9
10/// A trait for structs that describes their fields, containing some associated
11/// constants. Note that only `FIELDS` is provided. The remainder of the
12/// associated constants are computed from `FIELDS`.
13pub trait StructMeta: Copy + Clone + std::fmt::Debug {
14    type Struct<'a>: Copy + Clone + std::fmt::Debug;
15    const FIELDS: StructFields;
16    const FIELD_COUNT: usize = Self::FIELDS.fields.len();
17    const IS_FIXED_SIZE: bool = Self::FIELDS.constant_size().is_some();
18    const FIXED_SIZE: Option<usize> = Self::FIELDS.constant_size();
19    const LENGTH_FIELD_INDEX: Option<usize> = Self::FIELDS.length_field_fixed_offset();
20    const HAS_LENGTH_FIELD: bool = Self::LENGTH_FIELD_INDEX.is_some();
21
22    fn new(buf: &[u8]) -> Result<Self::Struct<'_>, ParseError>;
23    fn to_vec(&self) -> Vec<u8>;
24}
25
26/// A trait implemented for all structs with a boolean determining whether they
27/// are fixed size.
28///
29/// NOTE: A generic parameter is used here which allows for optional further
30/// trait implementations.
31pub trait StructAttributeFixedSize<const IS_FIXED_SIZE: bool>: StructMeta {}
32
33/// A trait implemented for all structs with a boolean determining whether they
34/// have a length field.
35///
36/// NOTE: A generic parameter is used here which allows for optional further
37/// trait implementations.
38pub trait StructAttributeHasLengthField<const HAS_LENGTH_FIELD: bool>: StructMeta {}
39
40/// A trait implemented for all structs with a count of fields.
41///
42/// NOTE: A generic parameter is used here which allows for optional further
43/// trait implementations.
44pub trait StructAttributeFieldCount<const FIELD_COUNT: usize>: StructMeta {}
45
46#[derive(Clone, Copy, Debug)]
47pub struct StructField {
48    /// The name of the field in the struct.
49    pub name: &'static str,
50    /// The metadata for the field.
51    pub meta: &'static StructFieldMeta,
52    /// The value of the field, if it is a constant.
53    pub value: Option<usize>,
54}
55
56/// A struct that contains metadata about a field's type.
57#[derive(Clone, Copy, Debug, Default)]
58pub struct StructFieldMeta {
59    pub type_name: &'static str,
60    pub constant_size: Option<usize>,
61    pub is_length: bool,
62    pub is_enum: bool,
63    pub is_struct: bool,
64    pub is_array: bool,
65    pub is_primitive: bool,
66}
67
68impl StructFieldMeta {
69    pub const fn new(type_name: &'static str, constant_size: Option<usize>) -> Self {
70        Self {
71            type_name,
72            constant_size,
73            is_length: false,
74            is_enum: false,
75            is_struct: false,
76            is_array: false,
77            is_primitive: false,
78        }
79    }
80
81    pub const fn set_length(self) -> Self {
82        Self {
83            is_length: true,
84            ..self
85        }
86    }
87
88    pub const fn set_enum(self) -> Self {
89        Self {
90            is_enum: true,
91            ..self
92        }
93    }
94
95    pub const fn set_struct(self) -> Self {
96        Self {
97            is_struct: true,
98            ..self
99        }
100    }
101
102    pub const fn set_array(self) -> Self {
103        Self {
104            is_array: true,
105            ..self
106        }
107    }
108
109    pub const fn set_primitive(self) -> Self {
110        Self {
111            is_primitive: true,
112            ..self
113        }
114    }
115}
116
117#[derive(Clone, Copy, Debug)]
118pub struct StructFieldComputed {
119    pub field: StructField,
120    pub fixed_offset: Option<usize>,
121}
122
123impl StructFieldComputed {
124    pub const fn new<const N: usize>(fields: [StructField; N]) -> [Self; N] {
125        // TODO: There's no way to prove to the compiler that the array is
126        // fully initialized yet so we dip into `unsafe`.
127        let mut out: [MaybeUninit<Self>; N] = [const { MaybeUninit::uninit() }; N];
128        let mut i = 0;
129        let mut fixed_offset = Some(0);
130        while i < N {
131            out[i] = MaybeUninit::new(Self {
132                field: fields[i],
133                fixed_offset,
134            });
135            if let Some(fixed_offset_value) = fixed_offset {
136                if let Some(size) = fields[i].meta.constant_size {
137                    fixed_offset = Some(fixed_offset_value + size);
138                } else {
139                    fixed_offset = None;
140                }
141            } else {
142                fixed_offset = None;
143            }
144            i += 1;
145        }
146        // SAFETY: This whole array is initialized. This can be replaced with
147        // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.array_assume_init
148        // once stable.
149        unsafe { out.as_ptr().cast::<[Self; N]>().read() }
150    }
151}
152
153pub struct StructFields {
154    pub fields: &'static [StructFieldComputed],
155    pub constant_size: Option<usize>,
156}
157
158impl StructFields {
159    pub const fn new(fields: &'static [StructFieldComputed]) -> Self {
160        let constant_size = Self::compute_constant_size(fields);
161        Self {
162            fields,
163            constant_size,
164        }
165    }
166
167    const fn compute_constant_size(fields: &'static [StructFieldComputed]) -> Option<usize> {
168        let mut i = 0;
169        let mut size = 0;
170        while i < fields.len() {
171            if let Some(constant_size) = fields[i].field.meta.constant_size {
172                size += constant_size;
173            } else {
174                return None;
175            }
176            i += 1;
177        }
178        Some(size)
179    }
180
181    pub const fn field_by_name(&self, name: &str) -> Option<usize> {
182        let mut i = 0;
183        while i < self.fields.len() {
184            if const_str::equal!(self.fields[i].field.name, name) {
185                return Some(i);
186            }
187            i += 1;
188        }
189        None
190    }
191
192    pub const fn field_by_index(&self, i: usize) -> &StructField {
193        &self.fields[i].field
194    }
195
196    pub const fn field_fixed_offset(&self, field_index: usize) -> Option<usize> {
197        self.fields[field_index].fixed_offset
198    }
199
200    pub const fn length_field_fixed_offset(&self) -> Option<usize> {
201        let mut i = 0;
202        while i < self.fields.len() {
203            if self.fields[i].field.meta.is_length {
204                return Some(i);
205            }
206            i += 1;
207        }
208        None
209    }
210
211    pub const fn constant_size(&self) -> Option<usize> {
212        self.constant_size
213    }
214
215    pub const fn matches_field_constants(&self, buf: &[u8]) -> bool {
216        let mut i = 0;
217        while i < self.fields.len() {
218            if let Some(value) = self.fields[i].field.value {
219                if let Some(fixed_offset) = self.fields[i].fixed_offset {
220                    if let Some(constant_size) = self.fields[i].field.meta.constant_size {
221                        if buf.len() < fixed_offset {
222                            return false;
223                        }
224                        let buf = buf.split_at(fixed_offset).1;
225                        if buf.len() < constant_size {
226                            return false;
227                        }
228                        let buf = buf.split_at(constant_size).0;
229                        match constant_size {
230                            1 => {
231                                if buf[0] != value as u8 {
232                                    return false;
233                                }
234                            }
235                            2 => {
236                                if value
237                                    != u16::from_be_bytes(*buf.split_first_chunk::<2>().unwrap().0)
238                                        as _
239                                {
240                                    return false;
241                                }
242                            }
243                            4 => {
244                                if value
245                                    != u32::from_be_bytes(*buf.split_first_chunk::<4>().unwrap().0)
246                                        as _
247                                {
248                                    return false;
249                                }
250                            }
251                            8 => {
252                                if value
253                                    != u64::from_be_bytes(*buf.split_first_chunk::<8>().unwrap().0)
254                                        as _
255                                {
256                                    return false;
257                                }
258                            }
259                            16 => {
260                                if value
261                                    != u128::from_be_bytes(
262                                        *buf.split_first_chunk::<16>().unwrap().0,
263                                    ) as _
264                                {
265                                    return false;
266                                }
267                            }
268                            _ => panic!("Unsupported constant size for field"),
269                        }
270                    }
271                }
272            }
273            i += 1;
274        }
275        true
276    }
277}
278
279impl<T: StructAttributeFixedSize<true>> DataTypeFixedSize for T {
280    const SIZE: usize = T::FIXED_SIZE.unwrap();
281}
282
283impl<T: StructAttributeHasLengthField<true> + StructMeta> StructLength for T {
284    fn length_of_buf(buf: &[u8]) -> Option<usize> {
285        let index = T::LENGTH_FIELD_INDEX.unwrap();
286        if buf.len() < index + std::mem::size_of::<u32>() {
287            None
288        } else {
289            let len = <u32 as DecoderFor<'_, u32>>::decode_for(
290                &mut &buf[index..index + std::mem::size_of::<u32>()],
291            )
292            .ok()?;
293            Some(index + len as usize)
294        }
295    }
296}
297
298/// Implemented for all generated structs that have a [`meta::Length`] field at a fixed offset.
299pub trait StructLength: StructMeta {
300    fn length_of_buf(buf: &[u8]) -> Option<usize>;
301}