Skip to main content

harmos_domain/schema/graph/
codec.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::borrow::Cow;
18use std::mem::size_of;
19// Third Party Imports
20
21// Local Imports
22use super::{Field, Kind, Schema};
23use crate::config::SchemaSettings;
24use crate::message::{MessageError, PostcardReader, PostcardValue, decoding_vector};
25use crate::schema::declaration::{self, Definition};
26use crate::schema::{Cardinality, Described, MAX_NAME_BYTES};
27
28/* === Constants === */
29
30/* === Macros === */
31
32macro_rules! shape {
33    ($ty:ty, $name:literal, $fields:expr) => {
34        impl Described for $ty {
35            const NAME: &'static str = $name;
36            const FIELDS: &'static [Field<Definition>] = $fields;
37            const MARKER: &'static [u8] =
38                &declaration::encode::<{ declaration::encoded_len(Self::NAME, Self::FIELDS) }>(Self::NAME, Self::FIELDS);
39        }
40    };
41}
42
43shape!(
44    Schema,
45    "harmos::schema::Graph",
46    &[
47        described("root", 1, declaration::Kind::U64, Cardinality::Required),
48        described("definitions", 2, declaration::Kind::Message { definition: DefinitionShape::DEFINITION }, Cardinality::Repeated),
49    ]
50);
51shape!(
52    DefinitionShape,
53    "harmos::schema::Definition",
54    &[described("fields", 1, declaration::Kind::Message { definition: FieldShape::DEFINITION }, Cardinality::Repeated),]
55);
56shape!(
57    FieldShape,
58    "harmos::schema::Field",
59    &[
60        described("name", 1, declaration::Kind::String, Cardinality::Required),
61        described("tag", 2, declaration::Kind::U32, Cardinality::Required),
62        described("kind", 3, declaration::Kind::U32, Cardinality::Required),
63        described("cardinality", 4, declaration::Kind::U32, Cardinality::Required),
64        described("definition", 5, declaration::Kind::U64, Cardinality::Optional),
65    ]
66);
67
68/* === Type Aliases === */
69
70type Result<T> = std::result::Result<T, MessageError>;
71
72/* === Structs === */
73
74struct DefinitionShape;
75struct FieldShape;
76
77struct Node<'a> {
78    name: &'a str,
79    tag: u32,
80    kind: u32,
81    cardinality: u32,
82    definition: Option<usize>,
83}
84
85struct Budget {
86    bytes: usize,
87    work: usize,
88    settings: SchemaSettings,
89}
90
91/* === Enums === */
92
93/* === Traits === */
94
95/* === Implementations === */
96
97impl Budget {
98    fn new() -> Self {
99        Self { bytes: size_of::<Schema>(), work: 0, settings: SchemaSettings::default() }
100    }
101
102    fn charge(&mut self, bytes: usize, work: usize) -> Result<()> {
103        self.bytes = self.bytes.checked_add(bytes).ok_or_else(invalid)?;
104        self.work = self.work.checked_add(work).ok_or_else(invalid)?;
105        if self.bytes > self.settings.preparation_bytes.get() || self.work > self.settings.preparation_work.get() {
106            return Err(invalid());
107        }
108        Ok(())
109    }
110
111    fn definition(&mut self, fields: usize) -> Result<()> {
112        let bits = usize::BITS as usize - fields.leading_zeros() as usize;
113        let work = fields.checked_mul(2 * bits + 3).ok_or_else(invalid)?;
114        self.charge(4 * size_of::<Vec<Field>>() + size_of::<usize>() + size_of::<bool>(), work + 1)
115    }
116
117    fn field(&mut self, node: &Node<'_>) -> Result<()> {
118        node.check()?;
119        self.charge(4 * size_of::<Field>() + 2 * node.name.len() + size_of::<&str>() + size_of::<u32>(), node.name.len() + 1)
120    }
121}
122
123impl Node<'_> {
124    fn check(&self) -> Result<()> {
125        if self.name.is_empty()
126            || self.name.len() > MAX_NAME_BYTES
127            || self.name.chars().any(char::is_control)
128            || self.tag == 0
129            || self.tag > 536_870_911
130            || (19_000..=19_999).contains(&self.tag)
131            || self.kind > 9
132            || self.cardinality > 3
133            || (self.kind == 9) != self.definition.is_some()
134        {
135            return Err(invalid());
136        }
137        Ok(())
138    }
139
140    fn owned(self) -> Result<Field> {
141        self.check()?;
142        let kind = match self.kind {
143            0 => Kind::Bool,
144            1 => Kind::U32,
145            2 => Kind::U64,
146            3 => Kind::I32,
147            4 => Kind::I64,
148            5 => Kind::F32,
149            6 => Kind::F64,
150            7 => Kind::String,
151            8 => Kind::Bytes,
152            9 => Kind::Message { definition: self.definition.ok_or_else(invalid)? },
153            _ => return Err(invalid()),
154        };
155        let cardinality = match self.cardinality {
156            0 => Cardinality::Required,
157            1 => Cardinality::Optional,
158            2 => Cardinality::Repeated,
159            3 => Cardinality::Variant,
160            _ => return Err(invalid()),
161        };
162        Ok(Field { name: self.name.to_owned().into(), tag: self.tag, kind, cardinality })
163    }
164}
165
166impl<'a> From<&'a Field> for Node<'a> {
167    fn from(field: &'a Field) -> Self {
168        let (kind, definition) = match field.kind {
169            Kind::Bool => (0, None),
170            Kind::U32 => (1, None),
171            Kind::U64 => (2, None),
172            Kind::I32 => (3, None),
173            Kind::I64 => (4, None),
174            Kind::F32 => (5, None),
175            Kind::F64 => (6, None),
176            Kind::String => (7, None),
177            Kind::Bytes => (8, None),
178            Kind::Message { definition } => (9, Some(definition)),
179        };
180        let cardinality = match field.cardinality {
181            Cardinality::Required => 0,
182            Cardinality::Optional => 1,
183            Cardinality::Repeated => 2,
184            Cardinality::Variant => 3,
185        };
186        Self { name: &field.name, tag: field.tag, kind, cardinality, definition }
187    }
188}
189
190impl PostcardValue for Schema {
191    fn write(&self, bytes: &mut Vec<u8>) {
192        self.root.write(bytes);
193        self.definitions.len().write(bytes);
194        for fields in &self.definitions {
195            fields.len().write(bytes);
196            for field in fields {
197                let node = Node::from(field);
198                node.name.len().write(bytes);
199                bytes.extend_from_slice(node.name.as_bytes());
200                node.tag.write(bytes);
201                node.kind.write(bytes);
202                node.cardinality.write(bytes);
203                match node.definition {
204                    None => 0u32.write(bytes),
205                    Some(definition) => {
206                        1u32.write(bytes);
207                        definition.write(bytes);
208                    }
209                }
210            }
211        }
212    }
213
214    fn size(&self) -> usize {
215        let mut size = self.root.size().saturating_add(self.definitions.len().size());
216        for fields in &self.definitions {
217            size = size.saturating_add(fields.len().size());
218            for field in fields {
219                let node = Node::from(field);
220                let reference = match node.definition {
221                    None => 1,
222                    Some(index) => 1usize.saturating_add(index.size()),
223                };
224                size = size
225                    .saturating_add(node.name.len().size())
226                    .saturating_add(node.name.len())
227                    .saturating_add(node.tag.size())
228                    .saturating_add(node.kind.size())
229                    .saturating_add(node.cardinality.size())
230                    .saturating_add(reference);
231            }
232        }
233        size
234    }
235
236    fn memory(input: &mut PostcardReader<'_>) -> Result<usize> {
237        input.nested(|input| {
238            let root = input.value::<usize>()?;
239            let count = input.value::<usize>()?;
240            if root >= count {
241                return Err(invalid());
242            }
243            input.claim(count)?;
244            let mut budget = Budget::new();
245            for _ in 0..count {
246                let fields = input.value::<usize>()?;
247                input.claim(fields)?;
248                budget.definition(fields)?;
249                for _ in 0..fields {
250                    let node = postcard_node(input)?;
251                    if node.definition.is_some_and(|reference| reference >= count) {
252                        return Err(invalid());
253                    }
254                    budget.field(&node)?;
255                }
256            }
257            Ok(budget.bytes)
258        })
259    }
260
261    fn read(input: &mut PostcardReader<'_>) -> Result<Self> {
262        Self::memory(&mut input.clone())?;
263        input.nested(|input| {
264            let root = input.value()?;
265            let count = input.value::<usize>()?;
266            input.claim(count)?;
267            let mut definitions = decoding_vector(count)?;
268            for _ in 0..count {
269                let count = input.value::<usize>()?;
270                input.claim(count)?;
271                let mut fields = decoding_vector(count)?;
272                for _ in 0..count {
273                    fields.push(postcard_node(input)?.owned()?);
274                }
275                definitions.push(fields);
276            }
277            let schema = Self { root, definitions };
278            schema.prepare(&SchemaSettings::default()).map_err(|_| invalid())?;
279            Ok(schema)
280        })
281    }
282}
283
284/* === Public Functions === */
285
286/* === Private Functions === */
287
288const fn described(name: &'static str, tag: u32, kind: declaration::Kind, cardinality: Cardinality) -> Field<Definition> {
289    Field { name: Cow::Borrowed(name), tag, kind, cardinality }
290}
291
292fn postcard_node<'a>(input: &mut PostcardReader<'a>) -> Result<Node<'a>> {
293    let length = input.value::<usize>()?;
294    if length > MAX_NAME_BYTES {
295        return Err(invalid());
296    }
297    let name = std::str::from_utf8(input.take(length)?).map_err(|_| invalid())?;
298    let tag = input.value()?;
299    let kind = input.value()?;
300    let cardinality = input.value()?;
301    let definition = match input.value::<u32>()? {
302        0 => None,
303        1 => Some(input.value()?),
304        _ => return Err(invalid()),
305    };
306    Ok(Node { name, tag, kind, cardinality, definition })
307}
308
309fn invalid() -> MessageError {
310    MessageError::borrowed("invalid finite schema graph or preparation allowance")
311}
312
313/* === Tests === */