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