Skip to main content

verit_core/
encode.rs

1//! Encoder: `Schema` + `Value` tree → message bytes.
2//!
3//! Envelope: 24-byte header, optional inline canonical schema, padding to 8,
4//! then the payload arena. All offsets in the message are absolute u32 byte
5//! positions within the buffer. See the architecture docs.
6
7use crate::error::{Error, Result};
8use crate::layout::{align_up, slot_size_align};
9use crate::schema::{Schema, Type};
10use crate::value::Value;
11
12pub const MESSAGE_MAGIC: &[u8; 4] = b"VRT2";
13pub const FLAG_INLINE_SCHEMA: u16 = 1;
14/// Envelope: magic(4) flags(2) reserved(2) schema_id(16) root_off(4) schema_len(4).
15pub const HEADER_LEN: usize = 32;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum SchemaMode {
19    /// Embed the full canonical schema: the message alone is self-describing.
20    Inline,
21    /// Carry only the 128-bit schema id (streams / registries amortize the
22    /// schema out of band).
23    HashOnly,
24}
25
26pub fn encode(schema: &Schema, root: &Value, mode: SchemaMode) -> Result<Vec<u8>> {
27    let root_fields = match root {
28        Value::Struct(fields) => fields,
29        other => {
30            return Err(Error::TypeMismatch {
31                expected: "struct".into(),
32                got: other.kind().into(),
33            })
34        }
35    };
36    let mut enc = Encoder {
37        schema,
38        buf: Vec::with_capacity(256),
39    };
40    enc.buf.extend_from_slice(MESSAGE_MAGIC);
41    let inline = mode == SchemaMode::Inline;
42    let flags: u16 = if inline { FLAG_INLINE_SCHEMA } else { 0 };
43    enc.buf.extend_from_slice(&flags.to_le_bytes());
44    enc.buf.extend_from_slice(&0u16.to_le_bytes()); // reserved
45    enc.buf.extend_from_slice(&schema.id().to_le_bytes()); // 16-byte schema id
46    enc.buf.extend_from_slice(&0u32.to_le_bytes()); // root offset, patched below
47    let schema_len = if inline {
48        schema.canonical_bytes().len()
49    } else {
50        0
51    };
52    let schema_len_u32 = u32::try_from(schema_len).map_err(|_| Error::MessageTooLarge)?;
53    enc.buf.extend_from_slice(&schema_len_u32.to_le_bytes());
54    if inline {
55        enc.buf.extend_from_slice(schema.canonical_bytes());
56    }
57    enc.pad_to(8)?;
58    let root_off = enc.write_struct(schema.root_index(), root_fields)?;
59    enc.buf[24..28].copy_from_slice(&root_off.to_le_bytes());
60    Ok(enc.buf)
61}
62
63enum ElemKind {
64    Scalar,
65    Ref,
66    InlineStruct(u16),
67}
68
69fn elem_info(schema: &Schema, elem: &Type) -> (u32, u32, ElemKind) {
70    match elem {
71        Type::Struct(i) => match schema.layout_unchecked(*i) {
72            // Fixed-size struct elements are stored inline at a constant
73            // stride. Packed struct elements are variable-size, so they ride
74            // in the list by u32 offset (the Ref discipline) instead.
75            crate::layout::StructLayout::Fixed(f) => (f.size, f.align, ElemKind::InlineStruct(*i)),
76            crate::layout::StructLayout::Packed(_) => (4, 4, ElemKind::Ref),
77        },
78        Type::String | Type::Bytes | Type::List(_) | Type::Map(_, _) | Type::Union(_) => {
79            (4, 4, ElemKind::Ref)
80        }
81        other => {
82            let (size, align) = slot_size_align(other);
83            (size, align, ElemKind::Scalar)
84        }
85    }
86}
87
88/// Total canonical order over map keys of a single key type: integers by value
89/// (sign-aware), `bool` false < true, enums by their u32, strings by UTF-8
90/// bytes. The encoder sorts entries by this so one logical map has exactly one
91/// byte encoding, regardless of insertion order (crucial for languages whose
92/// maps iterate in an unspecified order).
93fn key_cmp(a: &Value, b: &Value) -> std::cmp::Ordering {
94    use std::cmp::Ordering::Equal;
95    use Value::*;
96    match (a, b) {
97        (Bool(x), Bool(y)) => x.cmp(y),
98        (U8(x), U8(y)) => x.cmp(y),
99        (U16(x), U16(y)) => x.cmp(y),
100        (U32(x), U32(y)) => x.cmp(y),
101        (U64(x), U64(y)) => x.cmp(y),
102        (I8(x), I8(y)) => x.cmp(y),
103        (I16(x), I16(y)) => x.cmp(y),
104        (I32(x), I32(y)) => x.cmp(y),
105        (I64(x), I64(y)) => x.cmp(y),
106        (Enum(x), Enum(y)) => x.cmp(y),
107        (Str(x), Str(y)) => x.as_bytes().cmp(y.as_bytes()),
108        // Mixed or non-key variants: leave order unspecified here; the per-entry
109        // type check in `write_map` rejects a key of the wrong type.
110        _ => Equal,
111    }
112}
113
114fn type_mismatch(schema: &Schema, expected: &Type, got: &Value) -> Error {
115    Error::TypeMismatch {
116        expected: expected.describe(schema),
117        got: got.kind().into(),
118    }
119}
120
121struct Encoder<'s> {
122    schema: &'s Schema,
123    buf: Vec<u8>,
124}
125
126impl<'s> Encoder<'s> {
127    fn pos(&self) -> Result<u32> {
128        u32::try_from(self.buf.len()).map_err(|_| Error::MessageTooLarge)
129    }
130
131    fn pad_to(&mut self, align: u32) -> Result<u32> {
132        let pos = self.pos()?;
133        let target = align_up(pos, align);
134        self.buf.resize(self.buf.len() + (target - pos) as usize, 0);
135        Ok(target)
136    }
137
138    fn patch_u32(&mut self, at: u32, v: u32) {
139        let at = at as usize;
140        self.buf[at..at + 4].copy_from_slice(&v.to_le_bytes());
141    }
142
143    /// Reserve a zeroed, aligned fixed struct block and return its offset.
144    fn alloc_struct_block(&mut self, type_index: u16) -> Result<u32> {
145        let lay = self.schema.layout_unchecked(type_index).as_fixed();
146        let (align, size) = (lay.align, lay.size);
147        let base = self.pad_to(align)?;
148        self.buf.resize(self.buf.len() + size as usize, 0);
149        self.pos()?; // enforce the 4 GiB cap after growing
150        Ok(base)
151    }
152
153    fn write_struct(&mut self, type_index: u16, values: &[(u16, Value)]) -> Result<u32> {
154        if self.schema.struct_def_unchecked(type_index).is_packed() {
155            return self.write_packed_struct(type_index, values);
156        }
157        let base = self.alloc_struct_block(type_index)?;
158        self.fill_struct_at(base, type_index, values)?;
159        Ok(base)
160    }
161
162    /// Fill an already-reserved struct block: presence bits and scalar slots
163    /// in place, heap children appended afterwards with their offsets patched
164    /// into the slots.
165    fn fill_struct_at(
166        &mut self,
167        base: u32,
168        type_index: u16,
169        values: &[(u16, Value)],
170    ) -> Result<()> {
171        let schema = self.schema;
172        let sd = schema.struct_def_unchecked(type_index);
173        let lay = schema.layout_unchecked(type_index).as_fixed();
174        let mut heap_jobs: Vec<(u32, &Value, &'s Type)> = Vec::new();
175        let mut seen = vec![false; sd.fields.len()];
176        for (id, value) in values {
177            let pos = sd
178                .fields
179                .binary_search_by_key(id, |f| f.id)
180                .map_err(|_| Error::UnknownFieldId(*id))?;
181            if seen[pos] {
182                return Err(Error::DuplicateField(*id));
183            }
184            seen[pos] = true;
185            if !sd.is_dense() {
186                let bit_at = (base + pos as u32 / 8) as usize;
187                self.buf[bit_at] |= 1 << (pos % 8);
188            }
189            let slot = base + lay.slots[pos];
190            let ty = &sd.fields[pos].ty;
191            match ty {
192                Type::String
193                | Type::Bytes
194                | Type::List(_)
195                | Type::Struct(_)
196                | Type::Map(_, _)
197                | Type::Union(_) => {
198                    // Cheap shape check now for a good error; full check when written.
199                    let ok = matches!(
200                        (ty, value),
201                        (Type::String, Value::Str(_))
202                            | (Type::Bytes, Value::Bytes(_))
203                            | (Type::List(_), Value::List(_))
204                            | (Type::List(_), Value::Scalars(_))
205                            | (Type::Struct(_), Value::Struct(_))
206                            | (Type::Map(_, _), Value::Map(_))
207                            | (Type::Union(_), Value::Union(..))
208                    );
209                    if !ok {
210                        return Err(type_mismatch(schema, ty, value));
211                    }
212                    heap_jobs.push((slot, value, ty));
213                }
214                _ => self.store_scalar_at(slot, value, ty)?,
215            }
216        }
217        if sd.is_dense() {
218            if let Some(pos) = seen.iter().position(|s| !s) {
219                return Err(Error::MissingField(sd.fields[pos].id));
220            }
221        }
222        for (slot, value, ty) in heap_jobs {
223            let off = self.write_heap(value, ty)?;
224            self.patch_u32(slot, off);
225        }
226        Ok(())
227    }
228
229    /// Encode a packed struct: presence bitmap, then present fields packed by
230    /// size class. Block size and slot offsets come from the popcount layout.
231    fn write_packed_struct(&mut self, type_index: u16, values: &[(u16, Value)]) -> Result<u32> {
232        let schema = self.schema;
233        let sd = schema.struct_def_unchecked(type_index);
234        let lay = schema.layout_unchecked(type_index).as_packed();
235
236        // Resolve ids to positions, detect duplicates, build the bitmap.
237        let mut bitmap = 0u64;
238        let mut present: Vec<(usize, &Value, &'s Type)> = Vec::with_capacity(values.len());
239        for (id, value) in values {
240            let pos = sd
241                .fields
242                .binary_search_by_key(id, |f| f.id)
243                .map_err(|_| Error::UnknownFieldId(*id))?;
244            let bit = 1u64 << pos;
245            if bitmap & bit != 0 {
246                return Err(Error::DuplicateField(*id));
247            }
248            bitmap |= bit;
249            present.push((pos, value, &sd.fields[pos].ty));
250        }
251
252        let size = lay.block_size(bitmap);
253        let base = self.pad_to(lay.align)?;
254        self.buf.resize(self.buf.len() + size as usize, 0);
255        self.pos()?;
256        // Write the presence bitmap (only the used low bytes).
257        let bmap_bytes = bitmap.to_le_bytes();
258        let bstart = base as usize;
259        self.buf[bstart..bstart + lay.bitmap_bytes as usize]
260            .copy_from_slice(&bmap_bytes[..lay.bitmap_bytes as usize]);
261
262        let mut heap_jobs: Vec<(u32, &Value, &'s Type)> = Vec::new();
263        for (pos, value, ty) in present {
264            let slot = base + lay.field_offset(bitmap, pos);
265            match ty {
266                Type::String
267                | Type::Bytes
268                | Type::List(_)
269                | Type::Struct(_)
270                | Type::Map(_, _)
271                | Type::Union(_) => {
272                    let ok = matches!(
273                        (ty, value),
274                        (Type::String, Value::Str(_))
275                            | (Type::Bytes, Value::Bytes(_))
276                            | (Type::List(_), Value::List(_))
277                            | (Type::List(_), Value::Scalars(_))
278                            | (Type::Struct(_), Value::Struct(_))
279                            | (Type::Map(_, _), Value::Map(_))
280                            | (Type::Union(_), Value::Union(..))
281                    );
282                    if !ok {
283                        return Err(type_mismatch(schema, ty, value));
284                    }
285                    heap_jobs.push((slot, value, ty));
286                }
287                _ => self.store_scalar_at(slot, value, ty)?,
288            }
289        }
290        for (slot, value, ty) in heap_jobs {
291            let off = self.write_heap(value, ty)?;
292            self.patch_u32(slot, off);
293        }
294        Ok(base)
295    }
296
297    /// Write a scalar into an existing (zeroed) slot.
298    fn store_scalar_at(&mut self, at: u32, value: &Value, ty: &Type) -> Result<()> {
299        let at = at as usize;
300        let buf = &mut self.buf;
301        macro_rules! put {
302            ($bytes:expr) => {{
303                let b = $bytes;
304                buf[at..at + b.len()].copy_from_slice(&b);
305            }};
306        }
307        match (ty, value) {
308            (Type::Bool, Value::Bool(x)) => buf[at] = *x as u8,
309            (Type::U8, Value::U8(x)) => buf[at] = *x,
310            (Type::U16, Value::U16(x)) => put!(x.to_le_bytes()),
311            (Type::U32, Value::U32(x)) => put!(x.to_le_bytes()),
312            (Type::U64, Value::U64(x)) => put!(x.to_le_bytes()),
313            (Type::I8, Value::I8(x)) => buf[at] = *x as u8,
314            (Type::I16, Value::I16(x)) => put!(x.to_le_bytes()),
315            (Type::I32, Value::I32(x)) => put!(x.to_le_bytes()),
316            (Type::I64, Value::I64(x)) => put!(x.to_le_bytes()),
317            (Type::F32, Value::F32(x)) => put!(x.to_le_bytes()),
318            (Type::F64, Value::F64(x)) => put!(x.to_le_bytes()),
319            (Type::Enum(_), Value::Enum(x)) => put!(x.to_le_bytes()),
320            _ => return Err(type_mismatch(self.schema, ty, value)),
321        }
322        Ok(())
323    }
324
325    /// Append a scalar list element at the current (aligned) position.
326    fn push_scalar(&mut self, value: &Value, ty: &Type) -> Result<()> {
327        let buf = &mut self.buf;
328        match (ty, value) {
329            (Type::Bool, Value::Bool(x)) => buf.push(*x as u8),
330            (Type::U8, Value::U8(x)) => buf.push(*x),
331            (Type::U16, Value::U16(x)) => buf.extend_from_slice(&x.to_le_bytes()),
332            (Type::U32, Value::U32(x)) => buf.extend_from_slice(&x.to_le_bytes()),
333            (Type::U64, Value::U64(x)) => buf.extend_from_slice(&x.to_le_bytes()),
334            (Type::I8, Value::I8(x)) => buf.push(*x as u8),
335            (Type::I16, Value::I16(x)) => buf.extend_from_slice(&x.to_le_bytes()),
336            (Type::I32, Value::I32(x)) => buf.extend_from_slice(&x.to_le_bytes()),
337            (Type::I64, Value::I64(x)) => buf.extend_from_slice(&x.to_le_bytes()),
338            (Type::F32, Value::F32(x)) => buf.extend_from_slice(&x.to_le_bytes()),
339            (Type::F64, Value::F64(x)) => buf.extend_from_slice(&x.to_le_bytes()),
340            (Type::Enum(_), Value::Enum(x)) => buf.extend_from_slice(&x.to_le_bytes()),
341            _ => return Err(type_mismatch(self.schema, ty, value)),
342        }
343        Ok(())
344    }
345
346    /// Write a heap object (string, bytes, list, struct block) and return its
347    /// absolute offset.
348    fn write_heap(&mut self, value: &Value, ty: &Type) -> Result<u32> {
349        match (ty, value) {
350            (Type::String, Value::Str(s)) => self.write_blob(s.as_bytes()),
351            (Type::Bytes, Value::Bytes(b)) => self.write_blob(b),
352            (Type::Struct(i), Value::Struct(fields)) => self.write_struct(*i, fields),
353            (Type::List(elem), Value::List(items)) => self.write_list(elem, items),
354            (Type::List(elem), Value::Scalars(runs)) => self.write_scalar_list(elem, runs),
355            (Type::Map(k, v), Value::Map(entries)) => self.write_map(k, v, entries),
356            (Type::Union(variants), Value::Union(tag, inner)) => {
357                self.write_union(variants, *tag, inner)
358            }
359            _ => Err(type_mismatch(self.schema, ty, value)),
360        }
361    }
362
363    /// Write a `union` value: `u32 tag`, then the selected variant's value at
364    /// its natural-aligned payload slot (scalar in place, or a u32 heap offset).
365    fn write_union(&mut self, variants: &[Type], tag: u32, inner: &Value) -> Result<u32> {
366        let vty = variants.get(tag as usize).ok_or(Error::BadUnionTag(tag))?;
367        let (pslot, palign) = slot_size_align(vty);
368        let payload_off = crate::layout::align_up(4, palign);
369        let block_align = palign.max(4);
370        let block_size = crate::layout::align_up(payload_off + pslot, block_align);
371        let base = self.pad_to(block_align)?;
372        self.buf.resize(self.buf.len() + block_size as usize, 0);
373        self.pos()?;
374        self.patch_u32(base, tag);
375        let slot = base + payload_off;
376        match vty {
377            Type::String
378            | Type::Bytes
379            | Type::List(_)
380            | Type::Struct(_)
381            | Type::Map(_, _)
382            | Type::Union(_) => {
383                let off = self.write_heap(inner, vty)?;
384                self.patch_u32(slot, off);
385            }
386            _ => self.store_scalar_at(slot, inner, vty)?,
387        }
388        Ok(base)
389    }
390
391    /// Write a `map<K, V>`: `count`, then `count` entry blocks (each a dense
392    /// 2-field {key, value} struct), entries sorted by key in canonical order.
393    /// Returns the map's absolute offset.
394    fn write_map(
395        &mut self,
396        key_ty: &Type,
397        val_ty: &Type,
398        entries: &[(Value, Value)],
399    ) -> Result<u32> {
400        let lay = crate::layout::map_entry_layout(key_ty, val_ty);
401        let (key_slot, val_slot) = (lay.slots[0], lay.slots[1]);
402
403        // Sort a view of the entries by key into canonical order (leaving the
404        // caller's Vec untouched), then reject duplicate keys.
405        let mut order: Vec<usize> = (0..entries.len()).collect();
406        order.sort_by(|&i, &j| key_cmp(&entries[i].0, &entries[j].0));
407        for w in order.windows(2) {
408            if key_cmp(&entries[w[0]].0, &entries[w[1]].0) == std::cmp::Ordering::Equal {
409                return Err(Error::DuplicateMapKey);
410            }
411        }
412
413        let count = u32::try_from(entries.len()).map_err(|_| Error::MessageTooLarge)?;
414        let off = self.pad_to(4)?;
415        self.buf.extend_from_slice(&count.to_le_bytes());
416        let base = self.pad_to(lay.align)?;
417        let total = (lay.size as usize)
418            .checked_mul(entries.len())
419            .ok_or(Error::MessageTooLarge)?;
420        self.buf.resize(self.buf.len() + total, 0);
421        self.pos()?;
422
423        // Two passes so all in-block scalars land before any heap object is
424        // appended (mirrors struct filling), keeping the entry region contiguous.
425        let mut heap_jobs: Vec<(u32, &Value, &Type)> = Vec::new();
426        for (rank, &i) in order.iter().enumerate() {
427            let entry_base = base + rank as u32 * lay.size;
428            for (slot_off, ty, val) in [
429                (key_slot, key_ty, &entries[i].0),
430                (val_slot, val_ty, &entries[i].1),
431            ] {
432                let slot = entry_base + slot_off;
433                match ty {
434                    Type::String
435                    | Type::Bytes
436                    | Type::List(_)
437                    | Type::Struct(_)
438                    | Type::Map(_, _) => {
439                        heap_jobs.push((slot, val, ty));
440                    }
441                    _ => self.store_scalar_at(slot, val, ty)?,
442                }
443            }
444        }
445        for (slot, val, ty) in heap_jobs {
446            let child = self.write_heap(val, ty)?;
447            self.patch_u32(slot, child);
448        }
449        Ok(off)
450    }
451
452    fn write_blob(&mut self, bytes: &[u8]) -> Result<u32> {
453        let len = u32::try_from(bytes.len()).map_err(|_| Error::MessageTooLarge)?;
454        let off = self.pad_to(4)?;
455        self.buf.extend_from_slice(&len.to_le_bytes());
456        self.buf.extend_from_slice(bytes);
457        self.pos()?;
458        Ok(off)
459    }
460
461    /// Write a `list<scalar>` from native values — the bulk peer of
462    /// [`write_list`](Self::write_list).
463    ///
464    /// Produces exactly the bytes `write_list` would for the equivalent
465    /// `Value::List`: `u32 count`, alignment padding, then the elements packed
466    /// at their natural stride. The difference is only that the caller did not
467    /// have to build a `Value` per element, and the run is appended in one pass
468    /// the optimiser can vectorize.
469    fn write_scalar_list(&mut self, elem: &Type, runs: &crate::value::Scalars) -> Result<u32> {
470        use crate::value::Scalars;
471        let count = u32::try_from(runs.len()).map_err(|_| Error::MessageTooLarge)?;
472        let (_, elem_align, kind) = elem_info(self.schema, elem);
473        if !matches!(kind, ElemKind::Scalar) {
474            return Err(type_mismatch(
475                self.schema,
476                elem,
477                &Value::Scalars(runs.clone()),
478            ));
479        }
480        let off = self.pad_to(4)?;
481        self.buf.extend_from_slice(&count.to_le_bytes());
482        self.pad_to(elem_align)?;
483
484        // Each arm must match `push_scalar`'s bytes for that (Type, Value) pair
485        // exactly — the golden vectors are the proof that it does.
486        macro_rules! run {
487            ($values:expr) => {{
488                self.buf
489                    .reserve($values.len() * std::mem::size_of_val(&$values[0]));
490                for x in $values {
491                    self.buf.extend_from_slice(&x.to_le_bytes());
492                }
493            }};
494        }
495        match (elem, runs) {
496            (Type::Bool, Scalars::Bool(v)) => {
497                self.buf.reserve(v.len());
498                for x in v {
499                    self.buf.push(*x as u8);
500                }
501            }
502            (Type::U8, Scalars::U8(v)) => self.buf.extend_from_slice(v),
503            (Type::I8, Scalars::I8(v)) => {
504                self.buf.reserve(v.len());
505                for x in v {
506                    self.buf.push(*x as u8);
507                }
508            }
509            (Type::U16, Scalars::U16(v)) => run!(v),
510            (Type::U32, Scalars::U32(v)) => run!(v),
511            (Type::U64, Scalars::U64(v)) => run!(v),
512            (Type::I16, Scalars::I16(v)) => run!(v),
513            (Type::I32, Scalars::I32(v)) => run!(v),
514            (Type::I64, Scalars::I64(v)) => run!(v),
515            (Type::F32, Scalars::F32(v)) => run!(v),
516            (Type::F64, Scalars::F64(v)) => run!(v),
517            _ => {
518                return Err(type_mismatch(
519                    self.schema,
520                    elem,
521                    &Value::Scalars(runs.clone()),
522                ))
523            }
524        }
525        self.pos()?;
526        Ok(off)
527    }
528
529    fn write_list(&mut self, elem: &Type, items: &[Value]) -> Result<u32> {
530        let schema = self.schema;
531        let (stride, elem_align, kind) = elem_info(schema, elem);
532        let count = u32::try_from(items.len()).map_err(|_| Error::MessageTooLarge)?;
533        let off = self.pad_to(4)?;
534        self.buf.extend_from_slice(&count.to_le_bytes());
535        self.pad_to(elem_align)?;
536        match kind {
537            ElemKind::Scalar => {
538                for item in items {
539                    self.push_scalar(item, elem)?;
540                }
541            }
542            ElemKind::Ref => {
543                let slots_base = self.pos()?;
544                self.buf.resize(
545                    self.buf.len() + items.len().checked_mul(4).ok_or(Error::MessageTooLarge)?,
546                    0,
547                );
548                self.pos()?;
549                for (i, item) in items.iter().enumerate() {
550                    let child = self.write_heap(item, elem)?;
551                    self.patch_u32(slots_base + (i as u32) * 4, child);
552                }
553            }
554            ElemKind::InlineStruct(type_index) => {
555                let base = self.pos()?;
556                let total = (stride as usize)
557                    .checked_mul(items.len())
558                    .ok_or(Error::MessageTooLarge)?;
559                self.buf.resize(self.buf.len() + total, 0);
560                self.pos()?;
561                for (i, item) in items.iter().enumerate() {
562                    let fields = match item {
563                        Value::Struct(fields) => fields,
564                        other => return Err(type_mismatch(schema, elem, other)),
565                    };
566                    self.fill_struct_at(base + (i as u32) * stride, type_index, fields)?;
567                }
568            }
569        }
570        self.pos()?;
571        Ok(off)
572    }
573}