gel_db_protocol/
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<'a>(buf: &'a [u8]) -> Result<Self::Struct<'a>, 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}
66
67impl StructFieldMeta {
68    pub const fn new(type_name: &'static str, constant_size: Option<usize>) -> Self {
69        Self {
70            type_name,
71            constant_size,
72            is_length: false,
73            is_enum: false,
74            is_struct: false,
75            is_array: false,
76        }
77    }
78
79    pub const fn set_length(self) -> Self {
80        Self {
81            is_length: true,
82            ..self
83        }
84    }
85
86    pub const fn set_enum(self) -> Self {
87        Self {
88            is_enum: true,
89            ..self
90        }
91    }
92
93    pub const fn set_struct(self) -> Self {
94        Self {
95            is_struct: true,
96            ..self
97        }
98    }
99
100    pub const fn set_array(self) -> Self {
101        Self {
102            is_array: true,
103            ..self
104        }
105    }
106}
107
108#[derive(Clone, Copy, Debug)]
109pub struct StructFieldComputed {
110    pub field: StructField,
111    pub fixed_offset: Option<usize>,
112}
113
114impl StructFieldComputed {
115    pub const fn new<const N: usize>(fields: [StructField; N]) -> [Self; N] {
116        // TODO: There's no way to prove to the compiler that the array is
117        // fully initialized yet so we dip into `unsafe`.
118        let mut out: [MaybeUninit<Self>; N] = [const { MaybeUninit::uninit() }; N];
119        let mut i = 0;
120        let mut fixed_offset = Some(0);
121        while i < N {
122            out[i] = MaybeUninit::new(Self {
123                field: fields[i],
124                fixed_offset,
125            });
126            if let Some(fixed_offset_value) = fixed_offset {
127                if let Some(size) = fields[i].meta.constant_size {
128                    fixed_offset = Some(fixed_offset_value + size);
129                } else {
130                    fixed_offset = None;
131                }
132            } else {
133                fixed_offset = None;
134            }
135            i += 1;
136        }
137        // SAFETY: This whole array is initialized. This can be replaced with
138        // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.array_assume_init
139        // once stable.
140        unsafe { out.as_ptr().cast::<[Self; N]>().read() }
141    }
142}
143
144pub struct StructFields {
145    pub fields: &'static [StructFieldComputed],
146    pub constant_size: Option<usize>,
147}
148
149impl StructFields {
150    pub const fn new(fields: &'static [StructFieldComputed]) -> Self {
151        let constant_size = Self::compute_constant_size(fields);
152        Self {
153            fields,
154            constant_size,
155        }
156    }
157
158    const fn compute_constant_size(fields: &'static [StructFieldComputed]) -> Option<usize> {
159        let mut i = 0;
160        let mut size = 0;
161        while i < fields.len() {
162            if let Some(constant_size) = fields[i].field.meta.constant_size {
163                size += constant_size;
164            } else {
165                return None;
166            }
167            i += 1;
168        }
169        Some(size)
170    }
171
172    pub const fn field_by_name(&self, name: &str) -> Option<usize> {
173        let mut i = 0;
174        while i < self.fields.len() {
175            if const_str::equal!(self.fields[i].field.name, name) {
176                return Some(i);
177            }
178            i += 1;
179        }
180        None
181    }
182
183    pub const fn field_by_index(&self, i: usize) -> &StructField {
184        &self.fields[i].field
185    }
186
187    pub const fn field_fixed_offset(&self, field_index: usize) -> Option<usize> {
188        self.fields[field_index].fixed_offset
189    }
190
191    pub const fn length_field_fixed_offset(&self) -> Option<usize> {
192        let mut i = 0;
193        while i < self.fields.len() {
194            if self.fields[i].field.meta.is_length {
195                return Some(i);
196            }
197            i += 1;
198        }
199        None
200    }
201
202    pub const fn constant_size(&self) -> Option<usize> {
203        self.constant_size
204    }
205
206    pub const fn matches_field_constants(&self, buf: &[u8]) -> bool {
207        let mut i = 0;
208        while i < self.fields.len() {
209            if let Some(value) = self.fields[i].field.value {
210                if let Some(fixed_offset) = self.fields[i].fixed_offset {
211                    if let Some(constant_size) = self.fields[i].field.meta.constant_size {
212                        let buf = buf.split_at(fixed_offset).1;
213                        if buf.len() < constant_size {
214                            return false;
215                        }
216                        let buf = buf.split_at(constant_size).0;
217                        match constant_size {
218                            1 => {
219                                if buf[0] != value as u8 {
220                                    return false;
221                                }
222                            }
223                            2 => {
224                                if value
225                                    != u16::from_be_bytes(*buf.split_first_chunk::<2>().unwrap().0)
226                                        as _
227                                {
228                                    return false;
229                                }
230                            }
231                            4 => {
232                                if value
233                                    != u32::from_be_bytes(*buf.split_first_chunk::<4>().unwrap().0)
234                                        as _
235                                {
236                                    return false;
237                                }
238                            }
239                            8 => {
240                                if value
241                                    != u64::from_be_bytes(*buf.split_first_chunk::<8>().unwrap().0)
242                                        as _
243                                {
244                                    return false;
245                                }
246                            }
247                            16 => {
248                                if value
249                                    != u128::from_be_bytes(
250                                        *buf.split_first_chunk::<16>().unwrap().0,
251                                    ) as _
252                                {
253                                    return false;
254                                }
255                            }
256                            _ => panic!("Unsupported constant size for field"),
257                        }
258                    }
259                }
260            }
261            i += 1;
262        }
263        true
264    }
265}
266
267impl<T: StructAttributeFixedSize<true>> DataTypeFixedSize for T {
268    const SIZE: usize = T::FIXED_SIZE.unwrap();
269}
270
271impl<T: StructAttributeHasLengthField<true> + StructMeta> StructLength for T {
272    fn length_of_buf(buf: &[u8]) -> Option<usize> {
273        let index = T::LENGTH_FIELD_INDEX.unwrap();
274        if buf.len() < index + std::mem::size_of::<u32>() {
275            None
276        } else {
277            let len =
278                <u32 as DataType>::decode(&mut &buf[index..index + std::mem::size_of::<u32>()])
279                    .ok()?;
280            Some(index + len as usize)
281        }
282    }
283}
284
285/// Implemented for all generated structs that have a [`meta::Length`] field at a fixed offset.
286pub trait StructLength: StructMeta {
287    fn length_of_buf(buf: &[u8]) -> Option<usize>;
288}