Skip to main content

active_infinity/
encode.rs

1//! Encoder: canonical module → `AINF` bytes.
2//!
3//! The encoder is a pure function of the canonical prepared module, so
4//! two logically equal modules always produce byte-identical images. Emission
5//! is a single forward pass: every section is laid out in the fixed order
6//! declared by [`crate::layout::Section`], each starting on an 8-byte
7//! boundary, padded with zero bytes, and the content hash is computed over
8//! the finished image with the hash field itself zeroed.
9
10// Module-level: item docs for this layer live in docs/spec/layout.md and
11// docs/spec/streaming.md; the crate root keeps deny(missing_docs) for the
12// hand-written public surface.
13#![allow(missing_docs)]
14
15use crate::canonical::{prepare, Prepared};
16use crate::error::{Error, Result};
17use crate::ir::{
18    AnnotationKind, Constraint, InfinityModule, Inode, InterpPart, PatchOp, ProvValue, Provenance,
19    TypeSpec,
20};
21use crate::layout::{
22    align8, dialect_hash, EncodeOpts, Header, Section, SectionSpan, ANN_KIND_AI_IMMUTABLE,
23    ANN_KIND_CONSTRAINT, ANN_KIND_INTENT, AUX_CONSTRAINT_CUSTOM, AUX_CONSTRAINT_ENUM_ITEM,
24    AUX_CONSTRAINT_MAX, AUX_CONSTRAINT_MIN, AUX_CONSTRAINT_PATTERN, AUX_PATCH_PATH,
25    AUX_PATCH_RENAME_TO, AUX_PATCH_VALUE, AUX_SCHEMA_DEFAULT, AUX_SCHEMA_DEPRECATED,
26    AUX_SCHEMA_MAX, AUX_SCHEMA_MAX_LEN, AUX_SCHEMA_MIN, AUX_SCHEMA_MIN_LEN, AUX_SCHEMA_PATTERN,
27    AUX_TYPE_ENUM_ITEM, AUX_TYPE_TABLE, CONSTRAINT_KIND_CUSTOM, CONSTRAINT_KIND_ENUM,
28    CONSTRAINT_KIND_MIN_MAX, CONSTRAINT_KIND_ODD, CONSTRAINT_KIND_PATTERN, EXT_DIR_SIZE,
29    FLAG_ANNOTATIONS, FLAG_CANONICAL, FLAG_EXTENSIONS, FLAG_PROVENANCE, FLAG_SECRETS,
30    FLAG_STREAMED, FORMAT_MAJOR, FORMAT_MINOR, HEADER_SIZE, NODE_FLAG_0, NODE_FLAG_1,
31    NODE_RECORD_SIZE, PART_EXPR, PART_LITERAL, PATCH_KIND_REMOVE, PATCH_KIND_RENAME,
32    PATCH_KIND_SET, PROV_HEADER_SIZE,
33};
34
35/// One wire node record.
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub(crate) struct NodeRec {
38    pub kind: u8,
39    pub flags: u8,
40    /// Kind-specific small field (string id, op code, node id, type index).
41    pub aux: u32,
42    pub a: u64,
43    pub b: u64,
44    pub c: u64,
45}
46
47impl NodeRec {
48    pub(crate) fn to_bytes(self) -> [u8; NODE_RECORD_SIZE] {
49        let mut out = [0u8; NODE_RECORD_SIZE];
50        out[0] = self.kind;
51        out[1] = self.flags;
52        out[2..4].copy_from_slice(&0u16.to_le_bytes());
53        out[4..8].copy_from_slice(&self.aux.to_le_bytes());
54        out[8..16].copy_from_slice(&self.a.to_le_bytes());
55        out[16..24].copy_from_slice(&self.b.to_le_bytes());
56        out[24..32].copy_from_slice(&self.c.to_le_bytes());
57        out
58    }
59}
60
61struct NodeTables {
62    records: Vec<NodeRec>,
63    children: Vec<u32>,
64    fields: Vec<(u32, u32)>,
65    parts: Vec<(u32, u32)>,
66    aux: Vec<(u32, u32)>,
67}
68
69/// Encode a module with default options.
70pub fn encode(module: &InfinityModule) -> Result<Vec<u8>> {
71    encode_with(module, EncodeOpts::default())
72}
73
74/// Encode a module with explicit options.
75pub fn encode_with(module: &InfinityModule, opts: EncodeOpts) -> Result<Vec<u8>> {
76    let prepared = prepare(module)?;
77    encode_prepared(&prepared, opts)
78}
79
80/// Encode an already-prepared module.
81pub(crate) fn encode_prepared(prepared: &Prepared, opts: EncodeOpts) -> Result<Vec<u8>> {
82    let mut header = Header {
83        major: FORMAT_MAJOR,
84        minor: FORMAT_MINOR,
85        flags: 0,
86        header_size: HEADER_SIZE as u16,
87        content_hash: [0u8; 32],
88        dialect_hash: dialect_hash(),
89        total_size: 0,
90        sections: [SectionSpan::default(); crate::layout::SECTION_COUNT],
91    };
92    if opts.mark_canonical {
93        header.flags |= FLAG_CANONICAL;
94    }
95    if opts.streamed {
96        header.flags |= FLAG_STREAMED;
97    }
98    if !prepared.annotations.is_empty() {
99        header.flags |= FLAG_ANNOTATIONS;
100    }
101    if prepared.provenance.is_some() {
102        header.flags |= FLAG_PROVENANCE;
103    }
104    if !prepared.extensions.is_empty() {
105        header.flags |= FLAG_EXTENSIONS;
106    }
107    if !prepared.secrets.is_empty() {
108        header.flags |= FLAG_SECRETS;
109    }
110
111    let tables = build_node_tables(prepared)?;
112
113    let mut image: Vec<u8> =
114        Vec::with_capacity(HEADER_SIZE + prepared.nodes.len() * NODE_RECORD_SIZE);
115    image.resize(HEADER_SIZE, 0);
116
117    // --- nodes ---------------------------------------------------------
118    header.span_mut(Section::Nodes).offset = image.len() as u32;
119    header.span_mut(Section::Nodes).count = prepared.nodes.len() as u32;
120    for record in &tables.records {
121        image.extend_from_slice(&record.to_bytes());
122    }
123    pad(&mut image);
124
125    // --- children ------------------------------------------------------
126    set_span(
127        &mut header,
128        &image,
129        Section::Children,
130        tables.children.len(),
131    );
132    for child in &tables.children {
133        image.extend_from_slice(&child.to_le_bytes());
134    }
135    pad(&mut image);
136
137    // --- fields --------------------------------------------------------
138    set_span(&mut header, &image, Section::Fields, tables.fields.len());
139    for (name, node) in &tables.fields {
140        image.extend_from_slice(&name.to_le_bytes());
141        image.extend_from_slice(&0u32.to_le_bytes());
142        image.extend_from_slice(&node.to_le_bytes());
143        image.extend_from_slice(&0u32.to_le_bytes());
144    }
145    pad(&mut image);
146
147    // --- parts ---------------------------------------------------------
148    set_span(&mut header, &image, Section::Parts, tables.parts.len());
149    for (tag, value) in &tables.parts {
150        image.extend_from_slice(&tag.to_le_bytes());
151        image.extend_from_slice(&value.to_le_bytes());
152    }
153    pad(&mut image);
154
155    // --- edges (derived) ----------------------------------------------
156    let edges = derive_edges(prepared);
157    set_span(&mut header, &image, Section::Edges, edges.len());
158    for (from, to) in &edges {
159        image.extend_from_slice(&from.to_le_bytes());
160        image.extend_from_slice(&to.to_le_bytes());
161    }
162    pad(&mut image);
163
164    // --- strings -------------------------------------------------------
165    header.span_mut(Section::Strings).offset = image.len() as u32;
166    header.span_mut(Section::Strings).count = prepared.strings.len() as u32;
167    if !prepared.strings.is_empty() {
168        let mut heap_len = 0u32;
169        for string in &prepared.strings {
170            image.extend_from_slice(&heap_len.to_le_bytes());
171            heap_len = heap_len
172                .checked_add(string.len() as u32)
173                .ok_or(Error::TooLarge)?;
174        }
175        image.extend_from_slice(&heap_len.to_le_bytes());
176        for string in &prepared.strings {
177            image.extend_from_slice(string.as_bytes());
178        }
179    }
180    pad(&mut image);
181
182    // --- blobs ---------------------------------------------------------
183    header.span_mut(Section::Blobs).offset = image.len() as u32;
184    header.span_mut(Section::Blobs).count = prepared.blobs.len() as u32;
185    let mut blob_heap_len = 0u32;
186    for blob in &prepared.blobs {
187        let len = blob.payload.as_ref().map(|p| p.len()).unwrap_or(0) as u32;
188        image.extend_from_slice(&blob.hash);
189        image.extend_from_slice(&blob_heap_len.to_le_bytes());
190        image.extend_from_slice(&len.to_le_bytes());
191        blob_heap_len = blob_heap_len.checked_add(len).ok_or(Error::TooLarge)?;
192    }
193    for blob in &prepared.blobs {
194        if let Some(payload) = &blob.payload {
195            image.extend_from_slice(payload);
196        }
197    }
198    pad(&mut image);
199
200    // --- annotations ---------------------------------------------------
201    set_span(
202        &mut header,
203        &image,
204        Section::Annotations,
205        prepared.annotations.len(),
206    );
207    for annotation in &prepared.annotations {
208        let (kind, a, b) = match &annotation.kind {
209            AnnotationKind::Intent(text) => (
210                ANN_KIND_INTENT,
211                *prepared
212                    .string_index
213                    .get(text)
214                    .ok_or(Error::Encode("annotation string missing"))?,
215                0,
216            ),
217            AnnotationKind::Constraint { source, expr } => (
218                ANN_KIND_CONSTRAINT,
219                *prepared
220                    .string_index
221                    .get(source)
222                    .ok_or(Error::Encode("constraint source missing"))?,
223                expr.0,
224            ),
225            AnnotationKind::AiImmutable(flag) => (ANN_KIND_AI_IMMUTABLE, u32::from(*flag), 0),
226        };
227        image.extend_from_slice(&annotation.node.0.to_le_bytes());
228        image.extend_from_slice(&kind.to_le_bytes());
229        image.extend_from_slice(&a.to_le_bytes());
230        image.extend_from_slice(&b.to_le_bytes());
231    }
232    pad(&mut image);
233
234    // --- provenance ----------------------------------------------------
235    header.span_mut(Section::Provenance).offset = image.len() as u32;
236    if let Some(prov) = &prepared.provenance {
237        let block = encode_provenance(prov, prepared)?;
238        header.span_mut(Section::Provenance).count = block.len() as u32;
239        image.extend_from_slice(&block);
240    } else {
241        header.span_mut(Section::Provenance).count = 0;
242    }
243    pad(&mut image);
244
245    // --- extensions ----------------------------------------------------
246    header.span_mut(Section::Extensions).offset = image.len() as u32;
247    header.span_mut(Section::Extensions).count = prepared.extensions.len() as u32;
248    encode_extensions(&prepared.extensions, image.len(), &mut image)?;
249    pad(&mut image);
250
251    // --- unit table ----------------------------------------------------
252    set_span(&mut header, &image, Section::Units, prepared.units.len());
253    for unit in &prepared.units {
254        image.extend_from_slice(&unit.discriminant().to_le_bytes());
255        let name = if unit.is_builtin() {
256            0
257        } else {
258            *prepared
259                .string_index
260                .get(unit.name())
261                .ok_or(Error::Encode("unit name missing from heap"))?
262        };
263        image.extend_from_slice(&name.to_le_bytes());
264    }
265    pad(&mut image);
266
267    // --- type table ----------------------------------------------------
268    set_span(&mut header, &image, Section::Types, prepared.types.len());
269    let mut type_aux_start = vec![0u32; prepared.types.len()];
270    let mut type_aux_count = vec![0u32; prepared.types.len()];
271    {
272        let mut offset = tables.aux.len() as u32;
273        for (index, prepared_type) in prepared.types.iter().enumerate() {
274            type_aux_start[index] = offset;
275            type_aux_count[index] = prepared_type.aux.len() as u32;
276            offset = offset
277                .checked_add(prepared_type.aux.len() as u32)
278                .ok_or(Error::TooLarge)?;
279        }
280    }
281    for (index, prepared_type) in prepared.types.iter().enumerate() {
282        let (code, flags) = type_code(&prepared_type.spec)?;
283        image.extend_from_slice(&code.to_le_bytes());
284        image.extend_from_slice(&flags.to_le_bytes());
285        image.extend_from_slice(&type_aux_start[index].to_le_bytes());
286        image.extend_from_slice(&type_aux_count[index].to_le_bytes());
287    }
288    pad(&mut image);
289
290    // --- aux -----------------------------------------------------------
291    let aux_count = tables.aux.len() + prepared.types.iter().map(|t| t.aux.len()).sum::<usize>();
292    set_span(&mut header, &image, Section::Aux, aux_count);
293    for (tag, value) in &tables.aux {
294        image.extend_from_slice(&tag.to_le_bytes());
295        image.extend_from_slice(&value.to_le_bytes());
296    }
297    for prepared_type in &prepared.types {
298        for (tag, value) in &prepared_type.aux {
299            image.extend_from_slice(&tag.to_le_bytes());
300            image.extend_from_slice(&value.to_le_bytes());
301        }
302    }
303    pad(&mut image);
304
305    // --- numbers -------------------------------------------------------
306    set_span(
307        &mut header,
308        &image,
309        Section::Numbers,
310        prepared.numbers.len(),
311    );
312    for value in &prepared.numbers {
313        image.extend_from_slice(&value.to_le_bytes());
314    }
315    pad(&mut image);
316
317    // --- secrets -------------------------------------------------------
318    header.span_mut(Section::Secrets).offset = image.len() as u32;
319    header.span_mut(Section::Secrets).count = prepared.secrets.len() as u32;
320    let mut secret_heap = 0u32;
321    for secret in &prepared.secrets {
322        image.extend_from_slice(&secret_heap.to_le_bytes());
323        image.extend_from_slice(&(secret.len() as u32).to_le_bytes());
324        secret_heap = secret_heap
325            .checked_add(secret.len() as u32)
326            .ok_or(Error::TooLarge)?;
327    }
328    for secret in &prepared.secrets {
329        image.extend_from_slice(secret);
330    }
331    pad(&mut image);
332
333    // --- hashes --------------------------------------------------------
334    set_span(&mut header, &image, Section::Hashes, prepared.hashes.len());
335    for hash in &prepared.hashes {
336        image.extend_from_slice(hash);
337    }
338    pad(&mut image);
339
340    // --- bindings ------------------------------------------------------
341    header.span_mut(Section::Bindings).offset = image.len() as u32;
342    header.span_mut(Section::Bindings).count = prepared.bindings.len() as u32 + 1;
343    image.extend_from_slice(
344        &prepared
345            .root
346            .map(|root| root.0)
347            .unwrap_or(u32::MAX)
348            .to_le_bytes(),
349    );
350    image.extend_from_slice(&0u32.to_le_bytes());
351    for (name, id) in &prepared.bindings {
352        let sid = *prepared
353            .string_index
354            .get(name)
355            .ok_or(Error::Encode("binding name missing from heap"))?;
356        image.extend_from_slice(&sid.to_le_bytes());
357        image.extend_from_slice(&id.0.to_le_bytes());
358    }
359    pad(&mut image);
360
361    header.total_size = image.len() as u64;
362    header.write(&mut image);
363
364    if opts.write_hash {
365        let hash = hash_image(&image)?;
366        image[16..48].copy_from_slice(&hash);
367        let _ = hash;
368    }
369    Ok(image)
370}
371
372fn set_span(header: &mut Header, image: &[u8], section: Section, count: usize) {
373    header.span_mut(section).offset = image.len() as u32;
374    header.span_mut(section).count = count as u32;
375}
376
377fn pad(image: &mut Vec<u8>) {
378    while image.len() % 8 != 0 {
379        image.push(0);
380    }
381}
382
383/// Compute the Blake3-256 content hash of an image, treating the hash field as
384/// zero.
385pub fn hash_image(image: &[u8]) -> Result<[u8; 32]> {
386    if image.len() < HEADER_SIZE {
387        return Err(Error::HeaderTruncated);
388    }
389    let mut hasher = blake3::Hasher::new();
390    hasher.update(&image[..16]);
391    hasher.update(&[0u8; 32]);
392    hasher.update(&image[48..]);
393    Ok(*hasher.finalize().as_bytes())
394}
395
396fn build_node_tables(prepared: &Prepared) -> Result<NodeTables> {
397    let mut tables = NodeTables {
398        records: Vec::with_capacity(prepared.nodes.len()),
399        children: Vec::new(),
400        fields: Vec::new(),
401        parts: Vec::new(),
402        aux: Vec::new(),
403    };
404    let mut secret_cursor = 0u32;
405    for node in &prepared.nodes {
406        let string = |text: &str| -> Result<u32> {
407            prepared
408                .string_index
409                .get(text)
410                .copied()
411                .ok_or(Error::Encode("string missing from heap"))
412        };
413        let record = match node {
414            Inode::Null => NodeRec {
415                kind: 0,
416                ..NodeRec::default()
417            },
418            Inode::Bool(value) => NodeRec {
419                kind: 1,
420                a: u64::from(*value),
421                ..NodeRec::default()
422            },
423            Inode::Int(value) => NodeRec {
424                kind: 2,
425                a: *value as u64,
426                ..NodeRec::default()
427            },
428            Inode::Float(value) => NodeRec {
429                kind: 3,
430                a: crate::layout::canonical_f64(*value).to_bits(),
431                ..NodeRec::default()
432            },
433            Inode::Str(text) => NodeRec {
434                kind: 4,
435                aux: string(text)?,
436                ..NodeRec::default()
437            },
438            Inode::DurationMs(ms) => NodeRec {
439                kind: 5,
440                a: *ms,
441                ..NodeRec::default()
442            },
443            Inode::Quantity { value, unit } => NodeRec {
444                kind: 6,
445                aux: value.0,
446                a: *prepared
447                    .unit_index
448                    .get(unit)
449                    .ok_or(Error::Encode("unit missing from table"))? as u64,
450                ..NodeRec::default()
451            },
452            Inode::Array { elements } => {
453                let start = tables.children.len() as u32;
454                tables.children.extend(elements.iter().map(|id| id.0));
455                NodeRec {
456                    kind: 7,
457                    a: u64::from(start),
458                    b: elements.len() as u64,
459                    ..NodeRec::default()
460                }
461            }
462            Inode::Table { fields } => {
463                let start = tables.fields.len() as u32;
464                for (name, id) in fields {
465                    tables.fields.push((string(name)?, id.0));
466                }
467                NodeRec {
468                    kind: 8,
469                    a: u64::from(start),
470                    b: fields.len() as u64,
471                    ..NodeRec::default()
472                }
473            }
474            Inode::Secret(_) => {
475                let index = secret_cursor;
476                secret_cursor += 1;
477                NodeRec {
478                    kind: 9,
479                    a: u64::from(index),
480                    ..NodeRec::default()
481                }
482            }
483            Inode::SecretRef { locator } => NodeRec {
484                kind: 10,
485                a: u64::from(locator.0),
486                ..NodeRec::default()
487            },
488            Inode::Ref(id) => NodeRec {
489                kind: 11,
490                a: u64::from(id.0),
491                ..NodeRec::default()
492            },
493            Inode::Env { name, default } => NodeRec {
494                kind: 12,
495                aux: string(name)?,
496                a: default.map(|id| id.0 as u64).unwrap_or(u32::MAX as u64),
497                ..NodeRec::default()
498            },
499            Inode::Ident(name) => NodeRec {
500                kind: 13,
501                aux: string(name)?,
502                ..NodeRec::default()
503            },
504            Inode::Interpolate { parts } => {
505                let start = tables.parts.len() as u32;
506                for part in parts {
507                    match part {
508                        InterpPart::Literal(text) => {
509                            tables.parts.push((PART_LITERAL, string(text)?));
510                        }
511                        InterpPart::Expr(id) => {
512                            tables.parts.push((PART_EXPR, id.0));
513                        }
514                    }
515                }
516                NodeRec {
517                    kind: 14,
518                    a: u64::from(start),
519                    b: parts.len() as u64,
520                    ..NodeRec::default()
521                }
522            }
523            Inode::Binary { op, lhs, rhs } => NodeRec {
524                kind: 15,
525                aux: op.code(),
526                a: u64::from(lhs.0),
527                b: u64::from(rhs.0),
528                ..NodeRec::default()
529            },
530            Inode::Unary { op, expr } => NodeRec {
531                kind: 16,
532                aux: op.code(),
533                a: u64::from(expr.0),
534                ..NodeRec::default()
535            },
536            Inode::Constrained { target, constraint } => {
537                let start = tables.aux.len() as u32;
538                let kind = match constraint {
539                    Constraint::MinMax { min, max } => {
540                        if let Some(min) = min {
541                            tables.aux.push((
542                                AUX_CONSTRAINT_MIN,
543                                *prepared
544                                    .number_index
545                                    .get(min)
546                                    .ok_or(Error::Encode("constraint min missing"))?,
547                            ));
548                        }
549                        if let Some(max) = max {
550                            tables.aux.push((
551                                AUX_CONSTRAINT_MAX,
552                                *prepared
553                                    .number_index
554                                    .get(max)
555                                    .ok_or(Error::Encode("constraint max missing"))?,
556                            ));
557                        }
558                        CONSTRAINT_KIND_MIN_MAX
559                    }
560                    Constraint::Enum(items) => {
561                        for item in items {
562                            tables.aux.push((AUX_CONSTRAINT_ENUM_ITEM, string(item)?));
563                        }
564                        CONSTRAINT_KIND_ENUM
565                    }
566                    Constraint::Pattern(pattern) => {
567                        tables.aux.push((AUX_CONSTRAINT_PATTERN, string(pattern)?));
568                        CONSTRAINT_KIND_PATTERN
569                    }
570                    Constraint::Odd => CONSTRAINT_KIND_ODD,
571                    Constraint::Custom(text) => {
572                        tables.aux.push((AUX_CONSTRAINT_CUSTOM, string(text)?));
573                        CONSTRAINT_KIND_CUSTOM
574                    }
575                };
576                NodeRec {
577                    kind: 17,
578                    aux: kind,
579                    a: u64::from(target.0),
580                    b: u64::from(start),
581                    c: (tables.aux.len() as u32 - start) as u64,
582                    ..NodeRec::default()
583                }
584            }
585            Inode::Patch(op) => {
586                let start = tables.aux.len() as u32;
587                let kind = match op {
588                    PatchOp::Set { path, value } => {
589                        tables.aux.push((AUX_PATCH_PATH, string(path)?));
590                        tables.aux.push((AUX_PATCH_VALUE, value.0));
591                        PATCH_KIND_SET
592                    }
593                    PatchOp::Remove { path } => {
594                        tables.aux.push((AUX_PATCH_PATH, string(path)?));
595                        PATCH_KIND_REMOVE
596                    }
597                    PatchOp::Rename { from, to } => {
598                        tables.aux.push((AUX_PATCH_PATH, string(from)?));
599                        tables.aux.push((AUX_PATCH_RENAME_TO, string(to)?));
600                        PATCH_KIND_RENAME
601                    }
602                };
603                NodeRec {
604                    kind: 18,
605                    aux: kind,
606                    b: u64::from(start),
607                    c: (tables.aux.len() as u32 - start) as u64,
608                    ..NodeRec::default()
609                }
610            }
611            Inode::SchemaField(spec) => {
612                let (code, payload) = type_payload(&spec.typ, prepared)?;
613                let type_id = *prepared
614                    .type_index
615                    .get(&(code, payload))
616                    .ok_or(Error::Encode("schema type missing from table"))?;
617                let start = tables.aux.len() as u32;
618                for (tag, node) in [
619                    (AUX_SCHEMA_MIN, spec.min),
620                    (AUX_SCHEMA_MAX, spec.max),
621                    (AUX_SCHEMA_MIN_LEN, spec.min_len),
622                    (AUX_SCHEMA_MAX_LEN, spec.max_len),
623                    (AUX_SCHEMA_PATTERN, spec.pattern),
624                    (AUX_SCHEMA_DEFAULT, spec.default),
625                ] {
626                    if let Some(id) = node {
627                        tables.aux.push((tag, id.0));
628                    }
629                }
630                if let Some(deprecated) = &spec.deprecated {
631                    tables
632                        .aux
633                        .push((AUX_SCHEMA_DEPRECATED, string(deprecated)?));
634                }
635                NodeRec {
636                    kind: 19,
637                    aux: type_id,
638                    b: u64::from(start),
639                    c: (tables.aux.len() as u32 - start) as u64,
640                    ..NodeRec::default()
641                }
642            }
643            Inode::SchemaTable { fields, closed } => {
644                let start = tables.fields.len() as u32;
645                for (name, id) in fields {
646                    tables.fields.push((string(name)?, id.0));
647                }
648                NodeRec {
649                    kind: 20,
650                    flags: if *closed { NODE_FLAG_0 } else { 0 },
651                    a: u64::from(start),
652                    b: fields.len() as u64,
653                    ..NodeRec::default()
654                }
655            }
656            Inode::SchemaArray { element } => NodeRec {
657                kind: 21,
658                a: u64::from(element.0),
659                ..NodeRec::default()
660            },
661            Inode::Semantic { tag, inner } => NodeRec {
662                kind: 22,
663                aux: tag.code(),
664                a: u64::from(inner.0),
665                ..NodeRec::default()
666            },
667            Inode::Blob {
668                hash,
669                size,
670                tainted,
671                media_type,
672                payload,
673            } => {
674                let key = (*hash, *size, if *tainted { None } else { payload.clone() });
675                let index = *prepared
676                    .blob_index
677                    .get(&key)
678                    .ok_or(Error::Encode("blob missing from table"))?;
679                let mut flags = 0u8;
680                if *tainted {
681                    flags |= NODE_FLAG_0;
682                }
683                if payload.is_some() {
684                    flags |= NODE_FLAG_1;
685                }
686                NodeRec {
687                    kind: 23,
688                    flags,
689                    aux: string(media_type)?,
690                    a: u64::from(index),
691                    b: *size,
692                    ..NodeRec::default()
693                }
694            }
695            Inode::ContentAddressed { media_type, data } => NodeRec {
696                kind: 24,
697                a: u64::from(media_type.0),
698                b: u64::from(data.0),
699                ..NodeRec::default()
700            },
701        };
702        tables.records.push(record);
703    }
704    Ok(tables)
705}
706
707fn type_payload(spec: &TypeSpec, prepared: &Prepared) -> Result<(u32, Vec<(u32, u32)>)> {
708    let string = |text: &str| -> Result<u32> {
709        prepared
710            .string_index
711            .get(text)
712            .copied()
713            .ok_or(Error::Encode("type string missing from heap"))
714    };
715    let mut aux = Vec::new();
716    match spec {
717        TypeSpec::Enum(items) => {
718            for item in items {
719                aux.push((AUX_TYPE_ENUM_ITEM, string(item)?));
720            }
721        }
722        TypeSpec::Table { closed } => aux.push((AUX_TYPE_TABLE, u32::from(*closed))),
723        _ => {}
724    }
725    Ok((spec.code(), aux))
726}
727
728fn type_code(spec: &TypeSpec) -> Result<(u32, u32)> {
729    let flags = match spec {
730        TypeSpec::Table { closed } => u32::from(*closed),
731        _ => 0,
732    };
733    Ok((spec.code(), flags))
734}
735
736fn derive_edges(prepared: &Prepared) -> Vec<(u32, u32)> {
737    let mut edges = Vec::new();
738    for (index, node) in prepared.nodes.iter().enumerate() {
739        let dependent = index as u32;
740        for child in node.children() {
741            edges.push((child.0, dependent));
742        }
743    }
744    edges.sort_unstable();
745    edges.dedup();
746    edges
747}
748
749fn encode_provenance(prov: &Provenance, prepared: &Prepared) -> Result<Vec<u8>> {
750    let mut block = vec![0u8; PROV_HEADER_SIZE];
751    block[0..4].copy_from_slice(&1u32.to_le_bytes());
752    block[4..8].copy_from_slice(&(prov.entries.len() as u32).to_le_bytes());
753    block[8..16].copy_from_slice(&prov.timestamp_ms.to_le_bytes());
754    let sid = |text: &Option<String>| -> Result<u32> {
755        match text {
756            Some(text) => Ok(*prepared
757                .string_index
758                .get(text)
759                .ok_or(Error::Encode("provenance string missing"))?),
760            None => Ok(u32::MAX),
761        }
762    };
763    block[16..20].copy_from_slice(&sid(&prov.producer)?.to_le_bytes());
764    block[20..24].copy_from_slice(&sid(&prov.model)?.to_le_bytes());
765    block[24..28].copy_from_slice(&sid(&prov.tool)?.to_le_bytes());
766    let mut flags = 0u32;
767    if prov.parent.is_some() {
768        flags |= 1;
769    }
770    block[28..32].copy_from_slice(&flags.to_le_bytes());
771    if let Some(parent) = prov.parent {
772        block[32..64].copy_from_slice(&parent);
773    }
774    for entry in &prov.entries {
775        let key = *prepared
776            .string_index
777            .get(&entry.key)
778            .ok_or(Error::Encode("provenance key missing"))?;
779        let (kind, a) = match &entry.value {
780            ProvValue::Text(text) => (
781                0u32,
782                *prepared
783                    .string_index
784                    .get(text)
785                    .ok_or(Error::Encode("provenance text missing"))?,
786            ),
787            ProvValue::Int(value) => (
788                1u32,
789                *prepared
790                    .number_index
791                    .get(value)
792                    .ok_or(Error::Encode("provenance int missing"))?,
793            ),
794            ProvValue::Flag(flag) => (2u32, u32::from(*flag)),
795            ProvValue::Hash(hash) => (
796                3u32,
797                *prepared
798                    .hash_index
799                    .get(hash)
800                    .ok_or(Error::Encode("provenance hash missing"))?,
801            ),
802        };
803        block.extend_from_slice(&kind.to_le_bytes());
804        block.extend_from_slice(&key.to_le_bytes());
805        block.extend_from_slice(&a.to_le_bytes());
806        block.extend_from_slice(&0u32.to_le_bytes());
807    }
808    Ok(block)
809}
810
811fn encode_extensions(
812    extensions: &[(u64, Vec<u8>)],
813    start: usize,
814    image: &mut Vec<u8>,
815) -> Result<()> {
816    if extensions.is_empty() {
817        return Ok(());
818    }
819    let dir_size = EXT_DIR_SIZE * extensions.len();
820    let mut payload_offset = start + dir_size;
821    let mut directory = Vec::with_capacity(dir_size);
822    for (tag, payload) in extensions {
823        directory.extend_from_slice(&tag.to_le_bytes());
824        directory.extend_from_slice(&(payload_offset as u32).to_le_bytes());
825        directory.extend_from_slice(&(payload.len() as u32).to_le_bytes());
826        payload_offset = align8(payload_offset as u64 + payload.len() as u64) as usize;
827    }
828    image.extend_from_slice(&directory);
829    for (_, payload) in extensions {
830        image.extend_from_slice(payload);
831        pad(image);
832    }
833    Ok(())
834}