Skip to main content

active_infinity/
validate.rs

1//! Structural validation of untrusted images.
2//!
3//! Validation is total and fail-closed: every byte of the buffer is accounted
4//! for before any caller receives a node, a string, a blob or a
5//! [`crate::view::InfinityView`]. The checker enforces the canonical form as
6//! well as safety — sorted symbol tables, zeroed padding, zeroed unused record
7//! fields — so a validated image cannot carry covert payload in slack space,
8//! and a validated image re-encodes to itself.
9//!
10//! Work is bounded by [`Limits`]: counts are checked against the remaining
11//! bytes before allocation, traversals are iterative (no recursion), and every
12//! reference is bounds-checked.
13
14// Module-level: item docs for this layer live in docs/spec/layout.md and
15// docs/spec/streaming.md; the crate root keeps deny(missing_docs) for the
16// hand-written public surface.
17#![allow(missing_docs)]
18
19use crate::encode::{hash_image, NodeRec};
20use crate::error::{Error, Result};
21use crate::ir::{NodeId, ProvValue, Provenance};
22use crate::layout::{
23    align8, dialect_hash, read_u32, read_u64, Header, Limits, Meta, Section, BLOB_ENTRY_SIZE,
24    EXT_DIR_SIZE, FIELD_SIZE, FLAG_ANNOTATIONS, FLAG_CANONICAL, FLAG_EXTENSIONS, FLAG_PROVENANCE,
25    FLAG_SECRETS, HEADER_SIZE, NODE_FLAG_0, NODE_FLAG_1, NODE_FLAG_KNOWN, NODE_RECORD_SIZE,
26    PROV_ENTRY_SIZE, PROV_HEADER_SIZE, SECTION_COUNT,
27};
28use crate::unit::{UnitTag, UNIT_NAMED};
29
30/// A validated image plus zero-copy accessors.
31#[derive(Debug, Clone, Copy)]
32pub struct Validated<'a> {
33    buf: &'a [u8],
34    header: Header,
35    ends: [u64; SECTION_COUNT],
36    limits: Limits,
37}
38
39impl<'a> Validated<'a> {
40    /// Parse and validate `buf`.
41    pub fn new(buf: &'a [u8], opts: crate::layout::DecodeOpts) -> Result<Self> {
42        opts.limits.check()?;
43        let header = Header::parse(buf)?;
44        if header.dialect_hash != dialect_hash() {
45            return Err(Error::DialectMismatch(header.dialect_hash));
46        }
47        let limits = opts.limits;
48        if buf.len() as u64 > limits.max_total_bytes {
49            return Err(Error::Limit {
50                kind: "total bytes",
51                limit: limits.max_total_bytes,
52            });
53        }
54        validate_counts(&header, limits)?;
55        let ends = validate_layout(buf, &header)?;
56        let validated = Validated {
57            buf,
58            header,
59            ends,
60            limits,
61        };
62
63        if header.flags & FLAG_ANNOTATIONS != 0 && validated.span(Section::Annotations).count == 0 {
64            return Err(Error::Annotation {
65                id: 0,
66                reason: "annotations flag set with an empty table",
67            });
68        }
69        if header.flags & FLAG_ANNOTATIONS == 0 && validated.span(Section::Annotations).count != 0 {
70            return Err(Error::Annotation {
71                id: 0,
72                reason: "annotations present without the flag",
73            });
74        }
75        if header.flags & FLAG_PROVENANCE != 0 && validated.span(Section::Provenance).count == 0 {
76            return Err(Error::Provenance("provenance flag with an empty block"));
77        }
78        if header.flags & FLAG_PROVENANCE == 0 && validated.span(Section::Provenance).count != 0 {
79            return Err(Error::Provenance("provenance block without the flag"));
80        }
81        if header.flags & FLAG_EXTENSIONS != 0 && validated.span(Section::Extensions).count == 0 {
82            return Err(Error::Extension {
83                tag: 0,
84                reason: "extensions flag with an empty directory",
85            });
86        }
87        if header.flags & FLAG_EXTENSIONS == 0 && validated.span(Section::Extensions).count != 0 {
88            return Err(Error::Extension {
89                tag: 0,
90                reason: "extension blocks without the flag",
91            });
92        }
93        if header.flags & FLAG_SECRETS != 0 && validated.span(Section::Secrets).count == 0 {
94            return Err(Error::SecretEntry(0));
95        }
96        if header.flags & FLAG_SECRETS == 0 && validated.span(Section::Secrets).count != 0 {
97            return Err(Error::SecretEntry(0));
98        }
99
100        validated.check_strings()?;
101        validated.check_units(limits)?;
102        validated.check_types(limits)?;
103        validated.check_numbers()?;
104        validated.check_hashes()?;
105        validated.check_secrets(limits)?;
106        validated.check_blobs(limits)?;
107        validated.check_nodes(limits)?;
108        validated.check_fields()?;
109        validated.check_parts()?;
110        validated.check_edges()?;
111        validated.check_annotations()?;
112        validated.check_provenance(limits)?;
113        validated.check_extensions()?;
114        validated.check_reachability(limits)?;
115        validated.check_acyclic()?;
116        validated.check_depth(limits)?;
117
118        if opts.verify_hash {
119            let computed = hash_image(buf)?;
120            if computed != header.content_hash {
121                return Err(Error::HashMismatch);
122            }
123        }
124        if opts.verify_canonical && header.flags & FLAG_CANONICAL != 0 {
125            let module = crate::decode::decode_validated(&validated)?;
126            let image = crate::encode::encode_with(
127                &module,
128                crate::layout::EncodeOpts {
129                    write_hash: true,
130                    mark_canonical: true,
131                    streamed: header.flags & crate::layout::FLAG_STREAMED != 0,
132                },
133            )?;
134            if image != buf {
135                return Err(Error::NotCanonical);
136            }
137        }
138        Ok(validated)
139    }
140
141    pub fn buf(&self) -> &'a [u8] {
142        self.buf
143    }
144
145    /// The limits this image was validated against.
146    pub fn limits_snapshot(&self) -> Limits {
147        self.limits
148    }
149
150    pub fn header(&self) -> &Header {
151        &self.header
152    }
153
154    pub fn limits_span(&self, section: Section) -> (u32, u32) {
155        let span = self.header.span(section);
156        (span.offset, span.count)
157    }
158
159    pub fn span(&self, section: Section) -> crate::layout::SectionSpan {
160        self.header.span(section)
161    }
162
163    /// Byte length of a section body (from its offset to its computed end).
164    pub fn section_len(&self, section: Section) -> u64 {
165        self.ends[section as usize] - self.header.span(section).offset as u64
166    }
167
168    pub fn section(&self, section: Section) -> &'a [u8] {
169        let (offset, _) = self.limits_span(section);
170        let end = self.ends[section as usize];
171        &self.buf[offset as usize..end as usize]
172    }
173
174    /// `Meta` describing this image.
175    pub fn meta(&self) -> Meta {
176        let header = &self.header;
177        Meta {
178            flags: header.flags,
179            major: header.major,
180            minor: header.minor,
181            dialect_hash: header.dialect_hash,
182            content_hash: header.content_hash,
183            total_size: header.total_size,
184            nodes: header.span(Section::Nodes).count,
185            edges: header.span(Section::Edges).count,
186            strings: header.span(Section::Strings).count,
187            string_bytes: self.string_heap_len(),
188            blobs: header.span(Section::Blobs).count,
189            blob_bytes: self.blob_heap_len(),
190            annotations: header.span(Section::Annotations).count,
191            units: header.span(Section::Units).count,
192            types: header.span(Section::Types).count,
193            secrets: header.span(Section::Secrets).count,
194            extensions: header.span(Section::Extensions).count,
195        }
196    }
197
198    pub fn node_count(&self) -> u32 {
199        self.header.span(Section::Nodes).count
200    }
201
202    /// Read a node record.
203    pub(crate) fn node(&self, id: NodeId) -> Result<NodeRec> {
204        let count = self.node_count();
205        if id.0 >= count {
206            return Err(Error::NodeRef(id.0));
207        }
208        let offset =
209            self.header.span(Section::Nodes).offset as usize + id.index() * NODE_RECORD_SIZE;
210        let rec = &self.buf[offset..offset + NODE_RECORD_SIZE];
211        Ok(NodeRec {
212            kind: rec[0],
213            flags: rec[1],
214            aux: u32::from_le_bytes([rec[4], rec[5], rec[6], rec[7]]),
215            a: u64::from_le_bytes(rec[8..16].try_into().expect("8 bytes")),
216            b: u64::from_le_bytes(rec[16..24].try_into().expect("8 bytes")),
217            c: u64::from_le_bytes(rec[24..32].try_into().expect("8 bytes")),
218        })
219    }
220
221    /// Read a child index entry.
222    pub fn child(&self, index: u32) -> Result<u32> {
223        let count = self.header.span(Section::Children).count;
224        if index >= count {
225            return Err(Error::Node {
226                id: index,
227                reason: "child index out of range",
228            });
229        }
230        let offset = self.header.span(Section::Children).offset as usize + index as usize * 4;
231        read_u32(self.buf, offset)
232    }
233
234    /// Read a field entry (string id, node id).
235    pub fn field(&self, index: u32) -> Result<(u32, NodeId)> {
236        let count = self.header.span(Section::Fields).count;
237        if index >= count {
238            return Err(Error::Node {
239                id: index,
240                reason: "field index out of range",
241            });
242        }
243        let offset =
244            self.header.span(Section::Fields).offset as usize + index as usize * FIELD_SIZE;
245        let name = read_u32(self.buf, offset)?;
246        let node = read_u32(self.buf, offset + 8)?;
247        Ok((name, NodeId(node)))
248    }
249
250    /// Read an interpolation part (tag, value).
251    pub fn part(&self, index: u32) -> Result<(u32, u32)> {
252        let count = self.header.span(Section::Parts).count;
253        if index >= count {
254            return Err(Error::Node {
255                id: index,
256                reason: "part index out of range",
257            });
258        }
259        let offset = self.header.span(Section::Parts).offset as usize + index as usize * 8;
260        Ok((read_u32(self.buf, offset)?, read_u32(self.buf, offset + 4)?))
261    }
262
263    /// Read an edge (from, to).
264    pub fn edge(&self, index: u32) -> Result<(u32, u32)> {
265        let count = self.header.span(Section::Edges).count;
266        if index >= count {
267            return Err(Error::Edge {
268                from: index,
269                to: index,
270            });
271        }
272        let offset = self.header.span(Section::Edges).offset as usize + index as usize * 8;
273        Ok((read_u32(self.buf, offset)?, read_u32(self.buf, offset + 4)?))
274    }
275
276    /// Byte length of the string heap (excluding the offset table).
277    pub fn string_heap_len(&self) -> u32 {
278        let count = self.header.span(Section::Strings).count;
279        if count == 0 {
280            return 0;
281        }
282        let offset = self.header.span(Section::Strings).offset as usize + count as usize * 4;
283        read_u32(self.buf, offset).unwrap_or(0)
284    }
285
286    /// Borrow string `id` directly from the buffer (zero copy).
287    pub fn string(&self, id: u32) -> Result<&'a str> {
288        let count = self.header.span(Section::Strings).count;
289        if id >= count {
290            return Err(Error::StringRef(id));
291        }
292        let table = self.header.span(Section::Strings).offset as usize;
293        let start = read_u32(self.buf, table + id as usize * 4)?;
294        let end = read_u32(self.buf, table + id as usize * 4 + 4)?;
295        let heap = table + (count as usize + 1) * 4;
296        let bytes = &self.buf[heap + start as usize..heap + end as usize];
297        std::str::from_utf8(bytes).map_err(|_| Error::Utf8 {
298            offset: (heap + start as usize) as u64,
299        })
300    }
301
302    /// Total blob payload heap length.
303    pub fn blob_heap_len(&self) -> u32 {
304        let count = self.header.span(Section::Blobs).count;
305        if count == 0 {
306            return 0;
307        }
308        let table = self.header.span(Section::Blobs).offset as usize;
309        let last = table + (count as usize - 1) * BLOB_ENTRY_SIZE;
310        let off = read_u32(self.buf, last + 32).unwrap_or(0);
311        let len = read_u32(self.buf, last + 36).unwrap_or(0);
312        off.saturating_add(len)
313    }
314
315    /// Read a blob index entry: (hash, offset-in-heap, length).
316    pub fn blob_entry(&self, id: u32) -> Result<([u8; 32], u32, u32)> {
317        let count = self.header.span(Section::Blobs).count;
318        if id >= count {
319            return Err(Error::Blob {
320                id,
321                reason: "index out of range",
322            });
323        }
324        let offset =
325            self.header.span(Section::Blobs).offset as usize + id as usize * BLOB_ENTRY_SIZE;
326        let mut hash = [0u8; 32];
327        hash.copy_from_slice(&self.buf[offset..offset + 32]);
328        Ok((
329            hash,
330            read_u32(self.buf, offset + 32)?,
331            read_u32(self.buf, offset + 36)?,
332        ))
333    }
334
335    /// Borrow a blob payload directly from the buffer (zero copy).
336    pub fn blob(&self, id: u32) -> Result<&'a [u8]> {
337        let count = self.header.span(Section::Blobs).count;
338        let (_, off, len) = self.blob_entry(id)?;
339        let heap =
340            self.header.span(Section::Blobs).offset as usize + count as usize * BLOB_ENTRY_SIZE;
341        let start = heap + off as usize;
342        let end = start + len as usize;
343        if end > self.ends[Section::Blobs as usize] as usize {
344            return Err(Error::Blob {
345                id,
346                reason: "payload out of range",
347            });
348        }
349        Ok(&self.buf[start..end])
350    }
351
352    /// Read an annotation record: (node, kind, a, b).
353    pub fn annotation(&self, index: u32) -> Result<(u32, u32, u32, u32)> {
354        let count = self.header.span(Section::Annotations).count;
355        if index >= count {
356            return Err(Error::Annotation {
357                id: index,
358                reason: "index out of range",
359            });
360        }
361        let offset = self.header.span(Section::Annotations).offset as usize + index as usize * 16;
362        Ok((
363            read_u32(self.buf, offset)?,
364            read_u32(self.buf, offset + 4)?,
365            read_u32(self.buf, offset + 8)?,
366            read_u32(self.buf, offset + 12)?,
367        ))
368    }
369
370    /// Resolve a unit table entry.
371    pub fn unit(&self, id: u32) -> Result<UnitTag> {
372        let count = self.header.span(Section::Units).count;
373        if id >= count {
374            return Err(Error::UnitRef(id));
375        }
376        let offset = self.header.span(Section::Units).offset as usize + id as usize * 8;
377        let disc = read_u32(self.buf, offset)?;
378        let name = read_u32(self.buf, offset + 4)?;
379        if disc == UNIT_NAMED {
380            Ok(UnitTag::Named(self.string(name)?.to_string()))
381        } else {
382            UnitTag::from_discriminant(disc).ok_or(Error::UnknownUnit(disc))
383        }
384    }
385
386    /// Read a type table entry: (code, flags, aux start, aux count).
387    pub fn type_entry(&self, id: u32) -> Result<(u32, u32, u32, u32)> {
388        let count = self.header.span(Section::Types).count;
389        if id >= count {
390            return Err(Error::Type {
391                id,
392                reason: "index out of range",
393            });
394        }
395        let offset = self.header.span(Section::Types).offset as usize + id as usize * 16;
396        Ok((
397            read_u32(self.buf, offset)?,
398            read_u32(self.buf, offset + 4)?,
399            read_u32(self.buf, offset + 8)?,
400            read_u32(self.buf, offset + 12)?,
401        ))
402    }
403
404    /// Read an aux record.
405    pub fn aux(&self, index: u32) -> Result<(u32, u32)> {
406        let count = self.header.span(Section::Aux).count;
407        if index >= count {
408            return Err(Error::Node {
409                id: index,
410                reason: "aux index out of range",
411            });
412        }
413        let offset = self.header.span(Section::Aux).offset as usize + index as usize * 8;
414        Ok((read_u32(self.buf, offset)?, read_u32(self.buf, offset + 4)?))
415    }
416
417    /// Read a number table entry.
418    pub fn number(&self, index: u32) -> Result<i64> {
419        let count = self.header.span(Section::Numbers).count;
420        if index >= count {
421            return Err(Error::Node {
422                id: index,
423                reason: "number index out of range",
424            });
425        }
426        let offset = self.header.span(Section::Numbers).offset as usize + index as usize * 8;
427        Ok(read_u64(self.buf, offset)? as i64)
428    }
429
430    /// Borrow a secret's raw bytes (crate-internal: no public unlock API).
431    pub fn secret(&self, id: u32) -> Result<&'a [u8]> {
432        let count = self.header.span(Section::Secrets).count;
433        if id >= count {
434            return Err(Error::SecretEntry(id));
435        }
436        let table = self.header.span(Section::Secrets).offset as usize;
437        let off = read_u32(self.buf, table + id as usize * 8)?;
438        let len = read_u32(self.buf, table + id as usize * 8 + 4)?;
439        let heap = table + count as usize * 8;
440        let start = heap + off as usize;
441        let end = start + len as usize;
442        if end > self.ends[Section::Secrets as usize] as usize {
443            return Err(Error::SecretEntry(id));
444        }
445        Ok(&self.buf[start..end])
446    }
447
448    /// Read a hash table entry.
449    pub fn hash_entry(&self, index: u32) -> Result<[u8; 32]> {
450        let count = self.header.span(Section::Hashes).count;
451        if index >= count {
452            return Err(Error::Provenance("hash index out of range"));
453        }
454        let offset = self.header.span(Section::Hashes).offset as usize + index as usize * 32;
455        let mut hash = [0u8; 32];
456        hash.copy_from_slice(&self.buf[offset..offset + 32]);
457        Ok(hash)
458    }
459
460    /// Read one extension directory entry: (tag, payload offset, payload len).
461    pub fn extension_entry(&self, index: u32) -> Result<(u64, u32, u32)> {
462        let count = self.header.span(Section::Extensions).count;
463        if index >= count {
464            return Err(Error::Extension {
465                tag: index as u64,
466                reason: "index out of range",
467            });
468        }
469        let offset = self.header.span(Section::Extensions).offset as usize + index as usize * 16;
470        Ok((
471            read_u64(self.buf, offset)?,
472            read_u32(self.buf, offset + 8)?,
473            read_u32(self.buf, offset + 12)?,
474        ))
475    }
476
477    /// Borrow an extension payload (zero copy).
478    pub fn extension_payload(&self, offset: u32, len: u32) -> Result<&'a [u8]> {
479        let start = offset as usize;
480        let end = start.checked_add(len as usize).ok_or(Error::Extension {
481            tag: 0,
482            reason: "payload length overflows",
483        })?;
484        let section = self.header.span(Section::Extensions);
485        if start < section.offset as usize || end > self.ends[Section::Extensions as usize] as usize
486        {
487            return Err(Error::Extension {
488                tag: 0,
489                reason: "payload out of range",
490            });
491        }
492        Ok(&self.buf[start..end])
493    }
494
495    /// Parse the provenance block.
496    pub fn provenance(&self) -> Result<Option<Provenance>> {
497        let len = self.header.span(Section::Provenance).count as usize;
498        if len == 0 {
499            return Ok(None);
500        }
501        let start = self.header.span(Section::Provenance).offset as usize;
502        if len < PROV_HEADER_SIZE {
503            return Err(Error::Provenance("block shorter than its header"));
504        }
505        let block = &self.buf[start..start + len];
506        let version = read_u32(block, 0)?;
507        if version != 1 {
508            return Err(Error::Provenance("unsupported provenance version"));
509        }
510        let count = read_u32(block, 4)?;
511        let timestamp_ms = read_u64(block, 8)?;
512        let read_sid = |offset: usize| -> Result<Option<String>> {
513            let id = read_u32(block, offset)?;
514            if id == u32::MAX {
515                Ok(None)
516            } else {
517                Ok(Some(self.string(id)?.to_string()))
518            }
519        };
520        let producer = read_sid(16)?;
521        let model = read_sid(20)?;
522        let tool = read_sid(24)?;
523        let flags = read_u32(block, 28)?;
524        if flags & !1 != 0 {
525            return Err(Error::Provenance("unknown provenance flags"));
526        }
527        let parent = if flags & 1 != 0 {
528            let mut hash = [0u8; 32];
529            hash.copy_from_slice(&block[32..64]);
530            Some(hash)
531        } else {
532            if block[32..64].iter().any(|byte| *byte != 0) {
533                return Err(Error::Provenance("parent hash without the flag"));
534            }
535            None
536        };
537        if block[64..PROV_HEADER_SIZE].iter().any(|byte| *byte != 0) {
538            return Err(Error::Provenance("non-zero provenance reserved bytes"));
539        }
540        let mut entries = Vec::with_capacity(count.min(1024) as usize);
541        for index in 0..count as usize {
542            let offset = PROV_HEADER_SIZE + index * PROV_ENTRY_SIZE;
543            if offset + PROV_ENTRY_SIZE > block.len() {
544                return Err(Error::Provenance("entry exceeds block"));
545            }
546            let kind = read_u32(block, offset)?;
547            let key = self.string(read_u32(block, offset + 4)?)?.to_string();
548            let a = read_u32(block, offset + 8)?;
549            let b = read_u32(block, offset + 12)?;
550            if kind > 3 {
551                // Kinds 4+ are reserved. Accepting them would decode to a
552                // value that re-encodes as kind 3, i.e. a non-canonical image.
553                return Err(Error::Provenance("reserved provenance entry kind"));
554            }
555            if b != 0 {
556                return Err(Error::Provenance("non-zero entry reserved word"));
557            }
558            let value = match kind {
559                0 => ProvValue::Text(self.string(a)?.to_string()),
560                1 => ProvValue::Int(self.number(a)?),
561                2 => {
562                    if a > 1 {
563                        return Err(Error::Provenance("flag entry is not 0/1"));
564                    }
565                    ProvValue::Flag(a == 1)
566                }
567                3 => ProvValue::Hash(self.hash_entry(a)?),
568                _ => unreachable!("kind checked above"),
569            };
570            entries.push(crate::ir::ProvEntry { key, value });
571        }
572        if PROV_HEADER_SIZE + count as usize * PROV_ENTRY_SIZE != block.len() {
573            return Err(Error::Provenance("trailing bytes in provenance block"));
574        }
575        Ok(Some(Provenance {
576            producer,
577            timestamp_ms,
578            model,
579            tool,
580            parent,
581            entries,
582        }))
583    }
584
585    /// Module root recorded in the bindings index (row 0), if any.
586    pub fn root(&self) -> Result<Option<NodeId>> {
587        let count = self.header.span(Section::Bindings).count;
588        if count == 0 {
589            return Err(Error::Offset {
590                section: "bindings",
591                offset: self.header.span(Section::Bindings).offset as u64,
592            });
593        }
594        let offset = self.header.span(Section::Bindings).offset as usize;
595        let root = read_u32(self.buf, offset)?;
596        if root == u32::MAX {
597            Ok(None)
598        } else if root < self.node_count() {
599            Ok(Some(NodeId(root)))
600        } else {
601            Err(Error::NodeRef(root))
602        }
603    }
604
605    /// Name bindings in canonical order (sorted by name).
606    pub fn bindings(&self) -> Result<Vec<(&'a str, NodeId)>> {
607        let count = self.header.span(Section::Bindings).count;
608        if count == 0 {
609            return Err(Error::Offset {
610                section: "bindings",
611                offset: self.header.span(Section::Bindings).offset as u64,
612            });
613        }
614        let base = self.header.span(Section::Bindings).offset as usize;
615        let mut out = Vec::with_capacity(count.saturating_sub(1) as usize);
616        for index in 1..count {
617            let offset = base + index as usize * 8;
618            let name = read_u32(self.buf, offset)?;
619            let node = read_u32(self.buf, offset + 4)?;
620            if node >= self.node_count() {
621                return Err(Error::NodeRef(node));
622            }
623            out.push((self.string(name)?, NodeId(node)));
624        }
625        Ok(out)
626    }
627
628    fn check_bindings(&self) -> Result<()> {
629        let count = self.header.span(Section::Bindings).count;
630        if count == 0 {
631            return Err(Error::Offset {
632                section: "bindings",
633                offset: self.header.span(Section::Bindings).offset as u64,
634            });
635        }
636        let base = self.header.span(Section::Bindings).offset as usize;
637        if read_u32(self.buf, base + 4)? != 0 {
638            return Err(Error::ReservedNonZero {
639                section: "bindings",
640                offset: (base + 4) as u64,
641            });
642        }
643        if count as u64 > self.limits.max_nodes as u64 {
644            return Err(Error::Limit {
645                kind: "bindings",
646                limit: self.limits.max_nodes as u64,
647            });
648        }
649        let mut previous: Option<&str> = None;
650        for (name, _) in self.bindings()? {
651            if let Some(previous) = previous {
652                if previous >= name {
653                    return Err(Error::DuplicateBinding);
654                }
655            }
656            previous = Some(name);
657        }
658        Ok(())
659    }
660
661    // ---- checks -------------------------------------------------------
662
663    fn check_strings(&self) -> Result<()> {
664        let count = self.header.span(Section::Strings).count;
665        if count == 0 {
666            return Ok(());
667        }
668        let table = self.header.span(Section::Strings).offset as usize;
669        let heap_len = self.string_heap_len();
670        if heap_len as u64 > self.limits_snapshot().max_string_bytes as u64 {
671            return Err(Error::Limit {
672                kind: "string bytes",
673                limit: self.limits_snapshot().max_string_bytes as u64,
674            });
675        }
676        let heap_len = self.string_heap_len();
677        let heap = table + (count as usize + 1) * 4;
678        if heap + heap_len as usize > self.ends[Section::Strings as usize] as usize {
679            return Err(Error::Offset {
680                section: "strings",
681                offset: table as u64,
682            });
683        }
684        let mut previous: Option<&[u8]> = None;
685        let mut last_end = 0u32;
686        for id in 0..count {
687            let start = read_u32(self.buf, table + id as usize * 4)?;
688            let end = read_u32(self.buf, table + id as usize * 4 + 4)?;
689            if start != last_end || end < start || end > heap_len {
690                return Err(Error::Offset {
691                    section: "strings",
692                    offset: (heap + start as usize) as u64,
693                });
694            }
695            last_end = end;
696            let bytes = &self.buf[heap + start as usize..heap + end as usize];
697            std::str::from_utf8(bytes).map_err(|_| Error::Utf8 {
698                offset: (heap + start as usize) as u64,
699            })?;
700            if let Some(previous) = previous {
701                if previous >= bytes {
702                    return Err(Error::Offset {
703                        section: "strings",
704                        offset: (heap + start as usize) as u64,
705                    });
706                }
707            }
708            previous = Some(bytes);
709        }
710        if last_end != heap_len {
711            return Err(Error::Offset {
712                section: "strings",
713                offset: (heap + last_end as usize) as u64,
714            });
715        }
716        Ok(())
717    }
718
719    fn check_units(&self, limits: Limits) -> Result<()> {
720        let count = self.header.span(Section::Units).count;
721        if count > limits.max_units {
722            return Err(Error::Limit {
723                kind: "units",
724                limit: limits.max_units as u64,
725            });
726        }
727        let mut previous: Option<(u32, u32)> = None;
728        for id in 0..count {
729            let offset = self.header.span(Section::Units).offset as usize + id as usize * 8;
730            let disc = read_u32(self.buf, offset)?;
731            let name = read_u32(self.buf, offset + 4)?;
732            if disc == UNIT_NAMED {
733                if name >= self.header.span(Section::Strings).count {
734                    return Err(Error::UnitRef(name));
735                }
736                if self.string(name)?.is_empty() {
737                    return Err(Error::UnitRef(name));
738                }
739            } else {
740                if UnitTag::from_discriminant(disc).is_none() {
741                    return Err(Error::UnknownUnit(disc));
742                }
743                if name != 0 {
744                    return Err(Error::UnitRef(name));
745                }
746            }
747            if let Some(previous) = previous {
748                if previous >= (disc, name) {
749                    return Err(Error::Offset {
750                        section: "units",
751                        offset: offset as u64,
752                    });
753                }
754            }
755            previous = Some((disc, name));
756        }
757        Ok(())
758    }
759
760    fn check_types(&self, limits: Limits) -> Result<()> {
761        let count = self.header.span(Section::Types).count;
762        if count > limits.max_types {
763            return Err(Error::Limit {
764                kind: "types",
765                limit: limits.max_types as u64,
766            });
767        }
768        let aux_count = self.header.span(Section::Aux).count;
769        let mut previous: Option<(u32, u32, u32, u32)> = None;
770        for id in 0..count {
771            let offset = self.header.span(Section::Types).offset as usize + id as usize * 16;
772            let code = read_u32(self.buf, offset)?;
773            let flags = read_u32(self.buf, offset + 4)?;
774            let start = read_u32(self.buf, offset + 8)?;
775            let len = read_u32(self.buf, offset + 12)?;
776            if code > 11 {
777                return Err(Error::Type {
778                    id,
779                    reason: "unknown type code",
780                });
781            }
782            if code != 7 && flags != 0 {
783                return Err(Error::Type {
784                    id,
785                    reason: "flags on a non-table type",
786                });
787            }
788            if code == 7 && flags > 1 {
789                return Err(Error::Type {
790                    id,
791                    reason: "table closed flag must be 0 or 1",
792                });
793            }
794            if start > aux_count || len > aux_count - start {
795                return Err(Error::Type {
796                    id,
797                    reason: "aux range out of bounds",
798                });
799            }
800            for index in start..start + len {
801                let (tag, value) = self.aux(index)?;
802                match (code, tag) {
803                    (8, crate::layout::AUX_TYPE_ENUM_ITEM) => {
804                        if value >= self.header.span(Section::Strings).count {
805                            return Err(Error::StringRef(value));
806                        }
807                    }
808                    (7, crate::layout::AUX_TYPE_TABLE) => {
809                        if value > 1 {
810                            return Err(Error::Type {
811                                id,
812                                reason: "closed flag must be 0 or 1",
813                            });
814                        }
815                    }
816                    _ => {
817                        return Err(Error::Type {
818                            id,
819                            reason: "unexpected aux record",
820                        })
821                    }
822                }
823            }
824            if code == 8 && len == 0 {
825                return Err(Error::Type {
826                    id,
827                    reason: "enum type without members",
828                });
829            }
830            if let Some(previous) = previous {
831                if previous >= (code, flags, start, len) {
832                    return Err(Error::Offset {
833                        section: "types",
834                        offset: offset as u64,
835                    });
836                }
837            }
838            previous = Some((code, flags, start, len));
839        }
840        Ok(())
841    }
842
843    fn check_numbers(&self) -> Result<()> {
844        let count = self.header.span(Section::Numbers).count;
845        let base = self.header.span(Section::Numbers).offset as usize;
846        let mut previous: Option<i64> = None;
847        for index in 0..count {
848            let value = read_u64(self.buf, base + index as usize * 8)? as i64;
849            if let Some(previous) = previous {
850                if previous >= value {
851                    return Err(Error::Offset {
852                        section: "numbers",
853                        offset: (base + index as usize * 8) as u64,
854                    });
855                }
856            }
857            previous = Some(value);
858        }
859        Ok(())
860    }
861
862    fn check_hashes(&self) -> Result<()> {
863        let count = self.header.span(Section::Hashes).count;
864        if count as u64 > self.limits.max_nodes as u64 {
865            return Err(Error::Limit {
866                kind: "hashes",
867                limit: self.limits.max_nodes as u64,
868            });
869        }
870        let base = self.header.span(Section::Hashes).offset as usize;
871        let mut previous: Option<[u8; 32]> = None;
872        for index in 0..count {
873            let mut hash = [0u8; 32];
874            hash.copy_from_slice(
875                &self.buf[base + index as usize * 32..base + (index as usize + 1) * 32],
876            );
877            if let Some(previous) = previous {
878                if previous >= hash {
879                    return Err(Error::Offset {
880                        section: "hashes",
881                        offset: (base + index as usize * 32) as u64,
882                    });
883                }
884            }
885            previous = Some(hash);
886        }
887        Ok(())
888    }
889
890    fn check_secrets(&self, limits: Limits) -> Result<()> {
891        let count = self.header.span(Section::Secrets).count;
892        if count > limits.max_secrets {
893            return Err(Error::Limit {
894                kind: "secrets",
895                limit: limits.max_secrets as u64,
896            });
897        }
898        let table = self.header.span(Section::Secrets).offset as usize;
899        let heap = table + count as usize * 8;
900        let end = self.ends[Section::Secrets as usize] as usize;
901        let mut cursor = 0u32;
902        for id in 0..count {
903            let off = read_u32(self.buf, table + id as usize * 8)?;
904            let len = read_u32(self.buf, table + id as usize * 8 + 4)?;
905            if off != cursor || heap + off as usize + len as usize > end {
906                return Err(Error::SecretEntry(id));
907            }
908            cursor = cursor.checked_add(len).ok_or(Error::SecretEntry(id))?;
909        }
910        if cursor as u64 > limits.max_secret_bytes as u64 {
911            return Err(Error::Limit {
912                kind: "secret bytes",
913                limit: limits.max_secret_bytes as u64,
914            });
915        }
916        Ok(())
917    }
918
919    fn check_blobs(&self, limits: Limits) -> Result<()> {
920        let count = self.header.span(Section::Blobs).count;
921        if count > limits.max_blobs {
922            return Err(Error::Limit {
923                kind: "blobs",
924                limit: limits.max_blobs as u64,
925            });
926        }
927        let table = self.header.span(Section::Blobs).offset as usize;
928        let heap = table + count as usize * BLOB_ENTRY_SIZE;
929        let end = self.ends[Section::Blobs as usize] as usize;
930        let mut cursor = 0u32;
931        let mut previous: Option<([u8; 32], u32, u32)> = None;
932        for id in 0..count {
933            let offset = table + id as usize * BLOB_ENTRY_SIZE;
934            let mut hash = [0u8; 32];
935            hash.copy_from_slice(&self.buf[offset..offset + 32]);
936            let off = read_u32(self.buf, offset + 32)?;
937            let len = read_u32(self.buf, offset + 36)?;
938            if off != cursor || heap + off as usize + len as usize > end {
939                return Err(Error::Blob {
940                    id,
941                    reason: "payload out of range",
942                });
943            }
944            cursor = cursor.checked_add(len).ok_or(Error::Blob {
945                id,
946                reason: "payload heap overflows",
947            })?;
948            if let Some(previous) = previous {
949                if previous >= (hash, off, len) {
950                    return Err(Error::Offset {
951                        section: "blobs",
952                        offset: offset as u64,
953                    });
954                }
955            }
956            previous = Some((hash, off, len));
957        }
958        if cursor > limits.max_blob_bytes {
959            return Err(Error::Limit {
960                kind: "blob bytes",
961                limit: limits.max_blob_bytes as u64,
962            });
963        }
964        Ok(())
965    }
966
967    fn check_nodes(&self, limits: Limits) -> Result<()> {
968        let count = self.node_count();
969        if count > limits.max_nodes {
970            return Err(Error::Limit {
971                kind: "nodes",
972                limit: limits.max_nodes as u64,
973            });
974        }
975        let child_count = self.header.span(Section::Children).count;
976        let field_count = self.header.span(Section::Fields).count;
977        let part_count = self.header.span(Section::Parts).count;
978        let string_count = self.header.span(Section::Strings).count;
979        let unit_count = self.header.span(Section::Units).count;
980        let type_count = self.header.span(Section::Types).count;
981        let aux_count = self.header.span(Section::Aux).count;
982        let blob_count = self.header.span(Section::Blobs).count;
983        let secret_count = self.header.span(Section::Secrets).count;
984        for index in 0..count {
985            let id = NodeId(index);
986            let rec = self.node(id)?;
987            if rec.kind > 24 {
988                return Err(Error::Node {
989                    id: index,
990                    reason: "unknown node kind",
991                });
992            }
993            if rec.flags & !NODE_FLAG_KNOWN != 0 {
994                return Err(Error::Node {
995                    id: index,
996                    reason: "reserved node flag set",
997                });
998            }
999            let node = |value: u64| -> Result<()> {
1000                if value >= count as u64 {
1001                    Err(Error::NodeRef(value as u32))
1002                } else {
1003                    Ok(())
1004                }
1005            };
1006            let range = |start: u64, len: u64, total: u32, what: &'static str| -> Result<()> {
1007                if start > total as u64 || len > total as u64 - start {
1008                    return Err(Error::Node {
1009                        id: index,
1010                        reason: what,
1011                    });
1012                }
1013                Ok(())
1014            };
1015            let string = |value: u32| -> Result<()> {
1016                if value >= string_count {
1017                    Err(Error::StringRef(value))
1018                } else {
1019                    Ok(())
1020                }
1021            };
1022            let zero = |what: &'static str| -> Result<()> {
1023                if rec.aux != 0 || rec.a != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1024                    return Err(Error::Node {
1025                        id: index,
1026                        reason: what,
1027                    });
1028                }
1029                Ok(())
1030            };
1031            match rec.kind {
1032                0 => zero("unused fields in Null node")?,
1033                1 => {
1034                    if rec.a > 1 || rec.aux != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1035                        return Err(Error::Node {
1036                            id: index,
1037                            reason: "invalid Bool node",
1038                        });
1039                    }
1040                }
1041                2 => {
1042                    if rec.aux != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1043                        return Err(Error::Node {
1044                            id: index,
1045                            reason: "unused fields in Int node",
1046                        });
1047                    }
1048                }
1049                3 => {
1050                    let value = f64::from_bits(rec.a);
1051                    if rec.aux != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1052                        return Err(Error::Node {
1053                            id: index,
1054                            reason: "unused fields in Float node",
1055                        });
1056                    }
1057                    if value.is_nan() && !crate::canonical::is_canonical_nan(value) {
1058                        return Err(Error::Node {
1059                            id: index,
1060                            reason: "non-canonical NaN",
1061                        });
1062                    }
1063                    if value == 0.0 && value.is_sign_negative() {
1064                        return Err(Error::Node {
1065                            id: index,
1066                            reason: "negative zero must be canonicalized",
1067                        });
1068                    }
1069                }
1070                4 | 13 => {
1071                    string(rec.aux)?;
1072                    if rec.a != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1073                        return Err(Error::Node {
1074                            id: index,
1075                            reason: "unused fields in string node",
1076                        });
1077                    }
1078                }
1079                5 => {
1080                    if rec.aux != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1081                        return Err(Error::Node {
1082                            id: index,
1083                            reason: "unused fields in Duration node",
1084                        });
1085                    }
1086                }
1087                6 => {
1088                    node(rec.aux as u64)?;
1089                    if rec.a >= unit_count as u64 {
1090                        return Err(Error::UnitRef(rec.a as u32));
1091                    }
1092                    if rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1093                        return Err(Error::Node {
1094                            id: index,
1095                            reason: "unused fields in Quantity node",
1096                        });
1097                    }
1098                }
1099                7 => {
1100                    range(rec.a, rec.b, child_count, "array range out of bounds")?;
1101                    if rec.aux != 0 || rec.c != 0 || rec.flags != 0 {
1102                        return Err(Error::Node {
1103                            id: index,
1104                            reason: "unused fields in Array node",
1105                        });
1106                    }
1107                    for offset in rec.a..rec.a + rec.b {
1108                        let child = self.child(offset as u32)?;
1109                        node(child as u64)?;
1110                    }
1111                }
1112                8 | 20 => {
1113                    range(rec.a, rec.b, field_count, "field range out of bounds")?;
1114                    if rec.aux != 0 || rec.c != 0 {
1115                        return Err(Error::Node {
1116                            id: index,
1117                            reason: "unused fields in table node",
1118                        });
1119                    }
1120                    if rec.kind == 8 && rec.flags != 0 {
1121                        return Err(Error::Node {
1122                            id: index,
1123                            reason: "flags on a data table",
1124                        });
1125                    }
1126                    if rec.kind == 20 && rec.flags & !NODE_FLAG_0 != 0 {
1127                        return Err(Error::Node {
1128                            id: index,
1129                            reason: "unknown schema-table flag",
1130                        });
1131                    }
1132                    let mut previous: Option<u32> = None;
1133                    for offset in rec.a..rec.a + rec.b {
1134                        let (name, node_id) = self.field(offset as u32)?;
1135                        string(name)?;
1136                        node(node_id.0 as u64)?;
1137                        if let Some(previous) = previous {
1138                            if previous == name {
1139                                return Err(Error::DuplicateField { node: index });
1140                            }
1141                            if previous > name {
1142                                return Err(Error::Node {
1143                                    id: index,
1144                                    reason: "table fields not sorted by key",
1145                                });
1146                            }
1147                        }
1148                        previous = Some(name);
1149                    }
1150                }
1151                9 => {
1152                    if rec.a >= secret_count as u64 {
1153                        return Err(Error::SecretEntry(rec.a as u32));
1154                    }
1155                    if rec.aux != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1156                        return Err(Error::Node {
1157                            id: index,
1158                            reason: "unused fields in Secret node",
1159                        });
1160                    }
1161                }
1162                10 | 11 | 21 => {
1163                    node(rec.a)?;
1164                    if rec.aux != 0 || rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1165                        return Err(Error::Node {
1166                            id: index,
1167                            reason: "unused fields in reference node",
1168                        });
1169                    }
1170                }
1171                12 => {
1172                    string(rec.aux)?;
1173                    if rec.a != u32::MAX as u64 {
1174                        node(rec.a)?;
1175                    }
1176                    if rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1177                        return Err(Error::Node {
1178                            id: index,
1179                            reason: "unused fields in Env node",
1180                        });
1181                    }
1182                }
1183                14 => {
1184                    range(rec.a, rec.b, part_count, "part range out of bounds")?;
1185                    if rec.aux != 0 || rec.c != 0 || rec.flags != 0 {
1186                        return Err(Error::Node {
1187                            id: index,
1188                            reason: "unused fields in Interpolate node",
1189                        });
1190                    }
1191                    for offset in rec.a..rec.a + rec.b {
1192                        let (tag, value) = self.part(offset as u32)?;
1193                        match tag {
1194                            crate::layout::PART_LITERAL => string(value)?,
1195                            crate::layout::PART_EXPR => node(value as u64)?,
1196                            _ => {
1197                                return Err(Error::Node {
1198                                    id: index,
1199                                    reason: "unknown interpolation part tag",
1200                                })
1201                            }
1202                        }
1203                    }
1204                }
1205                15 => {
1206                    if crate::ir::BinOp::from_code(rec.aux).is_none() {
1207                        return Err(Error::Node {
1208                            id: index,
1209                            reason: "unknown binary operator",
1210                        });
1211                    }
1212                    node(rec.a)?;
1213                    node(rec.b)?;
1214                    if rec.c != 0 || rec.flags != 0 {
1215                        return Err(Error::Node {
1216                            id: index,
1217                            reason: "unused fields in Binary node",
1218                        });
1219                    }
1220                }
1221                16 => {
1222                    if crate::ir::UnOp::from_code(rec.aux).is_none() {
1223                        return Err(Error::Node {
1224                            id: index,
1225                            reason: "unknown unary operator",
1226                        });
1227                    }
1228                    node(rec.a)?;
1229                    if rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1230                        return Err(Error::Node {
1231                            id: index,
1232                            reason: "unused fields in Unary node",
1233                        });
1234                    }
1235                }
1236                17 => {
1237                    node(rec.a)?;
1238                    if rec.aux > crate::layout::CONSTRAINT_KIND_CUSTOM {
1239                        return Err(Error::Node {
1240                            id: index,
1241                            reason: "unknown constraint kind",
1242                        });
1243                    }
1244                    range(rec.b, rec.c, aux_count, "constraint aux out of range")?;
1245                    if rec.flags != 0 {
1246                        return Err(Error::Node {
1247                            id: index,
1248                            reason: "flags on a constraint node",
1249                        });
1250                    }
1251                    let mut seen_kinds = Vec::new();
1252                    for offset in rec.b..rec.b + rec.c {
1253                        let (tag, value) = self.aux(offset as u32)?;
1254                        seen_kinds.push(tag);
1255                        match tag {
1256                            crate::layout::AUX_CONSTRAINT_MIN
1257                            | crate::layout::AUX_CONSTRAINT_MAX => {
1258                                if value >= self.header.span(Section::Numbers).count {
1259                                    return Err(Error::Node {
1260                                        id: index,
1261                                        reason: "constraint bound out of range",
1262                                    });
1263                                }
1264                            }
1265                            crate::layout::AUX_CONSTRAINT_ENUM_ITEM
1266                            | crate::layout::AUX_CONSTRAINT_PATTERN
1267                            | crate::layout::AUX_CONSTRAINT_CUSTOM => string(value)?,
1268                            _ => {
1269                                return Err(Error::Node {
1270                                    id: index,
1271                                    reason: "unknown constraint aux tag",
1272                                })
1273                            }
1274                        }
1275                    }
1276                    let expected_matches = match rec.aux {
1277                        crate::layout::CONSTRAINT_KIND_MIN_MAX => seen_kinds.iter().all(|tag| {
1278                            *tag == crate::layout::AUX_CONSTRAINT_MIN
1279                                || *tag == crate::layout::AUX_CONSTRAINT_MAX
1280                        }),
1281                        crate::layout::CONSTRAINT_KIND_ENUM => seen_kinds
1282                            .iter()
1283                            .all(|tag| *tag == crate::layout::AUX_CONSTRAINT_ENUM_ITEM),
1284                        crate::layout::CONSTRAINT_KIND_PATTERN => {
1285                            seen_kinds == [crate::layout::AUX_CONSTRAINT_PATTERN]
1286                        }
1287                        crate::layout::CONSTRAINT_KIND_ODD => seen_kinds.is_empty(),
1288                        _ => seen_kinds == [crate::layout::AUX_CONSTRAINT_CUSTOM],
1289                    };
1290                    if !expected_matches {
1291                        return Err(Error::Node {
1292                            id: index,
1293                            reason: "constraint payload does not match its kind",
1294                        });
1295                    }
1296                    if rec.aux == crate::layout::CONSTRAINT_KIND_ENUM && rec.c == 0 {
1297                        return Err(Error::Node {
1298                            id: index,
1299                            reason: "empty enum constraint",
1300                        });
1301                    }
1302                }
1303                18 => {
1304                    if rec.aux > crate::layout::PATCH_KIND_RENAME {
1305                        return Err(Error::Node {
1306                            id: index,
1307                            reason: "unknown patch kind",
1308                        });
1309                    }
1310                    range(rec.b, rec.c, aux_count, "patch aux out of range")?;
1311                    if rec.a != 0 || rec.flags != 0 {
1312                        return Err(Error::Node {
1313                            id: index,
1314                            reason: "unused fields in Patch node",
1315                        });
1316                    }
1317                    let mut tags = Vec::new();
1318                    for offset in rec.b..rec.b + rec.c {
1319                        let (tag, value) = self.aux(offset as u32)?;
1320                        tags.push(tag);
1321                        match tag {
1322                            crate::layout::AUX_PATCH_PATH | crate::layout::AUX_PATCH_RENAME_TO => {
1323                                string(value)?
1324                            }
1325                            crate::layout::AUX_PATCH_VALUE => node(value as u64)?,
1326                            _ => {
1327                                return Err(Error::Node {
1328                                    id: index,
1329                                    reason: "unknown patch aux tag",
1330                                })
1331                            }
1332                        }
1333                    }
1334                    let ok = match rec.aux {
1335                        crate::layout::PATCH_KIND_SET => {
1336                            tags == [
1337                                crate::layout::AUX_PATCH_PATH,
1338                                crate::layout::AUX_PATCH_VALUE,
1339                            ]
1340                        }
1341                        crate::layout::PATCH_KIND_REMOVE => tags == [crate::layout::AUX_PATCH_PATH],
1342                        _ => {
1343                            tags == [
1344                                crate::layout::AUX_PATCH_PATH,
1345                                crate::layout::AUX_PATCH_RENAME_TO,
1346                            ]
1347                        }
1348                    };
1349                    if !ok {
1350                        return Err(Error::Node {
1351                            id: index,
1352                            reason: "patch payload does not match its kind",
1353                        });
1354                    }
1355                }
1356                19 => {
1357                    if rec.aux >= type_count {
1358                        return Err(Error::Type {
1359                            id: rec.aux,
1360                            reason: "schema field references a missing type",
1361                        });
1362                    }
1363                    range(rec.b, rec.c, aux_count, "schema aux out of range")?;
1364                    if rec.a != 0 || rec.flags != 0 {
1365                        return Err(Error::Node {
1366                            id: index,
1367                            reason: "unused fields in SchemaField node",
1368                        });
1369                    }
1370                    for offset in rec.b..rec.b + rec.c {
1371                        let (tag, value) = self.aux(offset as u32)?;
1372                        match tag {
1373                            crate::layout::AUX_SCHEMA_MIN
1374                            | crate::layout::AUX_SCHEMA_MAX
1375                            | crate::layout::AUX_SCHEMA_MIN_LEN
1376                            | crate::layout::AUX_SCHEMA_MAX_LEN
1377                            | crate::layout::AUX_SCHEMA_PATTERN
1378                            | crate::layout::AUX_SCHEMA_DEFAULT => node(value as u64)?,
1379                            crate::layout::AUX_SCHEMA_DEPRECATED => string(value)?,
1380                            _ => {
1381                                return Err(Error::Node {
1382                                    id: index,
1383                                    reason: "unknown schema aux tag",
1384                                })
1385                            }
1386                        }
1387                    }
1388                }
1389                22 => {
1390                    if rec.aux > 2 {
1391                        return Err(Error::Node {
1392                            id: index,
1393                            reason: "unknown semantic tag",
1394                        });
1395                    }
1396                    node(rec.a)?;
1397                    if rec.b != 0 || rec.c != 0 || rec.flags != 0 {
1398                        return Err(Error::Node {
1399                            id: index,
1400                            reason: "unused fields in Semantic node",
1401                        });
1402                    }
1403                }
1404                23 => {
1405                    if rec.a >= blob_count as u64 {
1406                        return Err(Error::Blob {
1407                            id: rec.a as u32,
1408                            reason: "node references a missing blob",
1409                        });
1410                    }
1411                    string(rec.aux)?;
1412                    if rec.c != 0 {
1413                        return Err(Error::Node {
1414                            id: index,
1415                            reason: "unused field in Blob node",
1416                        });
1417                    }
1418                    if rec.flags & !(NODE_FLAG_0 | NODE_FLAG_1) != 0 {
1419                        return Err(Error::Node {
1420                            id: index,
1421                            reason: "unknown blob flag",
1422                        });
1423                    }
1424                    let (hash, _, len) = self.blob_entry(rec.a as u32)?;
1425                    let tainted = rec.flags & NODE_FLAG_0 != 0;
1426                    let has_payload = rec.flags & NODE_FLAG_1 != 0;
1427                    if tainted && has_payload {
1428                        return Err(Error::Blob {
1429                            id: rec.a as u32,
1430                            reason: "tainted blob must not carry a payload",
1431                        });
1432                    }
1433                    let size = rec.b;
1434                    if has_payload {
1435                        let payload = self.blob(rec.a as u32)?;
1436                        if payload.len() as u64 != size {
1437                            return Err(Error::Blob {
1438                                id: rec.a as u32,
1439                                reason: "payload length disagrees with size",
1440                            });
1441                        }
1442                        let mut hasher = blake3::Hasher::new();
1443                        hasher.update(payload);
1444                        if hasher.finalize().as_bytes() != &hash {
1445                            return Err(Error::Blob {
1446                                id: rec.a as u32,
1447                                reason: "payload hash mismatch",
1448                            });
1449                        }
1450                    } else if len != 0 {
1451                        return Err(Error::Blob {
1452                            id: rec.a as u32,
1453                            reason: "payload present without the flag",
1454                        });
1455                    }
1456                }
1457                24 => {
1458                    node(rec.a)?;
1459                    node(rec.b)?;
1460                    if rec.aux != 0 || rec.c != 0 || rec.flags != 0 {
1461                        return Err(Error::Node {
1462                            id: index,
1463                            reason: "unused fields in ContentAddressed node",
1464                        });
1465                    }
1466                }
1467                _ => unreachable!("kind checked above"),
1468            }
1469        }
1470        Ok(())
1471    }
1472
1473    fn check_fields(&self) -> Result<()> {
1474        let count = self.header.span(Section::Fields).count;
1475        let base = self.header.span(Section::Fields).offset as usize;
1476        for index in 0..count {
1477            let offset = base + index as usize * FIELD_SIZE;
1478            if read_u32(self.buf, offset + 4)? != 0 || read_u32(self.buf, offset + 12)? != 0 {
1479                return Err(Error::ReservedNonZero {
1480                    section: "fields",
1481                    offset: (offset + 4) as u64,
1482                });
1483            }
1484        }
1485        Ok(())
1486    }
1487
1488    fn check_parts(&self) -> Result<()> {
1489        let count = self.header.span(Section::Parts).count;
1490        let base = self.header.span(Section::Parts).offset as usize;
1491        for index in 0..count {
1492            let tag = read_u32(self.buf, base + index as usize * 8)?;
1493            if tag > crate::layout::PART_EXPR {
1494                return Err(Error::Node {
1495                    id: index,
1496                    reason: "unknown part tag",
1497                });
1498            }
1499        }
1500        Ok(())
1501    }
1502
1503    fn check_edges(&self) -> Result<()> {
1504        let count = self.header.span(Section::Edges).count;
1505        let node_count = self.node_count();
1506        let base = self.header.span(Section::Edges).offset as usize;
1507        let mut previous: Option<(u32, u32)> = None;
1508        let mut edges = Vec::with_capacity(count.min(1 << 20) as usize);
1509        for index in 0..count {
1510            let from = read_u32(self.buf, base + index as usize * 8)?;
1511            let to = read_u32(self.buf, base + index as usize * 8 + 4)?;
1512            if from >= node_count || to >= node_count || from == to {
1513                return Err(Error::Edge { from, to });
1514            }
1515            if let Some(previous) = previous {
1516                if previous >= (from, to) {
1517                    return Err(Error::Edge { from, to });
1518                }
1519            }
1520            previous = Some((from, to));
1521            edges.push((from, to));
1522        }
1523        // The edge table must be exactly the derived graph: no extra edges,
1524        // no missing edges. This is what makes the graph unforgeable.
1525        let mut derived: Vec<(u32, u32)> = Vec::new();
1526        let mut scratch: Vec<u32> = Vec::new();
1527        for index in 0..node_count {
1528            let rec = self.node(NodeId(index))?;
1529            self.node_children_into(&rec, &mut scratch)?;
1530            for child in &scratch {
1531                derived.push((*child, index));
1532            }
1533        }
1534        derived.sort_unstable();
1535        derived.dedup();
1536        if derived != edges {
1537            return Err(Error::Edge { from: 0, to: 0 });
1538        }
1539        Ok(())
1540    }
1541
1542    /// Node references in canonical order, written into a reusable buffer.
1543    ///
1544    /// Validation walks every node three times (nodes, edges, reachability); a
1545    /// fresh `Vec` per node dominated the profile, so the hot paths reuse one
1546    /// scratch buffer per traversal.
1547    pub(crate) fn node_children_into(&self, rec: &NodeRec, out: &mut Vec<u32>) -> Result<()> {
1548        out.clear();
1549        match rec.kind {
1550            6 => out.push(rec.aux),
1551            7 => {
1552                for offset in rec.a..rec.a + rec.b {
1553                    out.push(self.child(offset as u32)?);
1554                }
1555            }
1556            8 | 20 => {
1557                for offset in rec.a..rec.a + rec.b {
1558                    out.push(self.field(offset as u32)?.1 .0);
1559                }
1560            }
1561            10 | 11 | 21 | 22 => out.push(rec.a as u32),
1562            12 => {
1563                if rec.a != u32::MAX as u64 {
1564                    out.push(rec.a as u32);
1565                }
1566            }
1567            14 => {
1568                for offset in rec.a..rec.a + rec.b {
1569                    let (tag, value) = self.part(offset as u32)?;
1570                    if tag == crate::layout::PART_EXPR {
1571                        out.push(value);
1572                    }
1573                }
1574            }
1575            15 => {
1576                out.push(rec.a as u32);
1577                out.push(rec.b as u32);
1578            }
1579            16 | 17 => out.push(rec.a as u32),
1580            18 => {
1581                for offset in rec.b..rec.b + rec.c {
1582                    let (tag, value) = self.aux(offset as u32)?;
1583                    if tag == crate::layout::AUX_PATCH_VALUE {
1584                        out.push(value);
1585                    }
1586                }
1587            }
1588            19 => {
1589                for offset in rec.b..rec.b + rec.c {
1590                    let (tag, value) = self.aux(offset as u32)?;
1591                    if tag != crate::layout::AUX_SCHEMA_DEPRECATED {
1592                        out.push(value);
1593                    }
1594                }
1595            }
1596            24 => {
1597                out.push(rec.a as u32);
1598                out.push(rec.b as u32);
1599            }
1600            _ => {}
1601        }
1602        Ok(())
1603    }
1604
1605    /// Node references in canonical order for a record.
1606    pub(crate) fn node_children(&self, _index: u32, rec: &NodeRec) -> Result<Vec<u32>> {
1607        let mut out = Vec::new();
1608        match rec.kind {
1609            6 => out.push(rec.aux),
1610            7 => {
1611                for offset in rec.a..rec.a + rec.b {
1612                    out.push(self.child(offset as u32)?);
1613                }
1614            }
1615            8 | 20 => {
1616                for offset in rec.a..rec.a + rec.b {
1617                    out.push(self.field(offset as u32)?.1 .0);
1618                }
1619            }
1620            10 | 11 | 21 | 22 => out.push(rec.a as u32),
1621            12 => {
1622                if rec.a != u32::MAX as u64 {
1623                    out.push(rec.a as u32);
1624                }
1625            }
1626            14 => {
1627                for offset in rec.a..rec.a + rec.b {
1628                    let (tag, value) = self.part(offset as u32)?;
1629                    if tag == crate::layout::PART_EXPR {
1630                        out.push(value);
1631                    }
1632                }
1633            }
1634            15 => {
1635                out.push(rec.a as u32);
1636                out.push(rec.b as u32);
1637            }
1638            16 => out.push(rec.a as u32),
1639            17 => out.push(rec.a as u32),
1640            18 => {
1641                for offset in rec.b..rec.b + rec.c {
1642                    let (tag, value) = self.aux(offset as u32)?;
1643                    if tag == crate::layout::AUX_PATCH_VALUE {
1644                        out.push(value);
1645                    }
1646                }
1647            }
1648            19 => {
1649                for offset in rec.b..rec.b + rec.c {
1650                    let (tag, value) = self.aux(offset as u32)?;
1651                    if tag != crate::layout::AUX_SCHEMA_DEPRECATED {
1652                        out.push(value);
1653                    }
1654                }
1655            }
1656            24 => {
1657                out.push(rec.a as u32);
1658                out.push(rec.b as u32);
1659            }
1660            _ => {}
1661        }
1662        Ok(out)
1663    }
1664
1665    fn check_annotations(&self) -> Result<()> {
1666        let count = self.header.span(Section::Annotations).count;
1667        let node_count = self.node_count();
1668        let mut previous: Option<(u32, u32, u32, u32)> = None;
1669        for index in 0..count {
1670            let (node, kind, a, b) = self.annotation(index)?;
1671            if node >= node_count {
1672                return Err(Error::Annotation {
1673                    id: index,
1674                    reason: "annotation subject out of range",
1675                });
1676            }
1677            match kind {
1678                crate::layout::ANN_KIND_INTENT => {
1679                    if a >= self.header.span(Section::Strings).count || b != 0 {
1680                        return Err(Error::Annotation {
1681                            id: index,
1682                            reason: "invalid intent annotation",
1683                        });
1684                    }
1685                }
1686                crate::layout::ANN_KIND_CONSTRAINT => {
1687                    if a >= self.header.span(Section::Strings).count || b >= node_count {
1688                        return Err(Error::Annotation {
1689                            id: index,
1690                            reason: "invalid constraint annotation",
1691                        });
1692                    }
1693                }
1694                crate::layout::ANN_KIND_AI_IMMUTABLE => {
1695                    if a > 1 || b != 0 {
1696                        return Err(Error::Annotation {
1697                            id: index,
1698                            reason: "invalid immutability annotation",
1699                        });
1700                    }
1701                }
1702                _ => {
1703                    return Err(Error::Annotation {
1704                        id: index,
1705                        reason: "unknown annotation kind",
1706                    })
1707                }
1708            }
1709            if let Some(previous) = previous {
1710                if previous >= (node, kind, a, b) {
1711                    return Err(Error::Annotation {
1712                        id: index,
1713                        reason: "annotations not sorted",
1714                    });
1715                }
1716            }
1717            previous = Some((node, kind, a, b));
1718        }
1719        Ok(())
1720    }
1721
1722    fn check_provenance(&self, limits: Limits) -> Result<()> {
1723        let len = self.header.span(Section::Provenance).count as usize;
1724        if len == 0 {
1725            return Ok(());
1726        }
1727        let start = self.header.span(Section::Provenance).offset as usize;
1728        let count = read_u32(self.buf, start + 4)?;
1729        if count > limits.max_prov_entries {
1730            return Err(Error::Limit {
1731                kind: "provenance entries",
1732                limit: limits.max_prov_entries as u64,
1733            });
1734        }
1735        let prov = self.provenance()?;
1736        if let Some(prov) = prov {
1737            let mut previous: Option<&str> = None;
1738            for entry in &prov.entries {
1739                if let Some(previous) = previous {
1740                    if previous >= entry.key.as_str() {
1741                        return Err(Error::Provenance("entries not sorted by key"));
1742                    }
1743                }
1744                previous = Some(entry.key.as_str());
1745            }
1746            if let Some(text) = &prov.producer {
1747                if text.is_empty() {
1748                    return Err(Error::Provenance("empty producer"));
1749                }
1750            }
1751        }
1752        Ok(())
1753    }
1754
1755    fn check_extensions(&self) -> Result<()> {
1756        let count = self.header.span(Section::Extensions).count;
1757        if count == 0 {
1758            return Ok(());
1759        }
1760        let start = self.header.span(Section::Extensions).offset as u64;
1761        let end = self.ends[Section::Extensions as usize];
1762        let dir_end = start + count as u64 * EXT_DIR_SIZE as u64;
1763        if dir_end > end {
1764            return Err(Error::Offset {
1765                section: "extensions",
1766                offset: start,
1767            });
1768        }
1769        let mut previous_tag: Option<u64> = None;
1770        let mut previous_end = dir_end;
1771        for index in 0..count {
1772            let offset = start as usize + index as usize * EXT_DIR_SIZE;
1773            let tag = read_u64(self.buf, offset)?;
1774            let payload_offset = read_u32(self.buf, offset + 8)? as u64;
1775            let payload_len = read_u32(self.buf, offset + 12)? as u64;
1776            if let Some(previous) = previous_tag {
1777                if previous >= tag {
1778                    return Err(Error::Extension {
1779                        tag,
1780                        reason: "extension tags not sorted",
1781                    });
1782                }
1783            }
1784            previous_tag = Some(tag);
1785            if payload_offset < align8(previous_end) || payload_offset + payload_len > end {
1786                return Err(Error::Extension {
1787                    tag,
1788                    reason: "payload out of range",
1789                });
1790            }
1791            previous_end = payload_offset + payload_len;
1792        }
1793        // `section_end` already reports the unaligned end of the last payload;
1794        // the padding up to `align8(end)` belongs to the inter-section gap and
1795        // is checked for zeroes by `validate_layout`.
1796        if previous_end != end {
1797            return Err(Error::Extension {
1798                tag: 0,
1799                reason: "extension section has trailing bytes",
1800            });
1801        }
1802        Ok(())
1803    }
1804
1805    /// Reject a dependency cycle with its own code.
1806    ///
1807    /// The depth ceiling would also stop a cycle, but it would report `depth`;
1808    /// a peer needs to distinguish "too deep" from "cyclic" (the text language
1809    /// treats a cycle as fatal before any value exists).
1810    fn check_acyclic(&self) -> Result<()> {
1811        let count = self.node_count();
1812        // 0 = unvisited, 1 = on the stack, 2 = done.
1813        let mut state = vec![0u8; count as usize];
1814        let mut scratch: Vec<u32> = Vec::new();
1815        let mut stack: Vec<(u32, usize)> = Vec::new();
1816        for start in 0..count {
1817            if state[start as usize] != 0 {
1818                continue;
1819            }
1820            stack.push((start, 0));
1821            state[start as usize] = 1;
1822            while let Some((node, cursor)) = stack.pop() {
1823                let rec = self.node(NodeId(node))?;
1824                self.node_children_into(&rec, &mut scratch)?;
1825                if cursor < scratch.len() {
1826                    stack.push((node, cursor + 1));
1827                    let next = scratch[cursor];
1828                    match state[next as usize] {
1829                        0 => {
1830                            state[next as usize] = 1;
1831                            stack.push((next, 0));
1832                        }
1833                        1 => return Err(Error::Cycle { node: next }),
1834                        _ => {}
1835                    }
1836                } else {
1837                    state[node as usize] = 2;
1838                }
1839            }
1840        }
1841        Ok(())
1842    }
1843
1844    fn check_reachability(&self, limits: Limits) -> Result<()> {
1845        self.check_bindings()?;
1846        let count = self.node_count();
1847        if count == 0 {
1848            return Ok(());
1849        }
1850        let mut roots = Vec::new();
1851        if let Some(root) = self.root()? {
1852            roots.push(root.0);
1853        }
1854        for (_, id) in self.bindings()? {
1855            roots.push(id.0);
1856        }
1857        for index in 0..self.header.span(Section::Annotations).count {
1858            let (node, kind, _, b) = self.annotation(index)?;
1859            roots.push(node);
1860            if kind == crate::layout::ANN_KIND_CONSTRAINT {
1861                roots.push(b);
1862            }
1863        }
1864        roots.sort_unstable();
1865        roots.dedup();
1866        let mut seen = vec![false; count as usize];
1867        let mut stack: Vec<u32> = roots;
1868        let mut scratch: Vec<u32> = Vec::new();
1869        let mut visited = 0u32;
1870        while let Some(node) = stack.pop() {
1871            if seen[node as usize] {
1872                continue;
1873            }
1874            seen[node as usize] = true;
1875            visited += 1;
1876            let rec = self.node(NodeId(node))?;
1877            self.node_children_into(&rec, &mut scratch)?;
1878            for child in &scratch {
1879                if !seen[*child as usize] {
1880                    stack.push(*child);
1881                }
1882            }
1883        }
1884        if visited != count {
1885            return Err(Error::Node {
1886                id: (0..count).find(|id| !seen[*id as usize]).unwrap_or(0),
1887                reason: "unreachable node",
1888            });
1889        }
1890        let _ = limits;
1891        Ok(())
1892    }
1893
1894    fn check_depth(&self, limits: Limits) -> Result<()> {
1895        if limits.max_depth == 0 {
1896            return Ok(());
1897        }
1898        let count = self.node_count();
1899        if count == 0 {
1900            return Ok(());
1901        }
1902        let mut depth = vec![0u32; count as usize];
1903        let mut scratch: Vec<u32> = Vec::new();
1904        // Every node with no incoming edge is a root (module root, bindings,
1905        // annotation subjects). Starting only at node 0 would let a second root
1906        // carry an arbitrarily deep chain past the ceiling.
1907        let mut has_parent = vec![false; count as usize];
1908        for node in 0..count {
1909            let rec = self.node(NodeId(node))?;
1910            self.node_children_into(&rec, &mut scratch)?;
1911            for child in &scratch {
1912                has_parent[*child as usize] = true;
1913            }
1914        }
1915        let mut stack: Vec<(u32, u32)> = Vec::new();
1916        for node in 0..count {
1917            if !has_parent[node as usize] {
1918                depth[node as usize] = 1;
1919                stack.push((node, 0));
1920            }
1921        }
1922        while let Some((node, cursor)) = stack.pop() {
1923            let rec = self.node(NodeId(node))?;
1924            self.node_children_into(&rec, &mut scratch)?;
1925            if cursor as usize >= scratch.len() {
1926                continue;
1927            }
1928            stack.push((node, cursor + 1));
1929            let child = scratch[cursor as usize];
1930            let candidate = depth[node as usize] + 1;
1931            if candidate > limits.max_depth {
1932                return Err(Error::Depth {
1933                    limit: limits.max_depth,
1934                });
1935            }
1936            if candidate > depth[child as usize] {
1937                depth[child as usize] = candidate;
1938                stack.push((child, 0));
1939            }
1940        }
1941        Ok(())
1942    }
1943}
1944
1945/// Check every count against the configured limits and the remaining bytes.
1946fn validate_counts(header: &Header, limits: Limits) -> Result<()> {
1947    let checks: [(Section, u32, u32); SECTION_COUNT] = [
1948        (
1949            Section::Nodes,
1950            header.span(Section::Nodes).count,
1951            limits.max_nodes,
1952        ),
1953        (
1954            Section::Children,
1955            header.span(Section::Children).count,
1956            limits.max_children,
1957        ),
1958        (
1959            Section::Fields,
1960            header.span(Section::Fields).count,
1961            limits.max_fields,
1962        ),
1963        (
1964            Section::Parts,
1965            header.span(Section::Parts).count,
1966            limits.max_parts,
1967        ),
1968        (
1969            Section::Edges,
1970            header.span(Section::Edges).count,
1971            limits.max_edges,
1972        ),
1973        (
1974            Section::Strings,
1975            header.span(Section::Strings).count,
1976            limits.max_strings,
1977        ),
1978        (
1979            Section::Blobs,
1980            header.span(Section::Blobs).count,
1981            limits.max_blobs,
1982        ),
1983        (
1984            Section::Annotations,
1985            header.span(Section::Annotations).count,
1986            limits.max_annotations,
1987        ),
1988        (
1989            Section::Units,
1990            header.span(Section::Units).count,
1991            limits.max_units,
1992        ),
1993        (
1994            Section::Types,
1995            header.span(Section::Types).count,
1996            limits.max_types,
1997        ),
1998        (
1999            Section::Aux,
2000            header.span(Section::Aux).count,
2001            limits.max_aux,
2002        ),
2003        (
2004            Section::Numbers,
2005            header.span(Section::Numbers).count,
2006            limits.max_numbers,
2007        ),
2008        (
2009            Section::Secrets,
2010            header.span(Section::Secrets).count,
2011            limits.max_secrets,
2012        ),
2013        (
2014            Section::Provenance,
2015            header.span(Section::Provenance).count,
2016            u32::MAX,
2017        ),
2018        (
2019            Section::Hashes,
2020            header.span(Section::Hashes).count,
2021            u32::MAX,
2022        ),
2023        (
2024            Section::Extensions,
2025            header.span(Section::Extensions).count,
2026            limits.max_extensions,
2027        ),
2028        (
2029            Section::Bindings,
2030            header.span(Section::Bindings).count,
2031            u32::MAX,
2032        ),
2033    ];
2034    for (section, count, limit) in checks {
2035        if count > limit {
2036            return Err(Error::Limit {
2037                kind: section.name(),
2038                limit: limit as u64,
2039            });
2040        }
2041    }
2042    Ok(())
2043}
2044
2045/// Check section ranges: canonical order, alignment, no overlap, zero padding,
2046/// and an exact total size.
2047fn validate_layout(buf: &[u8], header: &Header) -> Result<[u64; SECTION_COUNT]> {
2048    // Bound the buffer first: every later read is in range only after this.
2049    if header.total_size > buf.len() as u64 {
2050        return Err(Error::Truncated {
2051            needed: header.total_size,
2052            found: buf.len() as u64,
2053        });
2054    }
2055    if header.total_size < buf.len() as u64 {
2056        return Err(Error::TrailingBytes {
2057            at: header.total_size,
2058        });
2059    }
2060    let mut ends = [0u64; SECTION_COUNT];
2061    let mut cursor = HEADER_SIZE as u64;
2062    for section in Section::ALL {
2063        let span = header.span(section);
2064        if span.offset as u64 != align8(cursor) {
2065            return Err(Error::Offset {
2066                section: section.name(),
2067                offset: span.offset as u64,
2068            });
2069        }
2070        if span.offset as u64 > buf.len() as u64 {
2071            return Err(Error::Truncated {
2072                needed: span.offset as u64,
2073                found: buf.len() as u64,
2074            });
2075        }
2076        // Padding between the previous section and this one must be zero.
2077        for offset in cursor..span.offset as u64 {
2078            if buf[offset as usize] != 0 {
2079                return Err(Error::Padding { offset });
2080            }
2081        }
2082        let end = section_end(buf, header, section, span.offset as u64, span.count)?;
2083        if end > header.total_size || end > buf.len() as u64 {
2084            // The declared count cannot be satisfied by the image: a count
2085            // fault, distinct from an offset/alignment fault.
2086            return Err(Error::Count {
2087                field: section.name(),
2088                value: span.count as u64,
2089            });
2090        }
2091        ends[section as usize] = end;
2092        cursor = end;
2093    }
2094    if header.total_size != align8(cursor) {
2095        return Err(Error::Offset {
2096            section: "total-size",
2097            offset: header.total_size,
2098        });
2099    }
2100    if header.total_size != buf.len() as u64 {
2101        if header.total_size < buf.len() as u64 {
2102            return Err(Error::TrailingBytes {
2103                at: header.total_size,
2104            });
2105        }
2106        return Err(Error::Truncated {
2107            needed: header.total_size,
2108            found: buf.len() as u64,
2109        });
2110    }
2111    Ok(ends)
2112}
2113
2114fn section_end(
2115    buf: &[u8],
2116    _header: &Header,
2117    section: Section,
2118    offset: u64,
2119    count: u32,
2120) -> Result<u64> {
2121    let end = match section {
2122        Section::Strings => {
2123            if count == 0 {
2124                offset
2125            } else {
2126                let table = offset as usize;
2127                let heap_len = read_u32(buf, table + count as usize * 4)? as u64;
2128                offset + (count as u64 + 1) * 4 + heap_len
2129            }
2130        }
2131        Section::Blobs => {
2132            let table = offset as usize;
2133            let mut heap = 0u64;
2134            for index in 0..count {
2135                let len = read_u32(buf, table + index as usize * BLOB_ENTRY_SIZE + 36)? as u64;
2136                heap = heap.checked_add(len).ok_or(Error::Blob {
2137                    id: index,
2138                    reason: "payload heap overflows",
2139                })?;
2140            }
2141            offset + count as u64 * BLOB_ENTRY_SIZE as u64 + heap
2142        }
2143        Section::Provenance => offset + count as u64,
2144        Section::Secrets => {
2145            let table = offset as usize;
2146            let mut heap = 0u64;
2147            for index in 0..count {
2148                let len = read_u32(buf, table + index as usize * 8 + 4)? as u64;
2149                heap = heap.checked_add(len).ok_or(Error::SecretEntry(index))?;
2150            }
2151            offset + count as u64 * 8 + heap
2152        }
2153        Section::Extensions => {
2154            if count == 0 {
2155                offset
2156            } else {
2157                let mut max_end = offset + count as u64 * EXT_DIR_SIZE as u64;
2158                for index in 0..count {
2159                    let dir = offset as usize + index as usize * EXT_DIR_SIZE;
2160                    let payload_offset = read_u32(buf, dir + 8)? as u64;
2161                    let payload_len = read_u32(buf, dir + 12)? as u64;
2162                    max_end = max_end.max(payload_offset + payload_len);
2163                }
2164                max_end
2165            }
2166        }
2167        Section::Nodes => offset + count as u64 * NODE_RECORD_SIZE as u64,
2168        Section::Children => offset + count as u64 * 4,
2169        Section::Fields => offset + count as u64 * FIELD_SIZE as u64,
2170        Section::Parts => offset + count as u64 * 8,
2171        Section::Edges => offset + count as u64 * 8,
2172        Section::Annotations => offset + count as u64 * 16,
2173        Section::Units => offset + count as u64 * 8,
2174        Section::Types => offset + count as u64 * 16,
2175        Section::Aux => offset + count as u64 * 8,
2176        Section::Numbers => offset + count as u64 * 8,
2177        Section::Hashes => offset + count as u64 * 32,
2178        Section::Bindings => offset + count as u64 * 8,
2179    };
2180    Ok(end)
2181}
2182
2183/// Validate a buffer with the default options.
2184pub fn validate(buf: &[u8]) -> Result<Meta> {
2185    Validated::new(buf, crate::layout::DecodeOpts::default()).map(|validated| validated.meta())
2186}
2187
2188/// Validate a buffer with explicit options.
2189pub fn validate_with(buf: &[u8], opts: crate::layout::DecodeOpts) -> Result<Meta> {
2190    Validated::new(buf, opts).map(|validated| validated.meta())
2191}
2192
2193/// Validate and additionally prove `buf` is exactly the canonical encoding of
2194/// its own content.
2195pub fn validate_canonical(buf: &[u8], limits: Limits) -> Result<Meta> {
2196    let opts = crate::layout::DecodeOpts {
2197        limits,
2198        verify_hash: true,
2199        verify_canonical: true,
2200    };
2201    let validated = Validated::new(buf, opts)?;
2202    let module = crate::decode::decode_validated(&validated)?;
2203    let image = crate::encode::encode_with(
2204        &module,
2205        crate::layout::EncodeOpts {
2206            write_hash: true,
2207            mark_canonical: true,
2208            streamed: validated.header.flags & crate::layout::FLAG_STREAMED != 0,
2209        },
2210    )?;
2211    if image != buf {
2212        return Err(Error::NotCanonical);
2213    }
2214    Ok(validated.meta())
2215}
2216
2217/// Parse a provenance block from bytes already validated (used by tests).
2218pub fn provenance_hash_of(prov: Option<&Provenance>) -> Option<[u8; 32]> {
2219    prov.and_then(|prov| prov.parent)
2220}
2221
2222/// Report whether a value is a hash provenance entry (helper for projections).
2223pub fn is_hash_entry(entry: &crate::ir::ProvEntry) -> bool {
2224    matches!(entry.value, ProvValue::Hash(_))
2225}