Skip to main content

zerodds_ast/
snapshot.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Model snapshot. Spec §9.
4//!
5//! The whole model serialises to a deterministic binary form and loads
6//! back with every `NodeId` preserved. Determinism follows from the
7//! arena's ascending-slot iteration order rather than from a
8//! serialisation library's field ordering, which is why a snapshot hash
9//! can key a build cache.
10//!
11//! Every node kind the model holds round-trips: modules, expressions,
12//! constants, types and their parts, interfaces with their operations,
13//! parameters, attributes and exceptions, the deployment layer, the
14//! annotation declarations, and the facet payload on all of them.
15
16use crate::error::AstError;
17use crate::facet::Facets;
18use crate::model::Ast;
19use crate::node::AnyId;
20use crate::node::type_::{Bound, CharWidth, Completeness, PrimitiveKind, TypeKind};
21use alloc::string::String;
22use alloc::vec::Vec;
23use zerodds_ast_core::{AstCoreError, Id, IdSpace, NodeId, Ref, SnapshotReader, SnapshotWriter};
24
25/// Tag numbers for `TypeKind`, part of the snapshot format.
26mod tag {
27    pub const PRIMITIVE: u8 = 1;
28    pub const FIXED: u8 = 2;
29    pub const STRING: u8 = 3;
30    pub const SEQUENCE: u8 = 4;
31    pub const ARRAY: u8 = 5;
32    pub const MAP: u8 = 6;
33    pub const STRUCTURE: u8 = 7;
34    pub const UNION: u8 = 8;
35    pub const ENUMERATION: u8 = 9;
36    pub const BITMASK: u8 = 10;
37    pub const BITSET: u8 = 11;
38    pub const ALIAS: u8 = 12;
39    pub const NATIVE: u8 = 13;
40    pub const VALUETYPE: u8 = 14;
41    pub const VALUE_BOX: u8 = 15;
42    pub const OBJECT_REF: u8 = 16;
43    pub const ANY: u8 = 17;
44    pub const VALUE_BASE: u8 = 18;
45    pub const ANNOTATION_TYPE: u8 = 19;
46}
47
48impl Ast {
49    /// Serialise the model.
50    ///
51    /// # Errors
52    /// [`AstError::Core`] when a name or payload exceeds what the length
53    /// prefix can express.
54    pub fn to_snapshot(&self) -> Result<Vec<u8>, AstError> {
55        let mut w = SnapshotWriter::with_header();
56
57        // Identity high-water marks, so a loaded model continues where
58        // this one left off rather than minting duplicates.
59        w.u64(self.model_ids.next_counter());
60        w.u64(self.builtin_ids.next_counter());
61        w.u64(self.synthetic_ids.next_counter());
62        w.u64(self.sequence);
63
64        // Modules, in ascending slot order.
65        w.u32(count(self.modules.slot_count())?);
66        w.u32(count(self.modules.len())?);
67        for (id, module) in self.modules.iter() {
68            w.u32(id.slot());
69            w.u32(id.generation());
70            w.node_id(module.node_id);
71            w.str(&module.name)?;
72            match module.parent {
73                Some(parent) => {
74                    w.bool(true);
75                    w.u32(parent.slot());
76                    w.u32(parent.generation());
77                }
78                None => w.bool(false),
79            }
80            write_facets(&mut w, &module.facets)?;
81        }
82
83        // Expressions before the types that reference them.
84        w.u32(count(self.exprs.slot_count())?);
85        w.u32(count(self.exprs.len())?);
86        for (id, expr) in self.exprs.iter() {
87            w.u32(id.slot());
88            w.u32(id.generation());
89            w.node_id(expr.node_id);
90            write_expr(&mut w, &expr.expr)?;
91            write_facets(&mut w, &expr.facets)?;
92        }
93
94        // Types.
95        w.u32(count(self.types.slot_count())?);
96        w.u32(count(self.types.len())?);
97        for (id, node) in self.types.iter() {
98            w.u32(id.slot());
99            w.u32(id.generation());
100            w.node_id(node.node_id);
101            w.str(&node.name)?;
102            w.u32(node.module.slot());
103            w.u32(node.module.generation());
104            write_str_opt(&mut w, node.scope.as_deref())?;
105            w.bool(node.completeness == Completeness::Complete);
106            write_kind(&mut w, &node.kind)?;
107            write_facets(&mut w, &node.facets)?;
108        }
109
110        // Members, after the types they belong to.
111        w.u32(count(self.members.slot_count())?);
112        w.u32(count(self.members.len())?);
113        for (id, member) in self.members.iter() {
114            w.u32(id.slot());
115            w.u32(id.generation());
116            w.node_id(member.node_id);
117            w.str(&member.name)?;
118            write_type_ref(&mut w, &member.type_)?;
119            // A member is owned by a type or by an exception; encoding
120            // only the type case loses every exception member.
121            match member.parent {
122                AnyId::Type(id, _) => {
123                    w.u8(1);
124                    w.u32(id.slot());
125                    w.u32(id.generation());
126                }
127                AnyId::Exception(id, _) => {
128                    w.u8(2);
129                    w.u32(id.slot());
130                    w.u32(id.generation());
131                }
132                _ => w.u8(0),
133            }
134            write_facets(&mut w, &member.facets)?;
135        }
136
137        // Cases, enumerators, bit values and bitfields, each after the
138        // type that owns them.
139        w.u32(count(self.cases.slot_count())?);
140        w.u32(count(self.cases.len())?);
141        for (id, case) in self.cases.iter() {
142            w.u32(id.slot());
143            w.u32(id.generation());
144            w.node_id(case.node_id);
145            w.u32(case.parent.slot());
146            w.u32(case.parent.generation());
147            w.u32(case.member.slot());
148            w.u32(case.member.generation());
149            w.u32(count(case.labels.len())?);
150            for label in &case.labels {
151                match label {
152                    crate::CaseLabel::Default => w.u8(0),
153                    crate::CaseLabel::Value(expr) => {
154                        w.u8(1);
155                        w.u32(expr.slot());
156                        w.u32(expr.generation());
157                    }
158                }
159            }
160            write_facets(&mut w, &case.facets)?;
161        }
162
163        w.u32(count(self.enumerators.slot_count())?);
164        w.u32(count(self.enumerators.len())?);
165        for (id, node) in self.enumerators.iter() {
166            w.u32(id.slot());
167            w.u32(id.generation());
168            w.node_id(node.node_id);
169            w.str(&node.name)?;
170            w.u32(node.parent.slot());
171            w.u32(node.parent.generation());
172            match node.value {
173                Some(expr) => {
174                    w.bool(true);
175                    w.u32(expr.slot());
176                    w.u32(expr.generation());
177                }
178                None => w.bool(false),
179            }
180            write_facets(&mut w, &node.facets)?;
181        }
182
183        w.u32(count(self.bit_values.slot_count())?);
184        w.u32(count(self.bit_values.len())?);
185        for (id, node) in self.bit_values.iter() {
186            w.u32(id.slot());
187            w.u32(id.generation());
188            w.node_id(node.node_id);
189            w.str(&node.name)?;
190            w.u32(node.parent.slot());
191            w.u32(node.parent.generation());
192            match node.position {
193                Some(position) => {
194                    w.bool(true);
195                    w.u16(position);
196                }
197                None => w.bool(false),
198            }
199            write_facets(&mut w, &node.facets)?;
200        }
201
202        w.u32(count(self.bitfields.slot_count())?);
203        w.u32(count(self.bitfields.len())?);
204        for (id, node) in self.bitfields.iter() {
205            w.u32(id.slot());
206            w.u32(id.generation());
207            w.node_id(node.node_id);
208            match node.name.as_deref() {
209                Some(name) => {
210                    w.bool(true);
211                    w.str(name)?;
212                }
213                None => w.bool(false),
214            }
215            w.u16(node.bits);
216            w.u32(node.parent.slot());
217            w.u32(node.parent.generation());
218            write_type_ref_opt(&mut w, node.holder_type.as_ref())?;
219            write_facets(&mut w, &node.facets)?;
220        }
221
222        // Constants, after the expressions they point at.
223        w.u32(count(self.constants.slot_count())?);
224        w.u32(count(self.constants.len())?);
225        for (id, node) in self.constants.iter() {
226            w.u32(id.slot());
227            w.u32(id.generation());
228            w.node_id(node.node_id);
229            w.str(&node.name)?;
230            w.u32(node.module.slot());
231            w.u32(node.module.generation());
232            write_str_opt(&mut w, node.scope.as_deref())?;
233            w.u32(node.expr.slot());
234            w.u32(node.expr.generation());
235            write_type_ref(&mut w, &node.type_)?;
236            write_facets(&mut w, &node.facets)?;
237        }
238
239        // Exceptions before the operations that raise them.
240        w.u32(count(self.exceptions.slot_count())?);
241        w.u32(count(self.exceptions.len())?);
242        for (id, node) in self.exceptions.iter() {
243            w.u32(id.slot());
244            w.u32(id.generation());
245            w.node_id(node.node_id);
246            w.str(&node.name)?;
247            w.u32(node.module.slot());
248            w.u32(node.module.generation());
249            write_str_opt(&mut w, node.scope.as_deref())?;
250            write_handles(&mut w, &node.members)?;
251            write_facets(&mut w, &node.facets)?;
252        }
253
254        // Interfaces, then their operations, parameters and attributes.
255        w.u32(count(self.interfaces.slot_count())?);
256        w.u32(count(self.interfaces.len())?);
257        for (id, node) in self.interfaces.iter() {
258            w.u32(id.slot());
259            w.u32(id.generation());
260            w.node_id(node.node_id);
261            w.str(&node.name)?;
262            w.u32(node.module.slot());
263            w.u32(node.module.generation());
264            w.u8(interface_kind_tag(node.kind));
265            w.bool(node.completeness == Completeness::Complete);
266            w.u32(count(node.bases.len())?);
267            for base in &node.bases {
268                write_ref(&mut w, base)?;
269            }
270            write_handles(&mut w, &node.operations)?;
271            write_handles(&mut w, &node.attributes)?;
272            write_facets(&mut w, &node.facets)?;
273        }
274
275        w.u32(count(self.operations.slot_count())?);
276        w.u32(count(self.operations.len())?);
277        for (id, node) in self.operations.iter() {
278            w.u32(id.slot());
279            w.u32(id.generation());
280            w.node_id(node.node_id);
281            w.str(&node.name)?;
282            write_type_ref_opt(&mut w, node.return_type.as_ref())?;
283            w.bool(node.oneway);
284            write_handles(&mut w, &node.params)?;
285            w.u32(count(node.raises.len())?);
286            for raise in &node.raises {
287                write_ref(&mut w, raise)?;
288            }
289            w.u32(count(node.contexts.len())?);
290            for context in &node.contexts {
291                w.str(context)?;
292            }
293            write_any_parent(&mut w, node.parent);
294            write_facets(&mut w, &node.facets)?;
295        }
296
297        w.u32(count(self.params.slot_count())?);
298        w.u32(count(self.params.len())?);
299        for (id, node) in self.params.iter() {
300            w.u32(id.slot());
301            w.u32(id.generation());
302            w.node_id(node.node_id);
303            w.str(&node.name)?;
304            w.u8(direction_tag(node.direction));
305            write_type_ref(&mut w, &node.type_)?;
306            w.u32(node.parent.slot());
307            w.u32(node.parent.generation());
308            write_facets(&mut w, &node.facets)?;
309        }
310
311        w.u32(count(self.attributes.slot_count())?);
312        w.u32(count(self.attributes.len())?);
313        for (id, node) in self.attributes.iter() {
314            w.u32(id.slot());
315            w.u32(id.generation());
316            w.node_id(node.node_id);
317            w.str(&node.name)?;
318            write_type_ref(&mut w, &node.type_)?;
319            w.bool(node.readonly);
320            w.u32(node.parent.slot());
321            w.u32(node.parent.generation());
322            w.u32(count(node.get_raises.len())?);
323            for raise in &node.get_raises {
324                write_ref(&mut w, raise)?;
325            }
326            w.u32(count(node.set_raises.len())?);
327            for raise in &node.set_raises {
328                write_ref(&mut w, raise)?;
329            }
330            write_facets(&mut w, &node.facets)?;
331        }
332
333        // Annotation declarations and their members.
334        w.u32(count(self.annotation_defs.slot_count())?);
335        w.u32(count(self.annotation_defs.len())?);
336        for (id, node) in self.annotation_defs.iter() {
337            w.u32(id.slot());
338            w.u32(id.generation());
339            w.node_id(node.node_id);
340            w.str(&node.name)?;
341            w.u32(node.module.slot());
342            w.u32(node.module.generation());
343            write_handles(&mut w, &node.members)?;
344            write_facets(&mut w, &node.facets)?;
345        }
346
347        w.u32(count(self.annotation_members.slot_count())?);
348        w.u32(count(self.annotation_members.len())?);
349        for (id, node) in self.annotation_members.iter() {
350            w.u32(id.slot());
351            w.u32(id.generation());
352            w.node_id(node.node_id);
353            w.str(&node.name)?;
354            write_type_ref(&mut w, &node.type_)?;
355            w.bool(node.is_value_member);
356            w.u32(node.parent.slot());
357            w.u32(node.parent.generation());
358            match node.default {
359                Some(expr) => {
360                    w.bool(true);
361                    w.u32(expr.slot());
362                    w.u32(expr.generation());
363                }
364                None => w.bool(false),
365            }
366            write_facets(&mut w, &node.facets)?;
367        }
368
369        // The deployment layer.
370        w.u32(count(self.libraries.slot_count())?);
371        w.u32(count(self.libraries.len())?);
372        for (id, node) in self.libraries.iter() {
373            w.u32(id.slot());
374            w.u32(id.generation());
375            w.node_id(node.node_id);
376            w.str(&node.name)?;
377            write_facets(&mut w, &node.facets)?;
378        }
379
380        w.u32(count(self.domains.slot_count())?);
381        w.u32(count(self.domains.len())?);
382        for (id, node) in self.domains.iter() {
383            w.u32(id.slot());
384            w.u32(id.generation());
385            w.node_id(node.node_id);
386            w.str(&node.name)?;
387            w.u32(node.domain_id);
388            w.u32(node.parent.slot());
389            w.u32(node.parent.generation());
390            write_facets(&mut w, &node.facets)?;
391        }
392
393        w.u32(count(self.qos_profiles.slot_count())?);
394        w.u32(count(self.qos_profiles.len())?);
395        for (id, node) in self.qos_profiles.iter() {
396            w.u32(id.slot());
397            w.u32(id.generation());
398            w.node_id(node.node_id);
399            w.str(&node.name)?;
400            w.u32(node.parent.slot());
401            w.u32(node.parent.generation());
402            write_ref_opt(&mut w, node.base.as_ref())?;
403            write_policies(&mut w, &node.policies)?;
404            write_facets(&mut w, &node.facets)?;
405        }
406
407        w.u32(count(self.topics.slot_count())?);
408        w.u32(count(self.topics.len())?);
409        for (id, node) in self.topics.iter() {
410            w.u32(id.slot());
411            w.u32(id.generation());
412            w.node_id(node.node_id);
413            w.str(&node.name)?;
414            write_type_ref(&mut w, &node.type_)?;
415            write_ref_opt(&mut w, node.qos.as_ref())?;
416            w.u32(node.parent.slot());
417            w.u32(node.parent.generation());
418            write_facets(&mut w, &node.facets)?;
419        }
420
421        w.u32(count(self.participants.slot_count())?);
422        w.u32(count(self.participants.len())?);
423        for (id, node) in self.participants.iter() {
424            w.u32(id.slot());
425            w.u32(id.generation());
426            w.node_id(node.node_id);
427            w.str(&node.name)?;
428            write_ref(&mut w, &node.domain)?;
429            write_ref_opt(&mut w, node.qos.as_ref())?;
430            w.u32(node.parent.slot());
431            w.u32(node.parent.generation());
432            write_facets(&mut w, &node.facets)?;
433        }
434
435        w.u32(count(self.endpoints.slot_count())?);
436        w.u32(count(self.endpoints.len())?);
437        for (id, node) in self.endpoints.iter() {
438            w.u32(id.slot());
439            w.u32(id.generation());
440            w.node_id(node.node_id);
441            w.str(&node.name)?;
442            w.bool(node.role == crate::EndpointRole::DataWriter);
443            write_ref(&mut w, &node.topic)?;
444            write_ref_opt(&mut w, node.qos.as_ref())?;
445            w.u32(node.parent.slot());
446            w.u32(node.parent.generation());
447            write_facets(&mut w, &node.facets)?;
448        }
449
450        Ok(w.finish())
451    }
452
453    /// Load a model from a snapshot.
454    ///
455    /// Every `NodeId` is preserved and the indices are rebuilt, so the
456    /// loaded model is usable immediately.
457    ///
458    /// # Errors
459    /// [`AstError::Core`] with [`AstCoreError::MalformedSnapshot`] on a
460    /// wrong magic, an unknown version, truncation or an out-of-range
461    /// handle. Never panics; this is a fuzz target.
462    pub fn from_snapshot(bytes: &[u8]) -> Result<Self, AstError> {
463        let mut r = SnapshotReader::with_header(bytes)?;
464        let mut ast = Self::empty_for_load();
465
466        ast.model_ids.set_next_counter(r.u64()?);
467        ast.builtin_ids.set_next_counter(r.u64()?);
468        ast.synthetic_ids.set_next_counter(r.u64()?);
469        ast.sequence = r.u64()?;
470
471        let module_slots = bounded_count(&mut r, 1)?;
472        let module_count = bounded_count(&mut r, min_size::MODULE)?;
473        for _ in 0..module_count {
474            let slot = bounded_slot(&mut r, module_slots)?;
475            let generation = r.u32()?;
476            let node_id = r.node_id()?;
477            let name = r.string()?;
478            let parent = if r.bool()? {
479                Some(Id::from_parts(r.u32()?, r.u32()?)?)
480            } else {
481                None
482            };
483            let facets = read_facets(&mut r)?;
484            let id = ast.modules.insert_at(
485                slot,
486                generation,
487                crate::ModuleNode {
488                    node_id,
489                    name,
490                    parent,
491                    children: Vec::new(),
492                    facets,
493                },
494            )?;
495            let _ = ast.by_node_id.insert(node_id, AnyId::Module(id, node_id));
496        }
497
498        let expr_slots = bounded_count(&mut r, 1)?;
499        let expr_count = bounded_count(&mut r, min_size::EXPR)?;
500        for _ in 0..expr_count {
501            let slot = bounded_slot(&mut r, expr_slots)?;
502            let generation = r.u32()?;
503            let node_id = r.node_id()?;
504            let expr = read_expr(&mut r)?;
505            let facets = read_facets(&mut r)?;
506            let id = ast.exprs.insert_at(
507                slot,
508                generation,
509                crate::ConstExprNode {
510                    node_id,
511                    expr,
512                    facets,
513                },
514            )?;
515            let _ = ast
516                .by_node_id
517                .insert(node_id, AnyId::ConstExpr(id, node_id));
518        }
519
520        let type_slots = bounded_count(&mut r, 1)?;
521        let type_count = bounded_count(&mut r, min_size::TYPE)?;
522        for _ in 0..type_count {
523            let slot = bounded_slot(&mut r, type_slots)?;
524            let generation = r.u32()?;
525            let node_id = r.node_id()?;
526            let name = r.string()?;
527            let module = Id::from_parts(r.u32()?, r.u32()?)?;
528            let scope = read_str_opt(&mut r)?;
529            let completeness = if r.bool()? {
530                Completeness::Complete
531            } else {
532                Completeness::Forward
533            };
534            let kind = read_kind(&mut r)?;
535            let facets = read_facets(&mut r)?;
536            let id = ast.types.insert_at(
537                slot,
538                generation,
539                crate::TypeNode {
540                    node_id,
541                    name,
542                    module,
543                    scope,
544                    kind,
545                    completeness,
546                    facets,
547                },
548            )?;
549            let any = AnyId::Type(id, node_id);
550            let _ = ast.by_node_id.insert(node_id, any);
551            if let Ok(m) = ast.modules.get_mut(module) {
552                m.children.push(any);
553            }
554        }
555
556        let member_slots = bounded_count(&mut r, 1)?;
557        let member_count = bounded_count(&mut r, min_size::MEMBER)?;
558        for _ in 0..member_count {
559            let slot = bounded_slot(&mut r, member_slots)?;
560            let generation = r.u32()?;
561            let node_id = r.node_id()?;
562            let name = r.string()?;
563            let type_ = read_type_ref(&mut r)?;
564            let owner_tag = r.u8()?;
565            let owner = if owner_tag == 0 {
566                None
567            } else {
568                Some((r.u32()?, r.u32()?))
569            };
570            let facets = read_facets(&mut r)?;
571
572            let parent = match (owner_tag, owner) {
573                (1, Some((slot, generation))) => {
574                    let id: Id<crate::TypeNode> = Id::from_parts(slot, generation)?;
575                    AnyId::Type(id, ast.types.get(id)?.node_id)
576                }
577                (2, Some((slot, generation))) => {
578                    // Exceptions are written after members, so the
579                    // owner's identity is not known yet; it is filled in
580                    // by the fix-up pass once that section is read.
581                    let id: Id<crate::ExceptionNode> = Id::from_parts(slot, generation)?;
582                    AnyId::Exception(id, NodeId::from_parts(IdSpace::MODEL, 0))
583                }
584                _ => {
585                    return Err(AstError::Core(AstCoreError::MalformedSnapshot {
586                        offset: r.offset(),
587                        expected: "a member's owning type or exception",
588                    }));
589                }
590            };
591
592            let id = ast.members.insert_at(
593                slot,
594                generation,
595                crate::MemberNode {
596                    node_id,
597                    name,
598                    type_,
599                    parent,
600                    facets,
601                },
602            )?;
603            let _ = ast.by_node_id.insert(node_id, AnyId::Member(id, node_id));
604        }
605
606        // Cases, enumerators, bit values, bitfields.
607        let case_slots = bounded_count(&mut r, 1)?;
608        let case_count = bounded_count(&mut r, 24)?;
609        for _ in 0..case_count {
610            let slot = bounded_slot(&mut r, case_slots)?;
611            let generation = r.u32()?;
612            let node_id = r.node_id()?;
613            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
614            let member = Id::from_parts(r.u32()?, r.u32()?)?;
615            let label_count = bounded_count(&mut r, 1)? as usize;
616            let mut labels = Vec::new();
617            for _ in 0..label_count {
618                labels.push(match r.u8()? {
619                    0 => crate::CaseLabel::Default,
620                    1 => crate::CaseLabel::Value(Id::from_parts(r.u32()?, r.u32()?)?),
621                    _ => {
622                        return Err(AstError::Core(AstCoreError::MalformedSnapshot {
623                            offset: r.offset(),
624                            expected: "case label tag 0 or 1",
625                        }));
626                    }
627                });
628            }
629            let facets = read_facets(&mut r)?;
630            let id = ast.cases.insert_at(
631                slot,
632                generation,
633                crate::CaseNode {
634                    node_id,
635                    labels,
636                    member,
637                    parent,
638                    facets,
639                },
640            )?;
641            let _ = ast.by_node_id.insert(node_id, AnyId::Case(id, node_id));
642        }
643
644        let enum_slots = bounded_count(&mut r, 1)?;
645        let enum_count = bounded_count(&mut r, 24)?;
646        for _ in 0..enum_count {
647            let slot = bounded_slot(&mut r, enum_slots)?;
648            let generation = r.u32()?;
649            let node_id = r.node_id()?;
650            let name = r.string()?;
651            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
652            let value = read_handle_opt(&mut r)?;
653            let facets = read_facets(&mut r)?;
654            let id = ast.enumerators.insert_at(
655                slot,
656                generation,
657                crate::EnumeratorNode {
658                    node_id,
659                    name,
660                    value,
661                    parent,
662                    facets,
663                },
664            )?;
665            let _ = ast
666                .by_node_id
667                .insert(node_id, AnyId::Enumerator(id, node_id));
668        }
669
670        let bit_slots = bounded_count(&mut r, 1)?;
671        let bit_count = bounded_count(&mut r, 24)?;
672        for _ in 0..bit_count {
673            let slot = bounded_slot(&mut r, bit_slots)?;
674            let generation = r.u32()?;
675            let node_id = r.node_id()?;
676            let name = r.string()?;
677            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
678            let position = if r.bool()? { Some(r.u16()?) } else { None };
679            let facets = read_facets(&mut r)?;
680            let id = ast.bit_values.insert_at(
681                slot,
682                generation,
683                crate::BitValueNode {
684                    node_id,
685                    name,
686                    position,
687                    parent,
688                    facets,
689                },
690            )?;
691            let _ = ast.by_node_id.insert(node_id, AnyId::BitValue(id, node_id));
692        }
693
694        let field_slots = bounded_count(&mut r, 1)?;
695        let field_count = bounded_count(&mut r, 24)?;
696        for _ in 0..field_count {
697            let slot = bounded_slot(&mut r, field_slots)?;
698            let generation = r.u32()?;
699            let node_id = r.node_id()?;
700            let name = if r.bool()? { Some(r.string()?) } else { None };
701            let bits = r.u16()?;
702            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
703            let holder_type = read_ref_opt(&mut r)?;
704            let facets = read_facets(&mut r)?;
705            let id = ast.bitfields.insert_at(
706                slot,
707                generation,
708                crate::BitfieldNode {
709                    node_id,
710                    name,
711                    bits,
712                    holder_type,
713                    parent,
714                    facets,
715                },
716            )?;
717            let _ = ast.by_node_id.insert(node_id, AnyId::Bitfield(id, node_id));
718        }
719
720        // Constants.
721        let const_slots = bounded_count(&mut r, 1)?;
722        let const_count = bounded_count(&mut r, 24)?;
723        for _ in 0..const_count {
724            let slot = bounded_slot(&mut r, const_slots)?;
725            let generation = r.u32()?;
726            let node_id = r.node_id()?;
727            let name = r.string()?;
728            let module = Id::from_parts(r.u32()?, r.u32()?)?;
729            let scope = read_str_opt(&mut r)?;
730            let expr = Id::from_parts(r.u32()?, r.u32()?)?;
731            let type_ = read_type_ref(&mut r)?;
732            let facets = read_facets(&mut r)?;
733            let id = ast.constants.insert_at(
734                slot,
735                generation,
736                crate::ConstNode {
737                    node_id,
738                    name,
739                    module,
740                    scope,
741                    type_,
742                    expr,
743                    facets,
744                },
745            )?;
746            let any = AnyId::Const(id, node_id);
747            let _ = ast.by_node_id.insert(node_id, any);
748            if let Ok(m) = ast.modules.get_mut(module) {
749                m.children.push(any);
750            }
751        }
752
753        // Exceptions.
754        let exc_slots = bounded_count(&mut r, 1)?;
755        let exc_count = bounded_count(&mut r, 24)?;
756        for _ in 0..exc_count {
757            let slot = bounded_slot(&mut r, exc_slots)?;
758            let generation = r.u32()?;
759            let node_id = r.node_id()?;
760            let name = r.string()?;
761            let module = Id::from_parts(r.u32()?, r.u32()?)?;
762            let scope = read_str_opt(&mut r)?;
763            let members = read_handles(&mut r)?;
764            let facets = read_facets(&mut r)?;
765            let id = ast.exceptions.insert_at(
766                slot,
767                generation,
768                crate::ExceptionNode {
769                    node_id,
770                    name,
771                    module,
772                    scope,
773                    members,
774                    facets,
775                },
776            )?;
777            let any = AnyId::Exception(id, node_id);
778            let _ = ast.by_node_id.insert(node_id, any);
779            if let Ok(m) = ast.modules.get_mut(module) {
780                m.children.push(any);
781            }
782        }
783
784        // Interfaces, operations, parameters, attributes.
785        let iface_slots = bounded_count(&mut r, 1)?;
786        let iface_count = bounded_count(&mut r, 24)?;
787        for _ in 0..iface_count {
788            let slot = bounded_slot(&mut r, iface_slots)?;
789            let generation = r.u32()?;
790            let node_id = r.node_id()?;
791            let name = r.string()?;
792            let module = Id::from_parts(r.u32()?, r.u32()?)?;
793            let kind = interface_kind_from_tag(r.u8()?, &r)?;
794            let completeness = if r.bool()? {
795                Completeness::Complete
796            } else {
797                Completeness::Forward
798            };
799            let base_count = bounded_count(&mut r, 1)? as usize;
800            let mut bases = Vec::new();
801            for _ in 0..base_count {
802                bases.push(read_ref(&mut r)?);
803            }
804            let operations = read_handles(&mut r)?;
805            let attributes = read_handles(&mut r)?;
806            let facets = read_facets(&mut r)?;
807            let id = ast.interfaces.insert_at(
808                slot,
809                generation,
810                crate::InterfaceNode {
811                    node_id,
812                    name,
813                    module,
814                    kind,
815                    bases,
816                    operations,
817                    attributes,
818                    completeness,
819                    facets,
820                },
821            )?;
822            let any = AnyId::Interface(id, node_id);
823            let _ = ast.by_node_id.insert(node_id, any);
824            if let Ok(m) = ast.modules.get_mut(module) {
825                m.children.push(any);
826            }
827        }
828
829        let op_slots = bounded_count(&mut r, 1)?;
830        let op_count = bounded_count(&mut r, 24)?;
831        for _ in 0..op_count {
832            let slot = bounded_slot(&mut r, op_slots)?;
833            let generation = r.u32()?;
834            let node_id = r.node_id()?;
835            let name = r.string()?;
836            let return_type = read_ref_opt(&mut r)?;
837            let oneway = r.bool()?;
838            let params = read_handles(&mut r)?;
839            let raise_count = bounded_count(&mut r, 1)? as usize;
840            let mut raises = Vec::new();
841            for _ in 0..raise_count {
842                raises.push(read_ref(&mut r)?);
843            }
844            let context_count = bounded_count(&mut r, 4)? as usize;
845            let mut contexts = Vec::new();
846            for _ in 0..context_count {
847                contexts.push(r.string()?);
848            }
849            let parent = read_any_parent(&mut r, &ast)?;
850            let facets = read_facets(&mut r)?;
851            let id = ast.operations.insert_at(
852                slot,
853                generation,
854                crate::OperationNode {
855                    node_id,
856                    name,
857                    return_type,
858                    params,
859                    raises,
860                    oneway,
861                    contexts,
862                    parent,
863                    facets,
864                },
865            )?;
866            let _ = ast
867                .by_node_id
868                .insert(node_id, AnyId::Operation(id, node_id));
869        }
870
871        let param_slots = bounded_count(&mut r, 1)?;
872        let param_count = bounded_count(&mut r, 24)?;
873        for _ in 0..param_count {
874            let slot = bounded_slot(&mut r, param_slots)?;
875            let generation = r.u32()?;
876            let node_id = r.node_id()?;
877            let name = r.string()?;
878            let direction = direction_from_tag(r.u8()?, &r)?;
879            let type_ = read_type_ref(&mut r)?;
880            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
881            let facets = read_facets(&mut r)?;
882            let id = ast.params.insert_at(
883                slot,
884                generation,
885                crate::ParamNode {
886                    node_id,
887                    name,
888                    direction,
889                    type_,
890                    parent,
891                    facets,
892                },
893            )?;
894            let _ = ast.by_node_id.insert(node_id, AnyId::Param(id, node_id));
895        }
896
897        let attr_slots = bounded_count(&mut r, 1)?;
898        let attr_count = bounded_count(&mut r, 24)?;
899        for _ in 0..attr_count {
900            let slot = bounded_slot(&mut r, attr_slots)?;
901            let generation = r.u32()?;
902            let node_id = r.node_id()?;
903            let name = r.string()?;
904            let type_ = read_type_ref(&mut r)?;
905            let readonly = r.bool()?;
906            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
907            let get_count = bounded_count(&mut r, 1)? as usize;
908            let mut get_raises = Vec::new();
909            for _ in 0..get_count {
910                get_raises.push(read_ref(&mut r)?);
911            }
912            let set_count = bounded_count(&mut r, 1)? as usize;
913            let mut set_raises = Vec::new();
914            for _ in 0..set_count {
915                set_raises.push(read_ref(&mut r)?);
916            }
917            let facets = read_facets(&mut r)?;
918            let id = ast.attributes.insert_at(
919                slot,
920                generation,
921                crate::AttributeNode {
922                    node_id,
923                    name,
924                    type_,
925                    readonly,
926                    get_raises,
927                    set_raises,
928                    parent,
929                    facets,
930                },
931            )?;
932            let _ = ast
933                .by_node_id
934                .insert(node_id, AnyId::Attribute(id, node_id));
935        }
936
937        // Annotations.
938        let adef_slots = bounded_count(&mut r, 1)?;
939        let adef_count = bounded_count(&mut r, 24)?;
940        for _ in 0..adef_count {
941            let slot = bounded_slot(&mut r, adef_slots)?;
942            let generation = r.u32()?;
943            let node_id = r.node_id()?;
944            let name = r.string()?;
945            let module = Id::from_parts(r.u32()?, r.u32()?)?;
946            let members = read_handles(&mut r)?;
947            let facets = read_facets(&mut r)?;
948            let id = ast.annotation_defs.insert_at(
949                slot,
950                generation,
951                crate::AnnotationDefNode {
952                    node_id,
953                    name,
954                    module,
955                    members,
956                    facets,
957                },
958            )?;
959            let any = AnyId::AnnotationDef(id, node_id);
960            let _ = ast.by_node_id.insert(node_id, any);
961            if let Ok(m) = ast.modules.get_mut(module) {
962                m.children.push(any);
963            }
964        }
965
966        let amem_slots = bounded_count(&mut r, 1)?;
967        let amem_count = bounded_count(&mut r, 24)?;
968        for _ in 0..amem_count {
969            let slot = bounded_slot(&mut r, amem_slots)?;
970            let generation = r.u32()?;
971            let node_id = r.node_id()?;
972            let name = r.string()?;
973            let type_ = read_type_ref(&mut r)?;
974            let is_value_member = r.bool()?;
975            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
976            let default = read_handle_opt(&mut r)?;
977            let facets = read_facets(&mut r)?;
978            let id = ast.annotation_members.insert_at(
979                slot,
980                generation,
981                crate::AnnotationMemberNode {
982                    node_id,
983                    name,
984                    type_,
985                    default,
986                    is_value_member,
987                    parent,
988                    facets,
989                },
990            )?;
991            let _ = ast
992                .by_node_id
993                .insert(node_id, AnyId::AnnotationMember(id, node_id));
994        }
995
996        // Deployment.
997        let lib_slots = bounded_count(&mut r, 1)?;
998        let lib_count = bounded_count(&mut r, 24)?;
999        for _ in 0..lib_count {
1000            let slot = bounded_slot(&mut r, lib_slots)?;
1001            let generation = r.u32()?;
1002            let node_id = r.node_id()?;
1003            let name = r.string()?;
1004            let facets = read_facets(&mut r)?;
1005            let id = ast.libraries.insert_at(
1006                slot,
1007                generation,
1008                crate::LibraryNode {
1009                    node_id,
1010                    name,
1011                    children: Vec::new(),
1012                    facets,
1013                },
1014            )?;
1015            let _ = ast.by_node_id.insert(node_id, AnyId::Library(id, node_id));
1016        }
1017
1018        let dom_slots = bounded_count(&mut r, 1)?;
1019        let dom_count = bounded_count(&mut r, 24)?;
1020        for _ in 0..dom_count {
1021            let slot = bounded_slot(&mut r, dom_slots)?;
1022            let generation = r.u32()?;
1023            let node_id = r.node_id()?;
1024            let name = r.string()?;
1025            let domain_id = r.u32()?;
1026            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
1027            let facets = read_facets(&mut r)?;
1028            let id = ast.domains.insert_at(
1029                slot,
1030                generation,
1031                crate::DomainNode {
1032                    node_id,
1033                    name,
1034                    domain_id,
1035                    topics: Vec::new(),
1036                    parent,
1037                    facets,
1038                },
1039            )?;
1040            let any = AnyId::Domain(id, node_id);
1041            let _ = ast.by_node_id.insert(node_id, any);
1042            if let Ok(l) = ast.libraries.get_mut(parent) {
1043                l.children.push(any);
1044            }
1045        }
1046
1047        let qos_slots = bounded_count(&mut r, 1)?;
1048        let qos_count = bounded_count(&mut r, 24)?;
1049        for _ in 0..qos_count {
1050            let slot = bounded_slot(&mut r, qos_slots)?;
1051            let generation = r.u32()?;
1052            let node_id = r.node_id()?;
1053            let name = r.string()?;
1054            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
1055            let base = read_ref_opt(&mut r)?;
1056            let policies = read_policies(&mut r)?;
1057            let facets = read_facets(&mut r)?;
1058            let id = ast.qos_profiles.insert_at(
1059                slot,
1060                generation,
1061                crate::QosProfileNode {
1062                    node_id,
1063                    name,
1064                    base,
1065                    policies,
1066                    parent,
1067                    facets,
1068                },
1069            )?;
1070            let any = AnyId::QosProfile(id, node_id);
1071            let _ = ast.by_node_id.insert(node_id, any);
1072            if let Ok(l) = ast.libraries.get_mut(parent) {
1073                l.children.push(any);
1074            }
1075        }
1076
1077        let topic_slots = bounded_count(&mut r, 1)?;
1078        let topic_count = bounded_count(&mut r, 24)?;
1079        for _ in 0..topic_count {
1080            let slot = bounded_slot(&mut r, topic_slots)?;
1081            let generation = r.u32()?;
1082            let node_id = r.node_id()?;
1083            let name = r.string()?;
1084            let type_ = read_type_ref(&mut r)?;
1085            let qos = read_ref_opt(&mut r)?;
1086            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
1087            let facets = read_facets(&mut r)?;
1088            let id = ast.topics.insert_at(
1089                slot,
1090                generation,
1091                crate::TopicNode {
1092                    node_id,
1093                    name,
1094                    type_,
1095                    qos,
1096                    parent,
1097                    facets,
1098                },
1099            )?;
1100            let _ = ast.by_node_id.insert(node_id, AnyId::Topic(id, node_id));
1101            if let Ok(d) = ast.domains.get_mut(parent) {
1102                d.topics.push(id);
1103            }
1104        }
1105
1106        let part_slots = bounded_count(&mut r, 1)?;
1107        let part_count = bounded_count(&mut r, 24)?;
1108        for _ in 0..part_count {
1109            let slot = bounded_slot(&mut r, part_slots)?;
1110            let generation = r.u32()?;
1111            let node_id = r.node_id()?;
1112            let name = r.string()?;
1113            let domain = read_ref(&mut r)?;
1114            let qos = read_ref_opt(&mut r)?;
1115            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
1116            let facets = read_facets(&mut r)?;
1117            let id = ast.participants.insert_at(
1118                slot,
1119                generation,
1120                crate::ParticipantNode {
1121                    node_id,
1122                    name,
1123                    domain,
1124                    qos,
1125                    endpoints: Vec::new(),
1126                    parent,
1127                    facets,
1128                },
1129            )?;
1130            let any = AnyId::Participant(id, node_id);
1131            let _ = ast.by_node_id.insert(node_id, any);
1132            if let Ok(l) = ast.libraries.get_mut(parent) {
1133                l.children.push(any);
1134            }
1135        }
1136
1137        let ep_slots = bounded_count(&mut r, 1)?;
1138        let ep_count = bounded_count(&mut r, 24)?;
1139        for _ in 0..ep_count {
1140            let slot = bounded_slot(&mut r, ep_slots)?;
1141            let generation = r.u32()?;
1142            let node_id = r.node_id()?;
1143            let name = r.string()?;
1144            let role = if r.bool()? {
1145                crate::EndpointRole::DataWriter
1146            } else {
1147                crate::EndpointRole::DataReader
1148            };
1149            let topic = read_ref(&mut r)?;
1150            let qos = read_ref_opt(&mut r)?;
1151            let parent = Id::from_parts(r.u32()?, r.u32()?)?;
1152            let facets = read_facets(&mut r)?;
1153            let id = ast.endpoints.insert_at(
1154                slot,
1155                generation,
1156                crate::EndpointNode {
1157                    node_id,
1158                    name,
1159                    role,
1160                    topic,
1161                    qos,
1162                    parent,
1163                    facets,
1164                },
1165            )?;
1166            let _ = ast.by_node_id.insert(node_id, AnyId::Endpoint(id, node_id));
1167            if let Ok(p) = ast.participants.get_mut(parent) {
1168                p.endpoints.push(id);
1169            }
1170        }
1171
1172        r.expect_end()?;
1173
1174        // Members owned by an exception were loaded before the
1175        // exceptions were, so their owner identity is a placeholder
1176        // until now. Repairing it here rather than reordering the
1177        // sections keeps the format's dependency order simple.
1178        let member_ids: Vec<Id<crate::MemberNode>> = ast.members.ids().collect();
1179        for member in member_ids {
1180            let Ok(node) = ast.members.get(member) else {
1181                continue;
1182            };
1183            let AnyId::Exception(owner, _) = node.parent else {
1184                continue;
1185            };
1186            let Ok(owner_node) = ast.exceptions.get(owner) else {
1187                continue;
1188            };
1189            let owner_id = owner_node.node_id;
1190            if let Ok(node) = ast.members.get_mut(member) {
1191                node.parent = AnyId::Exception(owner, owner_id);
1192            }
1193        }
1194
1195        // Members owned by an exception were read before the exceptions
1196        // were, so their owner identity is still a placeholder. Filling
1197        // it in here keeps the format's section order simple; the
1198        // alternative is writing exceptions before members, which would
1199        // put them ahead of the types their members reference.
1200        let member_ids: Vec<Id<crate::MemberNode>> = ast.members.ids().collect();
1201        for member in member_ids {
1202            let Ok(node) = ast.members.get(member) else {
1203                continue;
1204            };
1205            let AnyId::Exception(owner, _) = node.parent else {
1206                continue;
1207            };
1208            let Ok(owner_node) = ast.exceptions.get(owner) else {
1209                continue;
1210            };
1211            let owner_id = owner_node.node_id;
1212            if let Ok(node) = ast.members.get_mut(member) {
1213                node.parent = AnyId::Exception(owner, owner_id);
1214            }
1215        }
1216
1217        // A struct's member list was written inside its kind, so the
1218        // handles are already in place; only the indices need building —
1219        // the waiter index too, or a pending reference read back would
1220        // never resolve.
1221        ast.reindex();
1222        let nodes: Vec<AnyId> = ast.by_node_id.values().copied().collect();
1223        for any in nodes {
1224            ast.index_pending(any);
1225        }
1226        Ok(ast)
1227    }
1228}
1229
1230fn count(value: usize) -> Result<u32, AstError> {
1231    u32::try_from(value).map_err(|_| AstError::Core(AstCoreError::Exhausted))
1232}
1233
1234/// Read an element count and check it against what the buffer can
1235/// actually hold.
1236///
1237/// Without this a crafted snapshot naming four billion entries makes the
1238/// loader reserve memory for them before discovering the bytes are not
1239/// there — found by the fuzz target as an out-of-memory abort rather
1240/// than the typed error the spec requires. `min_entry` is the smallest
1241/// number of bytes one entry can occupy.
1242fn bounded_count(r: &mut SnapshotReader<'_>, min_entry: usize) -> Result<u32, AstError> {
1243    let declared = r.u32()?;
1244    let needed = (declared as usize).saturating_mul(min_entry);
1245    if needed > r.remaining() {
1246        return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1247            offset: r.offset(),
1248            expected: "an element count the remaining buffer can hold",
1249        }));
1250    }
1251    Ok(declared)
1252}
1253
1254/// Read a slot index and check it against the section's declared slot
1255/// capacity.
1256///
1257/// Without this a crafted snapshot naming slot 4294967295 makes the
1258/// arena grow its slot vector to that index before failing — the second
1259/// out-of-memory path the fuzz target found. The capacity itself is
1260/// bounded against the remaining buffer by `bounded_count`.
1261fn bounded_slot(r: &mut SnapshotReader<'_>, capacity: u32) -> Result<u32, AstError> {
1262    let slot = r.u32()?;
1263    if slot >= capacity {
1264        return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1265            offset: r.offset(),
1266            expected: "a slot index within the section's declared capacity",
1267        }));
1268    }
1269    Ok(slot)
1270}
1271
1272/// Smallest byte size of one entry, per section. Slot, generation and
1273/// identity alone account for most of it.
1274mod min_size {
1275    /// slot + generation + node id + name length + parent flag
1276    pub const MODULE: usize = 4 + 4 + 16 + 4 + 1;
1277    /// slot + generation + node id + expression tag
1278    pub const EXPR: usize = 4 + 4 + 16 + 1;
1279    /// slot + generation + node id + name length + module handle +
1280    /// completeness + kind tag
1281    pub const TYPE: usize = 4 + 4 + 16 + 4 + 8 + 1 + 1;
1282    /// slot + generation + node id + name length + reference tag +
1283    /// parent flag + facet flag
1284    pub const MEMBER: usize = 4 + 4 + 16 + 4 + 1 + 1 + 1;
1285    /// slot + generation
1286    pub const HANDLE: usize = 8;
1287    /// reference tag, for a list of references
1288    pub const REFERENCE: usize = 1;
1289}
1290
1291fn write_type_ref(
1292    w: &mut SnapshotWriter,
1293    reference: &Ref<crate::TypeNode>,
1294) -> Result<(), AstError> {
1295    write_ref(w, reference)
1296}
1297
1298fn read_type_ref(r: &mut SnapshotReader<'_>) -> Result<Ref<crate::TypeNode>, AstError> {
1299    read_ref(r)
1300}
1301
1302fn write_bound(w: &mut SnapshotWriter, bound: Bound) {
1303    match bound {
1304        Bound::Unbounded => w.u8(0),
1305        Bound::Const(id) => {
1306            w.u8(1);
1307            w.u32(id.slot());
1308            w.u32(id.generation());
1309        }
1310    }
1311}
1312
1313fn read_bound(r: &mut SnapshotReader<'_>) -> Result<Bound, AstError> {
1314    Ok(match r.u8()? {
1315        0 => Bound::Unbounded,
1316        1 => Bound::Const(Id::from_parts(r.u32()?, r.u32()?)?),
1317        _ => {
1318            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1319                offset: r.offset(),
1320                expected: "bound tag 0 or 1",
1321            }));
1322        }
1323    })
1324}
1325
1326fn write_handles<T>(w: &mut SnapshotWriter, handles: &[Id<T>]) -> Result<(), AstError> {
1327    w.u32(count(handles.len())?);
1328    for handle in handles {
1329        w.u32(handle.slot());
1330        w.u32(handle.generation());
1331    }
1332    Ok(())
1333}
1334
1335fn read_handles<T>(r: &mut SnapshotReader<'_>) -> Result<Vec<Id<T>>, AstError> {
1336    let len = bounded_count(r, min_size::HANDLE)? as usize;
1337    let mut out = Vec::new();
1338    for _ in 0..len {
1339        out.push(Id::from_parts(r.u32()?, r.u32()?)?);
1340    }
1341    Ok(out)
1342}
1343
1344fn write_kind(w: &mut SnapshotWriter, kind: &TypeKind) -> Result<(), AstError> {
1345    match kind {
1346        TypeKind::Primitive(p) => {
1347            w.u8(tag::PRIMITIVE);
1348            w.u8(primitive_tag(*p));
1349        }
1350        TypeKind::Fixed { digits, scale } => {
1351            w.u8(tag::FIXED);
1352            w.u8(*digits);
1353            w.u8(*scale);
1354        }
1355        TypeKind::String { width, bound } => {
1356            w.u8(tag::STRING);
1357            w.u8(u8::from(matches!(width, CharWidth::WChar)));
1358            write_bound(w, *bound);
1359        }
1360        TypeKind::Sequence { element, bound } => {
1361            w.u8(tag::SEQUENCE);
1362            write_type_ref(w, element)?;
1363            write_bound(w, *bound);
1364        }
1365        TypeKind::Array { element, dims } => {
1366            w.u8(tag::ARRAY);
1367            write_type_ref(w, element)?;
1368            write_handles(w, dims)?;
1369        }
1370        TypeKind::Map { key, value, bound } => {
1371            w.u8(tag::MAP);
1372            write_type_ref(w, key)?;
1373            write_type_ref(w, value)?;
1374            write_bound(w, *bound);
1375        }
1376        TypeKind::Structure { base, members } => {
1377            w.u8(tag::STRUCTURE);
1378            match base {
1379                Some(base) => {
1380                    w.bool(true);
1381                    write_type_ref(w, base)?;
1382                }
1383                None => w.bool(false),
1384            }
1385            write_handles(w, members)?;
1386        }
1387        TypeKind::Union {
1388            discriminator,
1389            cases,
1390            default_case,
1391        } => {
1392            w.u8(tag::UNION);
1393            write_type_ref(w, discriminator)?;
1394            write_handles(w, cases)?;
1395            // The `default:` case was dropped on the way out and read
1396            // back as `None`, so a union with a default arm loaded as one
1397            // without: the arm was still in `cases`, but nothing said
1398            // which one it was.
1399            write_handle_opt(w, *default_case);
1400        }
1401        TypeKind::Enumeration { enumerators } => {
1402            w.u8(tag::ENUMERATION);
1403            write_handles(w, enumerators)?;
1404        }
1405        TypeKind::Bitmask { bits } => {
1406            w.u8(tag::BITMASK);
1407            write_handles(w, bits)?;
1408        }
1409        TypeKind::Bitset { base, fields } => {
1410            w.u8(tag::BITSET);
1411            write_handles(w, fields)?;
1412            // A bitset's inherited base was dropped the same way.
1413            write_type_ref_opt(w, base.as_ref())?;
1414        }
1415        TypeKind::Alias { aliased } => {
1416            w.u8(tag::ALIAS);
1417            write_type_ref(w, aliased)?;
1418        }
1419        TypeKind::Valuetype {
1420            kind,
1421            bases,
1422            state,
1423            initializers,
1424            supported,
1425        } => {
1426            w.u8(tag::VALUETYPE);
1427            w.u8(valuetype_tag(*kind));
1428            w.u32(count(bases.len())?);
1429            for base in bases {
1430                write_type_ref(w, base)?;
1431            }
1432            write_handles(w, state)?;
1433            write_handles(w, initializers)?;
1434            w.u32(count(supported.len())?);
1435            for interface in supported {
1436                write_ref(w, interface)?;
1437            }
1438        }
1439        TypeKind::ValueBox { boxed } => {
1440            w.u8(tag::VALUE_BOX);
1441            write_type_ref(w, boxed)?;
1442        }
1443        TypeKind::ObjectRef { interface } => {
1444            w.u8(tag::OBJECT_REF);
1445            match interface {
1446                Some(interface) => {
1447                    w.bool(true);
1448                    write_ref(w, interface)?;
1449                }
1450                None => w.bool(false),
1451            }
1452        }
1453        TypeKind::Any => w.u8(tag::ANY),
1454        TypeKind::ValueBase => w.u8(tag::VALUE_BASE),
1455        TypeKind::AnnotationType { definition } => {
1456            w.u8(tag::ANNOTATION_TYPE);
1457            write_handle_opt(w, Some(*definition));
1458        }
1459        TypeKind::Native => w.u8(tag::NATIVE),
1460    }
1461    Ok(())
1462}
1463
1464fn valuetype_tag(kind: crate::ValuetypeKind) -> u8 {
1465    match kind {
1466        crate::ValuetypeKind::Regular => 0,
1467        crate::ValuetypeKind::Abstract => 1,
1468        crate::ValuetypeKind::Custom => 2,
1469        crate::ValuetypeKind::Truncatable => 3,
1470    }
1471}
1472
1473fn read_kind(r: &mut SnapshotReader<'_>) -> Result<TypeKind, AstError> {
1474    Ok(match r.u8()? {
1475        tag::PRIMITIVE => TypeKind::Primitive(primitive_from_tag(r.u8()?, r)?),
1476        tag::FIXED => TypeKind::Fixed {
1477            digits: r.u8()?,
1478            scale: r.u8()?,
1479        },
1480        tag::STRING => TypeKind::String {
1481            width: if r.u8()? == 0 {
1482                CharWidth::Char
1483            } else {
1484                CharWidth::WChar
1485            },
1486            bound: read_bound(r)?,
1487        },
1488        tag::SEQUENCE => TypeKind::Sequence {
1489            element: read_type_ref(r)?,
1490            bound: read_bound(r)?,
1491        },
1492        tag::ARRAY => TypeKind::Array {
1493            element: read_type_ref(r)?,
1494            dims: read_handles(r)?,
1495        },
1496        tag::MAP => TypeKind::Map {
1497            key: read_type_ref(r)?,
1498            value: read_type_ref(r)?,
1499            bound: read_bound(r)?,
1500        },
1501        tag::STRUCTURE => TypeKind::Structure {
1502            base: if r.bool()? {
1503                Some(read_type_ref(r)?)
1504            } else {
1505                None
1506            },
1507            members: read_handles(r)?,
1508        },
1509        tag::UNION => TypeKind::Union {
1510            discriminator: read_type_ref(r)?,
1511            cases: read_handles(r)?,
1512            default_case: read_handle_opt(r)?,
1513        },
1514        tag::ENUMERATION => TypeKind::Enumeration {
1515            enumerators: read_handles(r)?,
1516        },
1517        tag::BITMASK => TypeKind::Bitmask {
1518            bits: read_handles(r)?,
1519        },
1520        tag::BITSET => {
1521            let fields = read_handles(r)?;
1522            TypeKind::Bitset {
1523                fields,
1524                base: read_ref_opt(r)?,
1525            }
1526        }
1527        tag::ALIAS => TypeKind::Alias {
1528            aliased: read_type_ref(r)?,
1529        },
1530        tag::NATIVE => TypeKind::Native,
1531        tag::VALUETYPE => {
1532            let tag = r.u8()?;
1533            let kind =
1534                crate::ValuetypeKind::ALL
1535                    .get(tag as usize)
1536                    .copied()
1537                    .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
1538                        offset: r.offset(),
1539                        expected: "a known valuetype modifier tag",
1540                    }))?;
1541            let base_count = bounded_count(r, min_size::REFERENCE)? as usize;
1542            let mut bases = Vec::new();
1543            for _ in 0..base_count {
1544                bases.push(read_type_ref(r)?);
1545            }
1546            let state = read_handles(r)?;
1547            let initializers = read_handles(r)?;
1548            let supported_count = bounded_count(r, min_size::REFERENCE)? as usize;
1549            let mut supported = Vec::new();
1550            for _ in 0..supported_count {
1551                supported.push(read_ref(r)?);
1552            }
1553            TypeKind::Valuetype {
1554                kind,
1555                bases,
1556                state,
1557                initializers,
1558                supported,
1559            }
1560        }
1561        tag::VALUE_BOX => TypeKind::ValueBox {
1562            boxed: read_type_ref(r)?,
1563        },
1564        tag::OBJECT_REF => TypeKind::ObjectRef {
1565            interface: if r.bool()? { Some(read_ref(r)?) } else { None },
1566        },
1567        tag::ANY => TypeKind::Any,
1568        tag::VALUE_BASE => TypeKind::ValueBase,
1569        tag::ANNOTATION_TYPE => TypeKind::AnnotationType {
1570            definition: read_handle_opt(r)?.ok_or(AstError::Core(
1571                AstCoreError::MalformedSnapshot {
1572                    offset: r.offset(),
1573                    expected: "an annotation type's declaration handle",
1574                },
1575            ))?,
1576        },
1577        _ => {
1578            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1579                offset: r.offset(),
1580                expected: "a known type kind tag",
1581            }));
1582        }
1583    })
1584}
1585
1586fn primitive_tag(kind: PrimitiveKind) -> u8 {
1587    match kind {
1588        PrimitiveKind::Boolean => 0,
1589        PrimitiveKind::Octet => 1,
1590        PrimitiveKind::Char => 2,
1591        PrimitiveKind::WChar => 3,
1592        PrimitiveKind::Int8 => 4,
1593        PrimitiveKind::UInt8 => 5,
1594        PrimitiveKind::Int16 => 6,
1595        PrimitiveKind::UInt16 => 7,
1596        PrimitiveKind::Int32 => 8,
1597        PrimitiveKind::UInt32 => 9,
1598        PrimitiveKind::Int64 => 10,
1599        PrimitiveKind::UInt64 => 11,
1600        PrimitiveKind::Float32 => 12,
1601        PrimitiveKind::Float64 => 13,
1602        PrimitiveKind::Float128 => 14,
1603    }
1604}
1605
1606fn primitive_from_tag(tag: u8, r: &SnapshotReader<'_>) -> Result<PrimitiveKind, AstError> {
1607    PrimitiveKind::ALL
1608        .get(tag as usize)
1609        .copied()
1610        .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
1611            offset: r.offset(),
1612            expected: "a known primitive tag",
1613        }))
1614}
1615
1616fn write_expr(w: &mut SnapshotWriter, expr: &crate::ConstExpr) -> Result<(), AstError> {
1617    match expr {
1618        crate::ConstExpr::Literal(crate::Literal::Integer(value)) => {
1619            w.u8(0);
1620            w.i128(*value);
1621        }
1622        crate::ConstExpr::Literal(crate::Literal::Boolean(value)) => {
1623            w.u8(1);
1624            w.bool(*value);
1625        }
1626        crate::ConstExpr::Binary { op, lhs, rhs } => {
1627            w.u8(2);
1628            w.u8(binary_tag(*op));
1629            w.u32(lhs.slot());
1630            w.u32(lhs.generation());
1631            w.u32(rhs.slot());
1632            w.u32(rhs.generation());
1633        }
1634        crate::ConstExpr::Unary { op, operand } => {
1635            w.u8(3);
1636            w.u8(unary_tag(*op));
1637            w.u32(operand.slot());
1638            w.u32(operand.generation());
1639        }
1640        crate::ConstExpr::Literal(crate::Literal::Float { mantissa, exponent }) => {
1641            w.u8(4);
1642            w.str(mantissa)?;
1643            write_str_opt(w, exponent.as_deref())?;
1644        }
1645        crate::ConstExpr::Literal(crate::Literal::Fixed { digits, scale }) => {
1646            w.u8(5);
1647            w.str(digits)?;
1648            w.u8(*scale);
1649        }
1650        crate::ConstExpr::Literal(crate::Literal::Char(value)) => {
1651            w.u8(6);
1652            w.u32(*value as u32);
1653        }
1654        crate::ConstExpr::Literal(crate::Literal::WChar(value)) => {
1655            w.u8(7);
1656            w.u32(*value);
1657        }
1658        crate::ConstExpr::Literal(crate::Literal::String(value)) => {
1659            w.u8(8);
1660            w.str(value)?;
1661        }
1662        crate::ConstExpr::Literal(crate::Literal::WString(value)) => {
1663            w.u8(9);
1664            w.u32(count(value.len())?);
1665            for point in value {
1666                w.u32(*point);
1667            }
1668        }
1669        crate::ConstExpr::Literal(crate::Literal::Enumerator(reference)) => {
1670            w.u8(10);
1671            write_ref(w, reference)?;
1672        }
1673        crate::ConstExpr::NameRef(reference) => {
1674            w.u8(11);
1675            write_ref(w, reference)?;
1676        }
1677    }
1678    Ok(())
1679}
1680
1681fn read_expr(r: &mut SnapshotReader<'_>) -> Result<crate::ConstExpr, AstError> {
1682    Ok(match r.u8()? {
1683        0 => crate::ConstExpr::Literal(crate::Literal::Integer(r.i128()?)),
1684        1 => crate::ConstExpr::Literal(crate::Literal::Boolean(r.bool()?)),
1685        2 => {
1686            let op = binary_from_tag(r.u8()?, r)?;
1687            let lhs = Id::from_parts(r.u32()?, r.u32()?)?;
1688            let rhs = Id::from_parts(r.u32()?, r.u32()?)?;
1689            crate::ConstExpr::Binary { op, lhs, rhs }
1690        }
1691        3 => {
1692            let op = unary_from_tag(r.u8()?, r)?;
1693            let operand = Id::from_parts(r.u32()?, r.u32()?)?;
1694            crate::ConstExpr::Unary { op, operand }
1695        }
1696        4 => crate::ConstExpr::Literal(crate::Literal::Float {
1697            mantissa: r.string()?,
1698            exponent: read_str_opt(r)?,
1699        }),
1700        5 => crate::ConstExpr::Literal(crate::Literal::Fixed {
1701            digits: r.string()?,
1702            scale: r.u8()?,
1703        }),
1704        6 => {
1705            let value = r.u32()?;
1706            let ch =
1707                char::from_u32(value).ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
1708                    offset: r.offset(),
1709                    expected: "a character literal's code point",
1710                }))?;
1711            crate::ConstExpr::Literal(crate::Literal::Char(ch))
1712        }
1713        7 => crate::ConstExpr::Literal(crate::Literal::WChar(r.u32()?)),
1714        8 => crate::ConstExpr::Literal(crate::Literal::String(r.string()?)),
1715        9 => {
1716            let n = bounded_count(r, 4)? as usize;
1717            let mut points = Vec::new();
1718            for _ in 0..n {
1719                points.push(r.u32()?);
1720            }
1721            crate::ConstExpr::Literal(crate::Literal::WString(points))
1722        }
1723        10 => crate::ConstExpr::Literal(crate::Literal::Enumerator(read_ref(r)?)),
1724        11 => crate::ConstExpr::NameRef(read_ref(r)?),
1725        _ => {
1726            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1727                offset: r.offset(),
1728                expected: "a known expression tag",
1729            }));
1730        }
1731    })
1732}
1733
1734fn binary_tag(op: crate::node::constant::BinaryOp) -> u8 {
1735    use crate::node::constant::BinaryOp as B;
1736    match op {
1737        B::Or => 0,
1738        B::Xor => 1,
1739        B::And => 2,
1740        B::Shl => 3,
1741        B::Shr => 4,
1742        B::Add => 5,
1743        B::Sub => 6,
1744        B::Mul => 7,
1745        B::Div => 8,
1746        B::Mod => 9,
1747    }
1748}
1749
1750fn binary_from_tag(
1751    tag: u8,
1752    r: &SnapshotReader<'_>,
1753) -> Result<crate::node::constant::BinaryOp, AstError> {
1754    crate::node::constant::BinaryOp::ALL
1755        .get(tag as usize)
1756        .copied()
1757        .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
1758            offset: r.offset(),
1759            expected: "a known binary operator tag",
1760        }))
1761}
1762
1763fn unary_tag(op: crate::UnaryOp) -> u8 {
1764    match op {
1765        crate::UnaryOp::Plus => 0,
1766        crate::UnaryOp::Minus => 1,
1767        crate::UnaryOp::Not => 2,
1768    }
1769}
1770
1771fn unary_from_tag(tag: u8, r: &SnapshotReader<'_>) -> Result<crate::UnaryOp, AstError> {
1772    crate::UnaryOp::ALL
1773        .get(tag as usize)
1774        .copied()
1775        .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
1776            offset: r.offset(),
1777            expected: "a known unary operator tag",
1778        }))
1779}
1780
1781// -- generic reference and facet helpers ---------------------------------
1782
1783/// A reference: tag 1 and the handle when resolved, tag 2 and the lookup
1784/// key when pending, tag 3 and the cause when broken.
1785///
1786/// A pending reference keeps its key so that a model saved half resolved
1787/// loads half resolved — still waiting on the same name, and resolved by
1788/// the declaration that arrives later — rather than as a broken reference
1789/// with an empty name. The key is the frontend's, and it is written as the
1790/// frontend left it.
1791fn write_ref<T>(w: &mut SnapshotWriter, reference: &Ref<T>) -> Result<(), AstError> {
1792    match reference {
1793        Ref::Resolved(id) => {
1794            w.u8(1);
1795            w.u32(id.slot());
1796            w.u32(id.generation());
1797        }
1798        Ref::Pending(key) => {
1799            w.u8(2);
1800            write_pending_key(w, key)?;
1801        }
1802        Ref::Broken(cause) => {
1803            w.u8(3);
1804            write_break_cause(w, cause)?;
1805        }
1806    }
1807    Ok(())
1808}
1809
1810fn write_pending_key(
1811    w: &mut SnapshotWriter,
1812    key: &zerodds_ast_core::PendingRef,
1813) -> Result<(), AstError> {
1814    use zerodds_ast_core::{PendingRef, RosInterfaceKind};
1815    match key {
1816        PendingRef::ScopedName(name) => {
1817            w.u8(0);
1818            w.bool(name.is_absolute());
1819            write_strings(w, name.segments())?;
1820        }
1821        PendingRef::TypeIdentifier(identifier) => {
1822            w.u8(1);
1823            for octet in identifier.0 {
1824                w.u8(octet);
1825            }
1826        }
1827        PendingRef::RosTypeName(name) => {
1828            w.u8(2);
1829            w.str(&name.package)?;
1830            w.u8(match name.kind {
1831                RosInterfaceKind::Msg => 0,
1832                RosInterfaceKind::Srv => 1,
1833                RosInterfaceKind::Action => 2,
1834            });
1835            w.str(&name.name)?;
1836        }
1837        PendingRef::RepositoryId(id) => {
1838            w.u8(3);
1839            w.str(&id.0)?;
1840        }
1841        PendingRef::XmlRef(key) => {
1842            w.u8(4);
1843            w.str(&key.0)?;
1844        }
1845    }
1846    Ok(())
1847}
1848
1849fn write_break_cause(
1850    w: &mut SnapshotWriter,
1851    cause: &zerodds_ast_core::BreakCause,
1852) -> Result<(), AstError> {
1853    use zerodds_ast_core::BreakCause;
1854    match cause {
1855        BreakCause::NotFound { what } => {
1856            w.u8(0);
1857            write_pending_key(w, what)?;
1858        }
1859        BreakCause::Ambiguous { what, candidates } => {
1860            w.u8(1);
1861            write_pending_key(w, what)?;
1862            w.u32(count(candidates.len())?);
1863            for candidate in candidates {
1864                w.node_id(*candidate);
1865            }
1866        }
1867        BreakCause::KindMismatch { expected, found } => {
1868            w.u8(2);
1869            w.str(expected)?;
1870            w.str(found)?;
1871        }
1872        BreakCause::IllegalCycle { through } => {
1873            w.u8(3);
1874            w.node_id(*through);
1875        }
1876    }
1877    Ok(())
1878}
1879
1880fn write_ref_opt<T>(w: &mut SnapshotWriter, reference: Option<&Ref<T>>) -> Result<(), AstError> {
1881    match reference {
1882        Some(reference) => {
1883            w.bool(true);
1884            write_ref(w, reference)?;
1885        }
1886        None => w.bool(false),
1887    }
1888    Ok(())
1889}
1890
1891fn write_type_ref_opt(
1892    w: &mut SnapshotWriter,
1893    reference: Option<&Ref<crate::TypeNode>>,
1894) -> Result<(), AstError> {
1895    write_ref_opt(w, reference)
1896}
1897
1898fn read_ref<T>(r: &mut SnapshotReader<'_>) -> Result<Ref<T>, AstError> {
1899    Ok(match r.u8()? {
1900        1 => Ref::Resolved(Id::from_parts(r.u32()?, r.u32()?)?),
1901        2 => Ref::Pending(read_pending_key(r)?),
1902        3 => Ref::Broken(read_break_cause(r)?),
1903        _ => {
1904            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1905                offset: r.offset(),
1906                expected: "reference tag 1, 2 or 3",
1907            }));
1908        }
1909    })
1910}
1911
1912fn read_pending_key(r: &mut SnapshotReader<'_>) -> Result<zerodds_ast_core::PendingRef, AstError> {
1913    use zerodds_ast_core::{
1914        PendingRef, RepositoryId, RosInterfaceKind, RosTypeName, ScopedName, TypeIdentifierKey,
1915        XmlRefKey,
1916    };
1917    Ok(match r.u8()? {
1918        0 => {
1919            let absolute = r.bool()?;
1920            let segments = read_strings(r)?;
1921            PendingRef::ScopedName(if absolute {
1922                ScopedName::absolute(segments)
1923            } else {
1924                ScopedName::relative(segments)
1925            })
1926        }
1927        1 => {
1928            let mut identifier = [0u8; 16];
1929            for octet in &mut identifier {
1930                *octet = r.u8()?;
1931            }
1932            PendingRef::TypeIdentifier(TypeIdentifierKey(identifier))
1933        }
1934        2 => {
1935            let package = r.string()?;
1936            let kind = match r.u8()? {
1937                0 => RosInterfaceKind::Msg,
1938                1 => RosInterfaceKind::Srv,
1939                2 => RosInterfaceKind::Action,
1940                _ => {
1941                    return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1942                        offset: r.offset(),
1943                        expected: "a ROS interface kind tag 0, 1 or 2",
1944                    }));
1945                }
1946            };
1947            let name = r.string()?;
1948            PendingRef::RosTypeName(RosTypeName {
1949                package,
1950                kind,
1951                name,
1952            })
1953        }
1954        3 => PendingRef::RepositoryId(RepositoryId(r.string()?)),
1955        4 => PendingRef::XmlRef(XmlRefKey(r.string()?)),
1956        _ => {
1957            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
1958                offset: r.offset(),
1959                expected: "a lookup key tag 0 to 4",
1960            }));
1961        }
1962    })
1963}
1964
1965/// The names `BreakCause::KindMismatch` carries, as the resolver writes
1966/// them: every node kind's `as_str`, and the three phrases of
1967/// `crate::resolve`. The cause holds `&'static str`, so a name read back
1968/// has to be one of these; anything else is refused rather than leaked.
1969fn kind_mismatch_name(text: &str) -> Option<&'static str> {
1970    const PHRASES: [&str; 3] = [
1971        "a different kind of declaration",
1972        "a declaration the reference accepts",
1973        "nothing",
1974    ];
1975    PHRASES
1976        .iter()
1977        .copied()
1978        .chain(crate::NodeKind::ALL.iter().map(|kind| kind.as_str()))
1979        .find(|known| *known == text)
1980}
1981
1982fn read_break_cause(r: &mut SnapshotReader<'_>) -> Result<zerodds_ast_core::BreakCause, AstError> {
1983    use zerodds_ast_core::BreakCause;
1984    Ok(match r.u8()? {
1985        0 => BreakCause::NotFound {
1986            what: read_pending_key(r)?,
1987        },
1988        1 => {
1989            let what = read_pending_key(r)?;
1990            let n = bounded_count(r, 16)? as usize;
1991            let mut candidates = Vec::new();
1992            for _ in 0..n {
1993                candidates.push(r.node_id()?);
1994            }
1995            BreakCause::Ambiguous { what, candidates }
1996        }
1997        2 => {
1998            let name = |r: &mut SnapshotReader<'_>| -> Result<&'static str, AstError> {
1999                let text = r.string()?;
2000                kind_mismatch_name(&text).ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2001                    offset: r.offset(),
2002                    expected: "a kind name the resolver writes",
2003                }))
2004            };
2005            let expected = name(r)?;
2006            let found = name(r)?;
2007            BreakCause::KindMismatch { expected, found }
2008        }
2009        3 => BreakCause::IllegalCycle {
2010            through: r.node_id()?,
2011        },
2012        _ => {
2013            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2014                offset: r.offset(),
2015                expected: "a break cause tag 0 to 3",
2016            }));
2017        }
2018    })
2019}
2020
2021fn read_ref_opt<T>(r: &mut SnapshotReader<'_>) -> Result<Option<Ref<T>>, AstError> {
2022    Ok(if r.bool()? { Some(read_ref(r)?) } else { None })
2023}
2024
2025fn write_handle_opt<T>(w: &mut SnapshotWriter, handle: Option<Id<T>>) {
2026    match handle {
2027        Some(id) => {
2028            w.bool(true);
2029            w.u32(id.slot());
2030            w.u32(id.generation());
2031        }
2032        None => w.bool(false),
2033    }
2034}
2035
2036fn read_handle_opt<T>(r: &mut SnapshotReader<'_>) -> Result<Option<Id<T>>, AstError> {
2037    Ok(if r.bool()? {
2038        Some(Id::from_parts(r.u32()?, r.u32()?)?)
2039    } else {
2040        None
2041    })
2042}
2043
2044fn write_any_parent(w: &mut SnapshotWriter, parent: AnyId) {
2045    // Only the kinds that can own an operation are recorded; anything
2046    // else would be a model the edit surface cannot build.
2047    match parent {
2048        AnyId::Interface(id, _) => {
2049            w.u8(1);
2050            w.u32(id.slot());
2051            w.u32(id.generation());
2052        }
2053        AnyId::Type(id, _) => {
2054            w.u8(2);
2055            w.u32(id.slot());
2056            w.u32(id.generation());
2057        }
2058        _ => w.u8(0),
2059    }
2060}
2061
2062fn read_any_parent(r: &mut SnapshotReader<'_>, ast: &Ast) -> Result<AnyId, AstError> {
2063    match r.u8()? {
2064        1 => {
2065            let id: Id<crate::InterfaceNode> = Id::from_parts(r.u32()?, r.u32()?)?;
2066            let node_id = ast.interfaces.get(id)?.node_id;
2067            Ok(AnyId::Interface(id, node_id))
2068        }
2069        2 => {
2070            let id: Id<crate::TypeNode> = Id::from_parts(r.u32()?, r.u32()?)?;
2071            let node_id = ast.types.get(id)?.node_id;
2072            Ok(AnyId::Type(id, node_id))
2073        }
2074        _ => Err(AstError::Core(AstCoreError::MalformedSnapshot {
2075            offset: r.offset(),
2076            expected: "an operation's owning interface or valuetype",
2077        })),
2078    }
2079}
2080
2081fn interface_kind_tag(kind: crate::InterfaceKind) -> u8 {
2082    use crate::InterfaceKind as K;
2083    match kind {
2084        K::Interface => 0,
2085        K::Local => 1,
2086        K::Abstract => 2,
2087        K::Component => 3,
2088        K::Home => 4,
2089        K::Connector => 5,
2090        K::PortType => 6,
2091    }
2092}
2093
2094fn interface_kind_from_tag(
2095    tag: u8,
2096    r: &SnapshotReader<'_>,
2097) -> Result<crate::InterfaceKind, AstError> {
2098    crate::InterfaceKind::ALL
2099        .get(tag as usize)
2100        .copied()
2101        .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2102            offset: r.offset(),
2103            expected: "a known interface kind tag",
2104        }))
2105}
2106
2107fn direction_tag(direction: crate::ParamDirection) -> u8 {
2108    use crate::ParamDirection as D;
2109    match direction {
2110        D::In => 0,
2111        D::Out => 1,
2112        D::InOut => 2,
2113    }
2114}
2115
2116fn direction_from_tag(tag: u8, r: &SnapshotReader<'_>) -> Result<crate::ParamDirection, AstError> {
2117    crate::ParamDirection::ALL
2118        .get(tag as usize)
2119        .copied()
2120        .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2121            offset: r.offset(),
2122            expected: "a known parameter direction tag",
2123        }))
2124}
2125
2126/// QoS policies, as an opaque count-prefixed list of kind tags.
2127///
2128/// The policy payloads are values a frontend re-derives from the source
2129/// it parsed; the snapshot records which policies a profile declared so
2130/// the shape survives, and a loader that needs the values re-reads the
2131/// DDS-XML. Recorded explicitly rather than dropped silently, so a
2132/// consumer can tell the difference.
2133fn write_policies(w: &mut SnapshotWriter, policies: &[crate::QosPolicy]) -> Result<(), AstError> {
2134    w.u32(count(policies.len())?);
2135    for policy in policies {
2136        w.u8(policy_tag(policy.kind())?);
2137    }
2138    Ok(())
2139}
2140
2141/// The snapshot tag of a QoS policy kind.
2142///
2143/// # Errors
2144/// [`AstCoreError::Exhausted`] when the kind is absent from `ALL`, which
2145/// means a variant was added without extending the table. Writing a
2146/// stand-in tag would put a different policy in the snapshot and only
2147/// surface when someone loaded it, on another machine, later.
2148fn policy_tag(kind: crate::QosPolicyKind) -> Result<u8, AstError> {
2149    tag_of(crate::QosPolicyKind::ALL.iter().position(|c| *c == kind))
2150}
2151
2152/// A variant's index in its `ALL` table, as the tag it is written as.
2153fn tag_of(position: Option<usize>) -> Result<u8, AstError> {
2154    position
2155        .and_then(|index| u8::try_from(index).ok())
2156        .ok_or(AstError::Core(AstCoreError::Exhausted))
2157}
2158
2159fn read_policies(r: &mut SnapshotReader<'_>) -> Result<Vec<crate::QosPolicy>, AstError> {
2160    let len = bounded_count(r, 1)? as usize;
2161    let mut out = Vec::new();
2162    for _ in 0..len {
2163        let tag = r.u8()?;
2164        let kind = crate::QosPolicyKind::ALL
2165            .get(tag as usize)
2166            .copied()
2167            .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2168                offset: r.offset(),
2169                expected: "a known QoS policy tag",
2170            }))?;
2171        out.push(empty_policy(kind));
2172    }
2173    Ok(out)
2174}
2175
2176/// A policy of the given kind with every field unset.
2177///
2178/// Unset rather than defaulted: the snapshot did not carry the values,
2179/// and a defaulted field would claim the profile wrote something it did
2180/// not. Spec §3.7.
2181fn empty_policy(kind: crate::QosPolicyKind) -> crate::QosPolicy {
2182    use crate::QosPolicy as P;
2183    use crate::QosPolicyKind as K;
2184    match kind {
2185        K::UserData => P::UserData { value: Vec::new() },
2186        K::TopicData => P::TopicData { value: Vec::new() },
2187        K::GroupData => P::GroupData { value: Vec::new() },
2188        K::Durability => P::Durability { kind: None },
2189        K::DurabilityService => P::DurabilityService {
2190            service_cleanup_delay: None,
2191            history_kind: None,
2192            history_depth: None,
2193            max_samples: None,
2194            max_instances: None,
2195            max_samples_per_instance: None,
2196        },
2197        K::Presentation => P::Presentation {
2198            access_scope: None,
2199            coherent_access: None,
2200            ordered_access: None,
2201        },
2202        K::Deadline => P::Deadline { period: None },
2203        K::LatencyBudget => P::LatencyBudget { duration: None },
2204        K::Ownership => P::Ownership { kind: None },
2205        K::OwnershipStrength => P::OwnershipStrength { value: None },
2206        K::Liveliness => P::Liveliness {
2207            kind: None,
2208            lease_duration: None,
2209        },
2210        K::TimeBasedFilter => P::TimeBasedFilter {
2211            minimum_separation: None,
2212        },
2213        K::Partition => P::Partition { name: Vec::new() },
2214        K::Reliability => P::Reliability {
2215            kind: None,
2216            max_blocking_time: None,
2217        },
2218        K::TransportPriority => P::TransportPriority { value: None },
2219        K::Lifespan => P::Lifespan { duration: None },
2220        K::DestinationOrder => P::DestinationOrder { kind: None },
2221        K::History => P::History {
2222            kind: None,
2223            depth: None,
2224        },
2225        K::ResourceLimits => P::ResourceLimits {
2226            max_samples: None,
2227            max_instances: None,
2228            max_samples_per_instance: None,
2229        },
2230        K::EntityFactory => P::EntityFactory {
2231            autoenable_created_entities: None,
2232        },
2233        K::WriterDataLifecycle => P::WriterDataLifecycle {
2234            autodispose_unregistered_instances: None,
2235        },
2236        K::ReaderDataLifecycle => P::ReaderDataLifecycle {
2237            autopurge_nowriter_samples_delay: None,
2238            autopurge_disposed_samples_delay: None,
2239        },
2240        K::DataRepresentation => P::DataRepresentation { value: Vec::new() },
2241        K::TypeConsistencyEnforcement => P::TypeConsistencyEnforcement {
2242            kind: None,
2243            ignore_sequence_bounds: None,
2244            ignore_string_bounds: None,
2245            ignore_member_names: None,
2246            prevent_type_widening: None,
2247            force_type_validation: None,
2248        },
2249    }
2250}
2251
2252/// The full facet payload.
2253///
2254/// Every facet round-trips: the XTypes annotations layout reads, the
2255/// CORBA repository identity, the ROS 2 naming and role, the DDS-XML
2256/// provenance, the FlatData properties, the vendor directives and the
2257/// source origin.
2258fn write_facets(w: &mut SnapshotWriter, facets: &Facets) -> Result<(), AstError> {
2259    // Source, always present.
2260    match &facets.source.origin {
2261        crate::SourceOrigin::File { path } => {
2262            w.u8(0);
2263            w.str(path)?;
2264        }
2265        crate::SourceOrigin::Synthetic { reason } => {
2266            w.u8(1);
2267            w.str(reason)?;
2268        }
2269        crate::SourceOrigin::Builtin => w.u8(2),
2270    }
2271    match facets.source.span {
2272        Some(span) => {
2273            w.bool(true);
2274            w.u32(span.start);
2275            w.u32(span.end);
2276        }
2277        None => w.bool(false),
2278    }
2279    w.u32(count(facets.source.macro_chain.len())?);
2280    for expansion in &facets.source.macro_chain {
2281        w.str(&expansion.name)?;
2282        match expansion.invoked_at {
2283            Some(span) => {
2284                w.bool(true);
2285                w.u32(span.start);
2286                w.u32(span.end);
2287            }
2288            None => w.bool(false),
2289        }
2290        match &expansion.path {
2291            Some(path) => {
2292                w.bool(true);
2293                w.str(path)?;
2294            }
2295            None => w.bool(false),
2296        }
2297    }
2298    w.u32(count(facets.source.doc_comments.len())?);
2299    for comment in &facets.source.doc_comments {
2300        w.str(comment)?;
2301    }
2302
2303    // XTypes.
2304    match facets.xtypes.as_ref() {
2305        Some(x) => {
2306            w.bool(true);
2307            w.u8(match x.extensibility {
2308                None => 0,
2309                Some(crate::Extensibility::Final) => 1,
2310                Some(crate::Extensibility::Appendable) => 2,
2311                Some(crate::Extensibility::Mutable) => 3,
2312            });
2313            w.bool(x.is_key);
2314            w.bool(x.is_optional);
2315            w.bool(x.is_external);
2316            w.bool(x.is_must_understand);
2317            w.bool(x.is_non_serialized);
2318            w.bool(x.is_nested);
2319            w.bool(x.is_default_literal);
2320            w.bool(x.discriminator_is_key);
2321            match x.default_nested {
2322                None => w.u8(0),
2323                Some(false) => w.u8(1),
2324                Some(true) => w.u8(2),
2325            }
2326            for behaviour in [
2327                x.try_construct,
2328                x.element_try_construct,
2329                x.key_try_construct,
2330                x.discriminator_try_construct,
2331            ] {
2332                w.u8(try_construct_tag(behaviour));
2333            }
2334            w.bool(x.discriminator_is_key);
2335            match x.member_id.as_ref() {
2336                Some(assignment) => {
2337                    w.bool(true);
2338                    w.u32(assignment.effective);
2339                    w.u8(match &assignment.origin {
2340                        crate::MemberIdOrigin::Declared => 0,
2341                        crate::MemberIdOrigin::Hashed { .. } => 1,
2342                        crate::MemberIdOrigin::AutoSequential => 2,
2343                        crate::MemberIdOrigin::AutoHash => 3,
2344                        crate::MemberIdOrigin::PreviousPlusOne => 4,
2345                    });
2346                    if let crate::MemberIdOrigin::Hashed { source_name } = &assignment.origin {
2347                        w.str(source_name)?;
2348                    }
2349                }
2350                None => w.bool(false),
2351            }
2352            match x.bit_bound {
2353                Some(bound) => {
2354                    w.bool(true);
2355                    w.u16(bound);
2356                }
2357                None => w.bool(false),
2358            }
2359            write_handle_opt(w, x.default_value);
2360            match x.range.as_ref() {
2361                Some(range) => {
2362                    w.bool(true);
2363                    write_handle_opt(w, range.min);
2364                    write_handle_opt(w, range.max);
2365                }
2366                None => w.bool(false),
2367            }
2368            match x.unit.as_deref() {
2369                Some(unit) => {
2370                    w.bool(true);
2371                    w.str(unit)?;
2372                }
2373                None => w.bool(false),
2374            }
2375            w.u32(count(x.verbatim.len())?);
2376            for directive in &x.verbatim {
2377                w.str(&directive.language)?;
2378                w.u8(verbatim_tag(directive.placement)?);
2379                w.str(&directive.text)?;
2380            }
2381            w.u8(match x.autoid {
2382                None => 0,
2383                Some(crate::AutoId::Sequential) => 1,
2384                Some(crate::AutoId::Hash) => 2,
2385            });
2386            match x.topic.as_ref() {
2387                Some(topic) => {
2388                    w.bool(true);
2389                    write_str_opt(&mut *w, topic.name.as_deref())?;
2390                    write_str_opt(&mut *w, topic.platform.as_deref())?;
2391                }
2392                None => w.bool(false),
2393            }
2394            match x.data_representation {
2395                Some(mask) => {
2396                    w.bool(true);
2397                    w.bool(mask.xcdr1);
2398                    w.bool(mask.xcdr2);
2399                    w.bool(mask.xml);
2400                }
2401                None => w.bool(false),
2402            }
2403            w.bool(x.ignore_literal_names);
2404            w.bool(x.nested_explicit);
2405            w.bool(x.element_is_external);
2406            w.bool(x.key_is_external);
2407        }
2408        None => w.bool(false),
2409    }
2410
2411    // CORBA.
2412    match facets.corba.as_ref() {
2413        Some(c) => {
2414            w.bool(true);
2415            match c.repository_id.as_ref() {
2416                Some(id) => {
2417                    w.bool(true);
2418                    w.str(&id.0)?;
2419                }
2420                None => w.bool(false),
2421            }
2422            match c.native_mapping.as_deref() {
2423                Some(mapping) => {
2424                    w.bool(true);
2425                    w.str(mapping)?;
2426                }
2427                None => w.bool(false),
2428            }
2429            w.u8(match c.repository_id_origin {
2430                None => 0,
2431                Some(crate::RepositoryIdOrigin::Derived) => 1,
2432                Some(crate::RepositoryIdOrigin::PragmaPrefix) => 2,
2433                Some(crate::RepositoryIdOrigin::PragmaVersion) => 3,
2434                Some(crate::RepositoryIdOrigin::PragmaId) => 4,
2435            });
2436            write_component_role(w, c.component_role.as_ref())?;
2437            w.u32(count(c.template_params.len())?);
2438            for param in &c.template_params {
2439                w.str(&param.name)?;
2440                w.u8(template_param_tag(param.kind)?);
2441                write_type_ref_opt(w, param.constraint.as_ref())?;
2442            }
2443            w.u32(count(c.template_args.len())?);
2444            for arg in &c.template_args {
2445                match arg {
2446                    crate::facet::corba::TemplateArg::Type(reference) => {
2447                        w.u8(0);
2448                        write_type_ref(w, reference)?;
2449                    }
2450                    crate::facet::corba::TemplateArg::Const(expr) => {
2451                        w.u8(1);
2452                        write_handle_opt(w, Some(*expr));
2453                    }
2454                }
2455            }
2456            w.bool(c.is_service);
2457            w.u32(count(c.service_arguments.len())?);
2458            for argument in &c.service_arguments {
2459                write_str_opt(w, argument.name.as_deref())?;
2460                write_handle_opt(w, Some(argument.value));
2461            }
2462            w.bool(c.is_ami);
2463            match c.manages.as_ref() {
2464                Some(manages) => {
2465                    w.bool(true);
2466                    write_ref(w, &manages.component)?;
2467                    write_type_ref_opt(w, manages.primary_key.as_ref())?;
2468                }
2469                None => w.bool(false),
2470            }
2471            write_strings(w, &c.imports)?;
2472            write_strings(w, &c.repository_directives)?;
2473            w.u32(count(c.supports.len())?);
2474            for interface in &c.supports {
2475                write_ref(w, interface)?;
2476            }
2477            w.u8(match c.state_visibility {
2478                None => 0,
2479                Some(crate::StateVisibility::Public) => 1,
2480                Some(crate::StateVisibility::Private) => 2,
2481            });
2482            w.bool(c.is_event_type);
2483        }
2484        None => w.bool(false),
2485    }
2486
2487    // ROS 2.
2488    match facets.ros2.as_ref() {
2489        Some(rf) => {
2490            w.bool(true);
2491            w.bool(rf.was_constant);
2492            write_handle_opt(w, rf.default_value);
2493            match rf.ros_name.as_ref() {
2494                Some(name) => {
2495                    w.bool(true);
2496                    w.str(&name.package)?;
2497                    w.u8(match name.kind {
2498                        zerodds_ast_core::RosInterfaceKind::Msg => 0,
2499                        zerodds_ast_core::RosInterfaceKind::Srv => 1,
2500                        zerodds_ast_core::RosInterfaceKind::Action => 2,
2501                    });
2502                    w.str(&name.name)?;
2503                }
2504                None => w.bool(false),
2505            }
2506            w.u8(match rf.bounded_origin {
2507                None => 0,
2508                Some(crate::RosBoundedOrigin::BoundedArray) => 1,
2509                Some(crate::RosBoundedOrigin::FixedArray) => 2,
2510                Some(crate::RosBoundedOrigin::BoundedString) => 3,
2511                Some(crate::RosBoundedOrigin::UnboundedArray) => 4,
2512            });
2513            w.u8(match rf.interface_role {
2514                None => 0,
2515                Some(crate::RosInterfaceRole::Msg) => 1,
2516                Some(crate::RosInterfaceRole::Request) => 2,
2517                Some(crate::RosInterfaceRole::Response) => 3,
2518                Some(crate::RosInterfaceRole::Goal) => 4,
2519                Some(crate::RosInterfaceRole::Result) => 5,
2520                Some(crate::RosInterfaceRole::Feedback) => 6,
2521            });
2522        }
2523        None => w.bool(false),
2524    }
2525
2526    // DDS-XML.
2527    match facets.xml.as_ref() {
2528        Some(x) => {
2529            w.bool(true);
2530            w.str(&x.element)?;
2531            w.u32(count(x.library_path.len())?);
2532            for segment in &x.library_path {
2533                w.str(segment)?;
2534            }
2535            w.u32(count(x.attributes.len())?);
2536            for (name, value) in &x.attributes {
2537                w.str(name)?;
2538                w.str(value)?;
2539            }
2540            w.u32(count(x.unresolved_idrefs.len())?);
2541            for key in &x.unresolved_idrefs {
2542                w.str(&key.0)?;
2543            }
2544            match x.xsd_type.as_deref() {
2545                Some(xsd) => {
2546                    w.bool(true);
2547                    w.str(xsd)?;
2548                }
2549                None => w.bool(false),
2550            }
2551        }
2552        None => w.bool(false),
2553    }
2554
2555    // FlatData.
2556    match facets.flatdata.as_ref() {
2557        Some(f) => {
2558            w.bool(true);
2559            w.bool(f.is_plain);
2560            w.bool(f.explicit_fixed_layout);
2561            match f.loan_alignment {
2562                Some(align) => {
2563                    w.bool(true);
2564                    w.u8(align);
2565                }
2566                None => w.bool(false),
2567            }
2568        }
2569        None => w.bool(false),
2570    }
2571
2572    // Vendor directives, in full: normalising or dropping them loses
2573    // exactly what a round trip back to that vendor's dialect needs.
2574    match facets.vendor.rti.as_ref() {
2575        Some(rti) => {
2576            w.bool(true);
2577            write_strings(w, &rti.copy_directives)?;
2578            write_bool_opt(w, rti.resolve_name);
2579            write_bool_opt(w, rti.top_level);
2580            write_strings(w, &rti.keylist)?;
2581            write_pairs(w, &rti.unrecognised)?;
2582        }
2583        None => w.bool(false),
2584    }
2585    match facets.vendor.open_dds.as_ref() {
2586        Some(open_dds) => {
2587            w.bool(true);
2588            w.bool(open_dds.dcps_data_type);
2589            write_strings(w, &open_dds.dcps_data_keys)?;
2590            write_bool_opt(w, open_dds.supports_zero_copy);
2591            write_pairs(w, &open_dds.unrecognised)?;
2592        }
2593        None => w.bool(false),
2594    }
2595    match facets.vendor.fast_dds.as_ref() {
2596        Some(fast_dds) => {
2597            w.bool(true);
2598            write_strings(w, &fast_dds.xtypes_extensions)?;
2599            write_bool_opt(w, fast_dds.plain_data_type);
2600            write_pairs(w, &fast_dds.unrecognised)?;
2601        }
2602        None => w.bool(false),
2603    }
2604    match facets.vendor.core_dx.as_ref() {
2605        Some(core_dx) => {
2606            w.bool(true);
2607            write_strings(w, &core_dx.key_fields)?;
2608            write_pairs(w, &core_dx.unrecognised)?;
2609        }
2610        None => w.bool(false),
2611    }
2612
2613    // User-declared annotations, which have no typed field to fall back
2614    // on: dropping one loses the declaration outright.
2615    w.u32(count(facets.annotations.len())?);
2616    for applied in &facets.annotations {
2617        w.str(&applied.name)?;
2618        write_ref(w, &applied.definition)?;
2619        w.u32(count(applied.arguments.len())?);
2620        for argument in &applied.arguments {
2621            write_str_opt(w, argument.name.as_deref())?;
2622            write_handle_opt(w, Some(argument.value));
2623        }
2624    }
2625
2626    Ok(())
2627}
2628
2629fn write_strings(w: &mut SnapshotWriter, values: &[String]) -> Result<(), AstError> {
2630    w.u32(count(values.len())?);
2631    for value in values {
2632        w.str(value)?;
2633    }
2634    Ok(())
2635}
2636
2637fn write_pairs(w: &mut SnapshotWriter, pairs: &[(String, String)]) -> Result<(), AstError> {
2638    w.u32(count(pairs.len())?);
2639    for (name, value) in pairs {
2640        w.str(name)?;
2641        w.str(value)?;
2642    }
2643    Ok(())
2644}
2645
2646fn write_bool_opt(w: &mut SnapshotWriter, value: Option<bool>) {
2647    match value {
2648        None => w.u8(0),
2649        Some(false) => w.u8(1),
2650        Some(true) => w.u8(2),
2651    }
2652}
2653
2654fn write_str_opt(w: &mut SnapshotWriter, value: Option<&str>) -> Result<(), AstError> {
2655    match value {
2656        Some(text) => {
2657            w.bool(true);
2658            w.str(text)?;
2659        }
2660        None => w.bool(false),
2661    }
2662    Ok(())
2663}
2664
2665/// The snapshot tag of a template parameter kind.
2666///
2667/// # Errors
2668/// As [`policy_tag`]. The old fallback was `0`, which is a valid tag —
2669/// an unknown kind read back as `TypeName` and nothing said so.
2670fn template_param_tag(kind: crate::TemplateParamKind) -> Result<u8, AstError> {
2671    tag_of(
2672        crate::TemplateParamKind::ALL
2673            .iter()
2674            .position(|c| *c == kind),
2675    )
2676}
2677
2678/// A component-model role, written as a tag plus its own fields.
2679fn write_component_role(
2680    w: &mut SnapshotWriter,
2681    role: Option<&crate::ComponentRole>,
2682) -> Result<(), AstError> {
2683    let Some(role) = role else {
2684        w.u8(0);
2685        return Ok(());
2686    };
2687    match role {
2688        crate::ComponentRole::Facet { name, interface } => {
2689            w.u8(1);
2690            w.str(name)?;
2691            write_ref(w, interface)?;
2692        }
2693        crate::ComponentRole::Receptacle {
2694            name,
2695            interface,
2696            multiple,
2697        } => {
2698            w.u8(2);
2699            w.str(name)?;
2700            write_ref(w, interface)?;
2701            w.bool(*multiple);
2702        }
2703        crate::ComponentRole::EventSource {
2704            name,
2705            event,
2706            publisher,
2707        } => {
2708            w.u8(3);
2709            w.str(name)?;
2710            write_type_ref(w, event)?;
2711            w.bool(*publisher);
2712        }
2713        crate::ComponentRole::EventSink { name, event } => {
2714            w.u8(4);
2715            w.str(name)?;
2716            write_type_ref(w, event)?;
2717        }
2718        crate::ComponentRole::HomeFactory => w.u8(5),
2719        crate::ComponentRole::HomeFinder => w.u8(6),
2720        crate::ComponentRole::PortTypeRole => w.u8(7),
2721        crate::ComponentRole::ConnectorRole => w.u8(8),
2722    }
2723    Ok(())
2724}
2725
2726/// The snapshot tag of a `@verbatim` placement.
2727///
2728/// # Errors
2729/// As [`policy_tag`]. The old fallback was `0`, a valid tag: an unknown
2730/// placement read back as `BeginFile`.
2731fn verbatim_tag(placement: crate::VerbatimPlacement) -> Result<u8, AstError> {
2732    tag_of(
2733        crate::VerbatimPlacement::ALL
2734            .iter()
2735            .position(|c| *c == placement),
2736    )
2737}
2738
2739/// The tag for one `@try_construct` slot. Zero is absence, which is not
2740/// `DISCARD`: an absent annotation and an explicit `@try_construct
2741/// (DISCARD)` behave alike but a regenerated declaration has to say
2742/// which was written. DDS-XTypes 1.3 §7.3.1.2.1.12.
2743const fn try_construct_tag(behaviour: Option<crate::TryConstruct>) -> u8 {
2744    match behaviour {
2745        None => 0,
2746        Some(crate::TryConstruct::Discard) => 1,
2747        Some(crate::TryConstruct::UseDefault) => 2,
2748        Some(crate::TryConstruct::Trim) => 3,
2749    }
2750}
2751
2752fn read_try_construct(r: &mut SnapshotReader<'_>) -> Result<Option<crate::TryConstruct>, AstError> {
2753    match r.u8()? {
2754        0 => Ok(None),
2755        1 => Ok(Some(crate::TryConstruct::Discard)),
2756        2 => Ok(Some(crate::TryConstruct::UseDefault)),
2757        3 => Ok(Some(crate::TryConstruct::Trim)),
2758        _ => Err(AstError::Core(AstCoreError::MalformedSnapshot {
2759            offset: r.offset(),
2760            expected: "a known try-construct tag",
2761        })),
2762    }
2763}
2764
2765fn read_facets(r: &mut SnapshotReader<'_>) -> Result<Facets, AstError> {
2766    let mut facets = Facets::synthetic("loaded from a snapshot");
2767
2768    facets.source.origin = match r.u8()? {
2769        0 => crate::SourceOrigin::File { path: r.string()? },
2770        1 => crate::SourceOrigin::Synthetic {
2771            reason: r.string()?,
2772        },
2773        2 => crate::SourceOrigin::Builtin,
2774        _ => {
2775            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2776                offset: r.offset(),
2777                expected: "a known source-origin tag",
2778            }));
2779        }
2780    };
2781    facets.source.span = if r.bool()? {
2782        Some(crate::ByteSpan {
2783            start: r.u32()?,
2784            end: r.u32()?,
2785        })
2786    } else {
2787        None
2788    };
2789    let expansions = bounded_count(r, 5)? as usize;
2790    for _ in 0..expansions {
2791        let name = r.string()?;
2792        let invoked_at = if r.bool()? {
2793            Some(crate::ByteSpan {
2794                start: r.u32()?,
2795                end: r.u32()?,
2796            })
2797        } else {
2798            None
2799        };
2800        let path = if r.bool()? { Some(r.string()?) } else { None };
2801        facets.source.macro_chain.push(crate::MacroExpansion {
2802            name,
2803            invoked_at,
2804            path,
2805        });
2806    }
2807    let comments = bounded_count(r, 4)? as usize;
2808    for _ in 0..comments {
2809        facets.source.doc_comments.push(r.string()?);
2810    }
2811
2812    if r.bool()? {
2813        let x = facets.xtypes_mut();
2814        x.extensibility = match r.u8()? {
2815            0 => None,
2816            1 => Some(crate::Extensibility::Final),
2817            2 => Some(crate::Extensibility::Appendable),
2818            3 => Some(crate::Extensibility::Mutable),
2819            _ => {
2820                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2821                    offset: r.offset(),
2822                    expected: "a known extensibility tag",
2823                }));
2824            }
2825        };
2826        x.is_key = r.bool()?;
2827        x.is_optional = r.bool()?;
2828        x.is_external = r.bool()?;
2829        x.is_must_understand = r.bool()?;
2830        x.is_non_serialized = r.bool()?;
2831        x.is_nested = r.bool()?;
2832        x.is_default_literal = r.bool()?;
2833        x.discriminator_is_key = r.bool()?;
2834        x.default_nested = match r.u8()? {
2835            0 => None,
2836            1 => Some(false),
2837            2 => Some(true),
2838            _ => {
2839                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2840                    offset: r.offset(),
2841                    expected: "a known @default_nested tag",
2842                }));
2843            }
2844        };
2845        x.try_construct = read_try_construct(r)?;
2846        x.element_try_construct = read_try_construct(r)?;
2847        x.key_try_construct = read_try_construct(r)?;
2848        x.discriminator_try_construct = read_try_construct(r)?;
2849        x.discriminator_is_key = r.bool()?;
2850        if r.bool()? {
2851            let effective = r.u32()?;
2852            let origin_tag = r.u8()?;
2853            let origin = match origin_tag {
2854                0 => crate::MemberIdOrigin::Declared,
2855                1 => crate::MemberIdOrigin::Hashed {
2856                    source_name: r.string()?,
2857                },
2858                2 => crate::MemberIdOrigin::AutoSequential,
2859                3 => crate::MemberIdOrigin::AutoHash,
2860                4 => crate::MemberIdOrigin::PreviousPlusOne,
2861                _ => {
2862                    return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2863                        offset: r.offset(),
2864                        expected: "a known member-id origin tag",
2865                    }));
2866                }
2867            };
2868            x.member_id = Some(crate::MemberIdAssignment { effective, origin });
2869        }
2870        if r.bool()? {
2871            x.bit_bound = Some(r.u16()?);
2872        }
2873        x.default_value = read_handle_opt(r)?;
2874        if r.bool()? {
2875            x.range = Some(crate::RangeConstraint {
2876                min: read_handle_opt(r)?,
2877                max: read_handle_opt(r)?,
2878            });
2879        }
2880        if r.bool()? {
2881            x.unit = Some(r.string()?);
2882        }
2883        let directives = bounded_count(r, 6)? as usize;
2884        for _ in 0..directives {
2885            let language = r.string()?;
2886            let tag = r.u8()?;
2887            let placement = crate::VerbatimPlacement::ALL
2888                .get(tag as usize)
2889                .copied()
2890                .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2891                    offset: r.offset(),
2892                    expected: "a known verbatim placement tag",
2893                }))?;
2894            let text = r.string()?;
2895            x.verbatim.push(crate::VerbatimDirective {
2896                language,
2897                placement,
2898                text,
2899            });
2900        }
2901        x.autoid = match r.u8()? {
2902            0 => None,
2903            1 => Some(crate::AutoId::Sequential),
2904            2 => Some(crate::AutoId::Hash),
2905            _ => {
2906                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2907                    offset: r.offset(),
2908                    expected: "a known autoid tag",
2909                }));
2910            }
2911        };
2912        if r.bool()? {
2913            x.topic = Some(crate::TopicAnnotation {
2914                name: read_str_opt(r)?,
2915                platform: read_str_opt(r)?,
2916            });
2917        }
2918        if r.bool()? {
2919            x.data_representation = Some(crate::DataRepresentationMask {
2920                xcdr1: r.bool()?,
2921                xcdr2: r.bool()?,
2922                xml: r.bool()?,
2923            });
2924        }
2925        x.ignore_literal_names = r.bool()?;
2926        x.nested_explicit = r.bool()?;
2927        x.element_is_external = r.bool()?;
2928        x.key_is_external = r.bool()?;
2929    }
2930
2931    if r.bool()? {
2932        let c = facets.corba_mut();
2933        if r.bool()? {
2934            c.repository_id = Some(zerodds_ast_core::RepositoryId(r.string()?));
2935        }
2936        if r.bool()? {
2937            c.native_mapping = Some(r.string()?);
2938        }
2939        c.repository_id_origin = match r.u8()? {
2940            0 => None,
2941            1 => Some(crate::RepositoryIdOrigin::Derived),
2942            2 => Some(crate::RepositoryIdOrigin::PragmaPrefix),
2943            3 => Some(crate::RepositoryIdOrigin::PragmaVersion),
2944            4 => Some(crate::RepositoryIdOrigin::PragmaId),
2945            _ => {
2946                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2947                    offset: r.offset(),
2948                    expected: "a known repository-id origin tag",
2949                }));
2950            }
2951        };
2952        c.component_role = read_component_role(r)?;
2953        let params = bounded_count(r, 8)? as usize;
2954        for _ in 0..params {
2955            let name = r.string()?;
2956            let tag = r.u8()?;
2957            let kind = crate::TemplateParamKind::ALL
2958                .get(tag as usize)
2959                .copied()
2960                .ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2961                    offset: r.offset(),
2962                    expected: "a known template-parameter kind tag",
2963                }))?;
2964            let constraint = read_ref_opt(r)?;
2965            c.template_params.push(crate::TemplateParam {
2966                name,
2967                kind,
2968                constraint,
2969            });
2970        }
2971        let args = bounded_count(r, 8)? as usize;
2972        for _ in 0..args {
2973            let arg = match r.u8()? {
2974                0 => crate::facet::corba::TemplateArg::Type(read_type_ref(r)?),
2975                1 => {
2976                    let expr = read_handle_opt(r)?.ok_or(AstError::Core(
2977                        AstCoreError::MalformedSnapshot {
2978                            offset: r.offset(),
2979                            expected: "a constant template argument's expression",
2980                        },
2981                    ))?;
2982                    crate::facet::corba::TemplateArg::Const(expr)
2983                }
2984                _ => {
2985                    return Err(AstError::Core(AstCoreError::MalformedSnapshot {
2986                        offset: r.offset(),
2987                        expected: "a known template-argument tag",
2988                    }));
2989                }
2990            };
2991            c.template_args.push(arg);
2992        }
2993        c.is_service = r.bool()?;
2994        let service_argument_count = bounded_count(r, 5)? as usize;
2995        for _ in 0..service_argument_count {
2996            let argument_name = read_str_opt(r)?;
2997            let value =
2998                read_handle_opt(r)?.ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
2999                    offset: r.offset(),
3000                    expected: "a `@service` argument's expression",
3001                }))?;
3002            c.service_arguments.push(crate::AppliedArgument {
3003                name: argument_name,
3004                value,
3005            });
3006        }
3007        c.is_ami = r.bool()?;
3008        if r.bool()? {
3009            c.manages = Some(crate::HomeManages {
3010                component: read_ref(r)?,
3011                primary_key: read_ref_opt(r)?,
3012            });
3013        }
3014        c.imports = read_strings(r)?;
3015        c.repository_directives = read_strings(r)?;
3016        let supports = bounded_count(r, 8)? as usize;
3017        for _ in 0..supports {
3018            c.supports.push(read_ref(r)?);
3019        }
3020        c.state_visibility = match r.u8()? {
3021            0 => None,
3022            1 => Some(crate::StateVisibility::Public),
3023            2 => Some(crate::StateVisibility::Private),
3024            _ => {
3025                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
3026                    offset: r.offset(),
3027                    expected: "a known state-visibility tag",
3028                }));
3029            }
3030        };
3031        c.is_event_type = r.bool()?;
3032    }
3033
3034    if r.bool()? {
3035        let rf = facets.ros2_mut();
3036        rf.was_constant = r.bool()?;
3037        rf.default_value = read_handle_opt(r)?;
3038        if r.bool()? {
3039            let package = r.string()?;
3040            let kind = match r.u8()? {
3041                0 => zerodds_ast_core::RosInterfaceKind::Msg,
3042                1 => zerodds_ast_core::RosInterfaceKind::Srv,
3043                2 => zerodds_ast_core::RosInterfaceKind::Action,
3044                _ => {
3045                    return Err(AstError::Core(AstCoreError::MalformedSnapshot {
3046                        offset: r.offset(),
3047                        expected: "a known ROS interface kind tag",
3048                    }));
3049                }
3050            };
3051            let name = r.string()?;
3052            rf.ros_name = Some(zerodds_ast_core::RosTypeName {
3053                package,
3054                kind,
3055                name,
3056            });
3057        }
3058        rf.bounded_origin = match r.u8()? {
3059            0 => None,
3060            1 => Some(crate::RosBoundedOrigin::BoundedArray),
3061            2 => Some(crate::RosBoundedOrigin::FixedArray),
3062            3 => Some(crate::RosBoundedOrigin::BoundedString),
3063            4 => Some(crate::RosBoundedOrigin::UnboundedArray),
3064            _ => {
3065                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
3066                    offset: r.offset(),
3067                    expected: "a known ROS bounded-origin tag",
3068                }));
3069            }
3070        };
3071        rf.interface_role = match r.u8()? {
3072            0 => None,
3073            1 => Some(crate::RosInterfaceRole::Msg),
3074            2 => Some(crate::RosInterfaceRole::Request),
3075            3 => Some(crate::RosInterfaceRole::Response),
3076            4 => Some(crate::RosInterfaceRole::Goal),
3077            5 => Some(crate::RosInterfaceRole::Result),
3078            6 => Some(crate::RosInterfaceRole::Feedback),
3079            _ => {
3080                return Err(AstError::Core(AstCoreError::MalformedSnapshot {
3081                    offset: r.offset(),
3082                    expected: "a known ROS interface-role tag",
3083                }));
3084            }
3085        };
3086    }
3087
3088    if r.bool()? {
3089        let x = facets.xml_mut();
3090        x.element = r.string()?;
3091        let segments = bounded_count(r, 4)? as usize;
3092        for _ in 0..segments {
3093            x.library_path.push(r.string()?);
3094        }
3095        let attributes = bounded_count(r, 8)? as usize;
3096        for _ in 0..attributes {
3097            let name = r.string()?;
3098            let value = r.string()?;
3099            x.attributes.push((name, value));
3100        }
3101        let idrefs = bounded_count(r, 4)? as usize;
3102        for _ in 0..idrefs {
3103            x.unresolved_idrefs
3104                .push(zerodds_ast_core::XmlRefKey(r.string()?));
3105        }
3106        if r.bool()? {
3107            x.xsd_type = Some(r.string()?);
3108        }
3109    }
3110
3111    if r.bool()? {
3112        let f = facets.flatdata_mut();
3113        f.is_plain = r.bool()?;
3114        f.explicit_fixed_layout = r.bool()?;
3115        if r.bool()? {
3116            f.loan_alignment = Some(r.u8()?);
3117        }
3118    }
3119
3120    if r.bool()? {
3121        facets.vendor.rti = Some(crate::RtiFacet {
3122            copy_directives: read_strings(r)?,
3123            resolve_name: read_bool_opt(r)?,
3124            top_level: read_bool_opt(r)?,
3125            keylist: read_strings(r)?,
3126            unrecognised: read_pairs(r)?,
3127        });
3128    }
3129    if r.bool()? {
3130        facets.vendor.open_dds = Some(crate::OpenDdsFacet {
3131            dcps_data_type: r.bool()?,
3132            dcps_data_keys: read_strings(r)?,
3133            supports_zero_copy: read_bool_opt(r)?,
3134            unrecognised: read_pairs(r)?,
3135        });
3136    }
3137    if r.bool()? {
3138        facets.vendor.fast_dds = Some(crate::FastDdsFacet {
3139            xtypes_extensions: read_strings(r)?,
3140            plain_data_type: read_bool_opt(r)?,
3141            unrecognised: read_pairs(r)?,
3142        });
3143    }
3144    if r.bool()? {
3145        facets.vendor.core_dx = Some(crate::CoreDxFacet {
3146            key_fields: read_strings(r)?,
3147            unrecognised: read_pairs(r)?,
3148        });
3149    }
3150
3151    let applied_count = bounded_count(r, 10)? as usize;
3152    for _ in 0..applied_count {
3153        let name = r.string()?;
3154        let definition = read_ref(r)?;
3155        let argument_count = bounded_count(r, 5)? as usize;
3156        let mut arguments = Vec::new();
3157        for _ in 0..argument_count {
3158            let argument_name = read_str_opt(r)?;
3159            let value =
3160                read_handle_opt(r)?.ok_or(AstError::Core(AstCoreError::MalformedSnapshot {
3161                    offset: r.offset(),
3162                    expected: "an annotation argument's expression",
3163                }))?;
3164            arguments.push(crate::AppliedArgument {
3165                name: argument_name,
3166                value,
3167            });
3168        }
3169        facets.annotations.push(crate::AppliedAnnotation {
3170            name,
3171            definition,
3172            arguments,
3173        });
3174    }
3175
3176    Ok(facets)
3177}
3178
3179fn read_strings(r: &mut SnapshotReader<'_>) -> Result<Vec<String>, AstError> {
3180    let n = bounded_count(r, 4)? as usize;
3181    let mut out = Vec::new();
3182    for _ in 0..n {
3183        out.push(r.string()?);
3184    }
3185    Ok(out)
3186}
3187
3188fn read_pairs(r: &mut SnapshotReader<'_>) -> Result<Vec<(String, String)>, AstError> {
3189    let n = bounded_count(r, 8)? as usize;
3190    let mut out = Vec::new();
3191    for _ in 0..n {
3192        let name = r.string()?;
3193        let value = r.string()?;
3194        out.push((name, value));
3195    }
3196    Ok(out)
3197}
3198
3199fn read_bool_opt(r: &mut SnapshotReader<'_>) -> Result<Option<bool>, AstError> {
3200    Ok(match r.u8()? {
3201        0 => None,
3202        1 => Some(false),
3203        2 => Some(true),
3204        _ => {
3205            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
3206                offset: r.offset(),
3207                expected: "a known optional-boolean tag",
3208            }));
3209        }
3210    })
3211}
3212
3213fn read_str_opt(r: &mut SnapshotReader<'_>) -> Result<Option<String>, AstError> {
3214    Ok(if r.bool()? { Some(r.string()?) } else { None })
3215}
3216
3217/// A component-model role, read back by its tag.
3218fn read_component_role(
3219    r: &mut SnapshotReader<'_>,
3220) -> Result<Option<crate::ComponentRole>, AstError> {
3221    Ok(match r.u8()? {
3222        0 => None,
3223        1 => Some(crate::ComponentRole::Facet {
3224            name: r.string()?,
3225            interface: read_ref(r)?,
3226        }),
3227        2 => Some(crate::ComponentRole::Receptacle {
3228            name: r.string()?,
3229            interface: read_ref(r)?,
3230            multiple: r.bool()?,
3231        }),
3232        3 => Some(crate::ComponentRole::EventSource {
3233            name: r.string()?,
3234            event: read_type_ref(r)?,
3235            publisher: r.bool()?,
3236        }),
3237        4 => Some(crate::ComponentRole::EventSink {
3238            name: r.string()?,
3239            event: read_type_ref(r)?,
3240        }),
3241        5 => Some(crate::ComponentRole::HomeFactory),
3242        6 => Some(crate::ComponentRole::HomeFinder),
3243        7 => Some(crate::ComponentRole::PortTypeRole),
3244        8 => Some(crate::ComponentRole::ConnectorRole),
3245        _ => {
3246            return Err(AstError::Core(AstCoreError::MalformedSnapshot {
3247                offset: r.offset(),
3248                expected: "a known component-role tag",
3249            }));
3250        }
3251    })
3252}
3253
3254impl Ast {
3255    /// A model with no root and no built-ins, for the snapshot loader to
3256    /// fill from the file's own content.
3257    fn empty_for_load() -> Self {
3258        let mut ast = Self::empty_public();
3259        ast.modules = zerodds_ast_core::Arena::new();
3260        ast.annotation_defs = zerodds_ast_core::Arena::new();
3261        ast.annotation_members = zerodds_ast_core::Arena::new();
3262        ast.by_node_id.clear();
3263        ast.model_ids = zerodds_ast_core::IdAllocator::new(IdSpace::MODEL);
3264        ast.builtin_ids = zerodds_ast_core::IdAllocator::new(IdSpace::BUILTIN);
3265        ast.synthetic_ids = zerodds_ast_core::IdAllocator::new(IdSpace::SYNTHETIC);
3266        ast
3267    }
3268}
3269
3270/// The `NodeId` a fresh model gives its root module, so a loaded model
3271/// can be told apart from one built through the API.
3272pub const ROOT_MODULE_ID: NodeId = NodeId::from_parts(IdSpace::MODEL, 1);