Skip to main content

active_infinity/
view.rs

1//! Zero-copy views over a validated image.
2//!
3//! A [`InfinityView`] can only be built from bytes that passed
4//! [`crate::validate::validate`], so every accessor is bounds-checked by construction.
5//! Variable-length payloads (strings, blobs, secrets) are returned as borrows
6//! into the caller's buffer — no copy, no alignment requirement, and no
7//! `unsafe`: the format stores integers little-endian and the view decodes them
8//! with `from_le_bytes`, which makes the zero-copy contract memory-safe by
9//! construction (ADR-0004).
10
11// Module-level: item docs for this layer live in docs/spec/layout.md and
12// docs/spec/streaming.md; the crate root keeps deny(missing_docs) for the
13// hand-written public surface.
14#![allow(missing_docs)]
15
16use crate::error::{Error, Result};
17use crate::ir::{NodeId, ProvEntry, Provenance};
18use crate::layout::{DecodeOpts, Meta};
19use crate::validate::Validated;
20
21/// A node record in a validated image.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct NodeView {
24    pub id: NodeId,
25    pub kind: u8,
26    pub flags: u8,
27    pub aux: u32,
28    pub a: u64,
29    pub b: u64,
30    pub c: u64,
31}
32
33impl NodeView {
34    pub fn kind_name(&self) -> &'static str {
35        node_kind_name(self.kind)
36    }
37
38    /// Child count for container kinds.
39    pub fn count(&self) -> u64 {
40        match self.kind {
41            7 | 8 | 14 | 20 => self.b,
42            _ => 0,
43        }
44    }
45}
46
47/// A validated, borrow-only module view.
48#[derive(Debug, Clone, Copy)]
49pub struct InfinityView<'a> {
50    validated: Validated<'a>,
51}
52
53impl<'a> InfinityView<'a> {
54    /// Validate `buf` and borrow it.
55    pub fn new(buf: &'a [u8], limits: crate::layout::Limits) -> Result<Self> {
56        let opts = DecodeOpts {
57            limits,
58            verify_hash: true,
59            verify_canonical: false,
60        };
61        Ok(Self {
62            validated: Validated::new(buf, opts)?,
63        })
64    }
65
66    /// Validate `buf` and borrow it, with full option control.
67    pub fn with_opts(buf: &'a [u8], opts: DecodeOpts) -> Result<Self> {
68        Ok(Self {
69            validated: Validated::new(buf, opts)?,
70        })
71    }
72
73    /// Borrow an image that was already validated.
74    pub fn from_validated(validated: Validated<'a>) -> Self {
75        Self { validated }
76    }
77
78    pub fn validated(&self) -> &Validated<'a> {
79        &self.validated
80    }
81
82    pub fn buf(&self) -> &'a [u8] {
83        self.validated.buf()
84    }
85
86    pub fn meta(&self) -> Meta {
87        self.validated.meta()
88    }
89
90    pub fn content_hash(&self) -> [u8; 32] {
91        self.validated.header().content_hash
92    }
93
94    pub fn content_hash_hex(&self) -> String {
95        crate::ir::hash_hex(&self.content_hash())
96    }
97
98    pub fn node_count(&self) -> u32 {
99        self.validated.node_count()
100    }
101
102    pub fn root(&self) -> Result<Option<NodeId>> {
103        self.validated.root()
104    }
105
106    pub fn bindings(&self) -> Result<Vec<(&'a str, NodeId)>> {
107        self.validated.bindings()
108    }
109
110    pub fn annotations(&self) -> Result<Vec<(NodeId, u32, u32)>> {
111        let mut out = Vec::new();
112        for index in 0..self
113            .validated
114            .span(crate::layout::Section::Annotations)
115            .count
116        {
117            let (node, kind, a, _b) = self.validated.annotation(index)?;
118            out.push((NodeId(node), kind, a));
119        }
120        Ok(out)
121    }
122
123    pub fn provenance(&self) -> Result<Option<Provenance>> {
124        self.validated.provenance()
125    }
126
127    /// Read a node record.
128    pub fn node(&self, id: NodeId) -> Result<NodeView> {
129        let rec = self.validated.node(id)?;
130        Ok(NodeView {
131            id,
132            kind: rec.kind,
133            flags: rec.flags,
134            aux: rec.aux,
135            a: rec.a,
136            b: rec.b,
137            c: rec.c,
138        })
139    }
140
141    /// Borrow string `id` from the image (zero copy).
142    pub fn string(&self, id: u32) -> Result<&'a str> {
143        self.validated.string(id)
144    }
145
146    /// Borrow blob payload `id` from the image (zero copy).
147    pub fn blob(&self, id: u32) -> Result<&'a [u8]> {
148        self.validated.blob(id)
149    }
150
151    /// The string id a node points at, for the string-bearing scalar kinds.
152    pub fn node_string_id(&self, id: NodeId) -> Result<Option<u32>> {
153        let node = self.node(id)?;
154        Ok(match node.kind {
155            4 | 13 | 23 => Some(node.aux),
156            _ => None,
157        })
158    }
159
160    /// Borrow the primary string of a string-bearing node (zero copy).
161    pub fn primary_string(&self, id: NodeId) -> Result<Option<&'a str>> {
162        match self.node_string_id(id)? {
163            Some(sid) => Ok(Some(self.string(sid)?)),
164            None => Ok(None),
165        }
166    }
167
168    /// Child ids of a container node, in stored order.
169    pub fn children(&self, id: NodeId) -> Result<Vec<NodeId>> {
170        let node = self.node(id)?;
171        let mut out = Vec::new();
172        match node.kind {
173            6 => out.push(NodeId(node.aux)),
174            7 => {
175                for offset in node.a..node.a + node.b {
176                    out.push(NodeId(self.validated.child(offset as u32)?));
177                }
178            }
179            8 | 20 => {
180                for offset in node.a..node.a + node.b {
181                    out.push(self.validated.field(offset as u32)?.1);
182                }
183            }
184            10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 24 => {
185                let rec = self.validated.node(id)?;
186                for child in self.validated.node_children(id.0, &rec)? {
187                    out.push(NodeId(child));
188                }
189            }
190            _ => {}
191        }
192        Ok(out)
193    }
194
195    /// Table fields of a table node, borrowing names from the image.
196    pub fn fields(&self, id: NodeId) -> Result<Vec<(&'a str, NodeId)>> {
197        let node = self.node(id)?;
198        if node.kind != 8 && node.kind != 20 {
199            return Err(Error::Node {
200                id: id.0,
201                reason: "not a table node",
202            });
203        }
204        let mut out = Vec::with_capacity(node.b as usize);
205        for offset in node.a..node.a + node.b {
206            let (name, child) = self.validated.field(offset as u32)?;
207            out.push((self.string(name)?, child));
208        }
209        Ok(out)
210    }
211
212    /// Derived dependents of a node (nodes that reference it), sorted.
213    pub fn dependents(&self, id: NodeId) -> Result<Vec<NodeId>> {
214        let mut out = Vec::new();
215        for (from, to) in self.edges()? {
216            if from == id {
217                out.push(to);
218            }
219        }
220        out.sort_unstable();
221        out.dedup();
222        Ok(out)
223    }
224
225    /// Derived dependencies of a node (nodes it references), sorted.
226    pub fn dependencies(&self, id: NodeId) -> Result<Vec<NodeId>> {
227        let mut out = self.children(id)?;
228        out.sort_unstable();
229        out.dedup();
230        Ok(out)
231    }
232
233    /// All edges, as (dependency, dependent).
234    pub fn edges(&self) -> Result<Vec<(NodeId, NodeId)>> {
235        let mut out = Vec::new();
236        for index in 0..self.validated.span(crate::layout::Section::Edges).count {
237            let (from, to) = self.validated.edge(index)?;
238            out.push((NodeId(from), NodeId(to)));
239        }
240        Ok(out)
241    }
242
243    /// Provenance entries, borrowed.
244    ///
245    /// A raw accessor: it returns the stored entries as they are. The encoded
246    /// form is already redacted against the module's secrets, so this is safe
247    /// for a validated image; a caller that builds a view over hand-made bytes
248    /// should use `interop::provenance_json` for output that leaves the process.
249    pub fn provenance_entries(&self) -> Result<Vec<(String, ProvEntry)>> {
250        Ok(self
251            .validated
252            .provenance()?
253            .map(|prov| {
254                prov.entries
255                    .into_iter()
256                    .map(|entry| (entry.key.clone(), entry))
257                    .collect()
258            })
259            .unwrap_or_default())
260    }
261}
262
263/// Display name of a node kind.
264pub const fn node_kind_name(kind: u8) -> &'static str {
265    match kind {
266        0 => "Null",
267        1 => "Bool",
268        2 => "Int",
269        3 => "Float",
270        4 => "Str",
271        5 => "DurationMs",
272        6 => "Quantity",
273        7 => "Array",
274        8 => "Table",
275        9 => "Secret",
276        10 => "SecretRef",
277        11 => "Ref",
278        12 => "Env",
279        13 => "Ident",
280        14 => "Interpolate",
281        15 => "Binary",
282        16 => "Unary",
283        17 => "Constrained",
284        18 => "Patch",
285        19 => "SchemaField",
286        20 => "SchemaTable",
287        21 => "SchemaArray",
288        22 => "Semantic",
289        23 => "Blob",
290        24 => "ContentAddressed",
291        _ => "Unknown",
292    }
293}