graven/
rt.rs

1//! Support for code generated by the Graven compiler (and the hand-written code in
2//! `graven-schemas` that freezes its pattern). Items marked `#[doc(hidden)]` are called only
3//! by generated code; the rest ([`ListR`], [`VListR`], [`VStrList`], [`ListW`],
4//! [`StreamW`]) appear in generated signatures.
5//!
6//! All `unsafe` of the generated pattern lives here and in
7//! [`TableRef::str_unchecked`]: [`FromVerified::from_verified`], [`VListR::new`] and
8//! [`VStrList::new`]. Generated code has no `unsafe` blocks of its own beyond calling these
9//! fixed-contract functions, each item carrying `#[allow(unsafe_code)]` and a `// SAFETY:`
10//! comment that cites the invariant below.
11//!
12//! # Generated pattern (frozen by hand in `graven-schemas`)
13//!
14//! - **The caller charges the header.** [`FromTable::verify`] never charges its own header
15//!   (`2 + fixed_len`). The public inherent method `X::verify(&self, ctx)` is the "as root"
16//!   entry point: it charges `2 + fixed_len` and calls `FromTable::verify`; `X::verified` /
17//!   `X::verified_with` only call the inherent `X::verify` (they do not charge again). A
18//!   table/union field charges `2 + fixed_len` of the target and calls the child's
19//!   `FromTable::verify` (or the child's inherent `verify`, which does both); [`ListR::verify`]
20//!   charges the elements and calls `FromTable::verify` on each one. `[string]` / `[bytes]` go
21//!   through [`verify_strs`] / [`verify_bytes_list`]. The totals are those of spec §7.3.
22//! - **A single kind of view.** Every table generates `XVerified<'a>` with explicit accessors
23//!   (never `Deref`) and `reader()`; strings via `str_unchecked`, tables via `YVerified`,
24//!   table lists via [`VListR`], `[string]` via [`VStrList`], unions via an enum with an
25//!   `Unknown(u16)` that is not descended into.
26//! - **Every table can be a root.** `X::SCHEMA_ID`, `read`, `verified`, `verified_with`,
27//!   `from_bytes`, `XW::root`, `XW::to_bytes`, `XW::seal`.
28//!
29//! # Invariants for the code generator (codegen)
30//!
31//! The `*Verified` views skip UTF-8 validation, relying on the `verify()` that already
32//! ran. That is only sound if the generated code maintains, for every type `X`:
33//!
34//! 1. **Same edge, same type.** An `XVerified` is only created (via
35//!    [`FromVerified::from_verified`]) over a table that the root's `verify()` reached
36//!    successfully **along the same edge** (same field of the same parent table, same list
37//!    element) and **with the same reader type `X`**. Being "reachable" is not enough: the
38//!    same bytes can be pointed to by two edges as tables of different types, and `verify`
39//!    only validated the type of the edge it walked.
40//! 2. **`str_unchecked` mirrors `str`.** Every `str_unchecked(off)` call in a view
41//!    corresponds to a validating `str(off)` call, at the same offset, in that type's
42//!    `verify`. Likewise, every [`VStrList`] in a view corresponds to a [`verify_strs`] call
43//!    over the same list (same offset) in that type's `verify`; a `VStrList` is only sound
44//!    for lists verified by `verify_strs`.
45//! 3. **Nothing beyond what `verify` walks.** Views never follow edges that `verify` skips:
46//!    union tags unknown to the reader, fields beyond the reader's layout, reserved fields.
47//! 4. **`VListR<T>` with `verify`'s `T`.** [`VListR::new`] is only called with the same `T`
48//!    that `verify` used for that list.
49//! 5. **`verify` is `#[inline]`.** The generated `verify` functions carry `#[inline]` (its
50//!    absence once caused a performance regression in verified reads).
51//! 6. **Strings validated by `str()`.** `verify` MUST validate every string by calling
52//!    `str()` (which checks UTF-8), never by charging `bytes(off).len()` to the budget
53//!    without validating; otherwise item 2 no longer holds.
54//!
55//! Items 2 and 6 also hold for `[string]`: `verify` MUST call [`verify_strs`] (never
56//! [`verify_bytes_list`]) on every list that the view exposes as a [`VStrList`].
57//!
58//! The mutation test in `graven-schemas/tests/verified.rs`
59//! (`verified_matches_checked_under_mutation`) is the acceptance gate: for mutated messages
60//! that pass `verify`, every read through the verified view MUST equal the checked read.
61
62use crate::{Bit, Error, StrList, StreamListW, TableList, TableListW, TableRef, VerifyCtx, envelope};
63#[doc(hidden)]
64pub use alloc::vec::Vec;
65use core::marker::PhantomData;
66
67/// One required field of the reader's schema.
68#[doc(hidden)]
69pub enum Req {
70    /// Optional scalar: only checks the slot is not partially covered
71    /// (`docs/spec/graven-format-v1.md` §4.3: every inline slot gets §4.2 step 1).
72    Slot { off: u16, size: u16 },
73    Scalar { name: &'static str, off: u16, size: u16, bit: Bit },
74    Bool { name: &'static str, bit: Bit },
75    Ptr { name: &'static str, off: u16 },
76    /// Required union `[u16 tag][i32 ptr]`: present ⇔ slot inside `fixed_len`, `tag ≠ 0`
77    /// and `ptr ≠ 0` (spec §4.3).
78    Union { name: &'static str, off: u16 },
79}
80
81/// Validate that every required field (per the reader's schema) is present.
82#[doc(hidden)]
83#[inline]
84pub fn check_required(t: &TableRef<'_>, reqs: &[Req]) -> Result<(), Error> {
85    for r in reqs {
86        match *r {
87            Req::Slot { off, size } => {
88                t.slot_state(off, size)?;
89            }
90            Req::Scalar { name, off, size, bit } => {
91                if !t.slot_state(off, size)? || !t.bit(bit) {
92                    return Err(Error::Missing { field: name });
93                }
94            }
95            Req::Bool { name, bit } => {
96                if !t.bit(bit) {
97                    return Err(Error::Missing { field: name });
98                }
99            }
100            Req::Ptr { name, off } => {
101                // Presence only; the target is bounds-checked when accessed.
102                if !t.slot_state(off, 4)? || t.raw::<i32>(off) == 0 {
103                    return Err(Error::Missing { field: name });
104                }
105            }
106            Req::Union { name, off } => {
107                // Presence only; the target is bounds-checked when accessed.
108                if !t.slot_state(off, 6)? || t.raw::<u16>(off) == 0 || t.raw::<i32>(off + 2) == 0 {
109                    return Err(Error::Missing { field: name });
110                }
111            }
112        }
113    }
114    Ok(())
115}
116
117/// Precomputed fast path for `check_required` (`docs/spec/graven-format-v1.md` §4.3): when the writer's
118/// `fixed_len` covers the reader's whole layout, presence is one AND per bit
119/// byte plus a non-zero test per required pointer and union (tag and pointer).
120#[doc(hidden)]
121pub struct ReqMask {
122    pub len: u16,
123    pub bits: &'static [(u16, u8)],
124    pub ptrs: &'static [u16],
125    pub unions: &'static [u16],
126}
127
128/// Required-bit mask of `reqs` for bit byte `byte` (evaluated at compile time).
129#[doc(hidden)]
130pub const fn mask_for(reqs: &[Req], byte: u16) -> u8 {
131    let mut m = 0u8;
132    let mut i = 0;
133    while i < reqs.len() {
134        match reqs[i] {
135            Req::Scalar { bit, .. } | Req::Bool { bit, .. } if bit.byte == byte => m |= 1 << bit.shift,
136            _ => {}
137        }
138        i += 1;
139    }
140    m
141}
142
143#[doc(hidden)]
144#[inline(always)]
145pub fn check_required_fast(t: &TableRef<'_>, m: &ReqMask, reqs: &[Req]) -> Result<(), Error> {
146    if t.fixed_len() >= m.len {
147        let mut ok = true;
148        for &(b, mask) in m.bits {
149            ok &= t.fixed_byte(b) & mask == mask;
150        }
151        for &o in m.ptrs {
152            ok &= t.raw::<i32>(o) != 0;
153        }
154        for &o in m.unions {
155            ok &= t.raw::<u16>(o) != 0 && t.raw::<i32>(o + 2) != 0;
156        }
157        if ok {
158            return Ok(());
159        }
160    }
161    check_required(t, reqs)
162}
163
164/// Generated reader types.
165#[doc(hidden)]
166pub trait FromTable<'a>: Sized {
167    fn from_ref(t: TableRef<'a>) -> Result<Self, Error>;
168    /// Verifies everything below this table. Does **not** charge this table's own
169    /// header (`2 + fixed_len`): the caller does (root: the inherent `X::verify`; nested:
170    /// the parent; list element: [`ListR::verify`]).
171    fn verify(&self, ctx: &mut VerifyCtx) -> Result<(), Error>;
172}
173
174/// Typed list of tables; an absent list reads as empty.
175#[derive(Clone, Copy)]
176pub struct ListR<'a, T> {
177    inner: Option<TableList<'a>>,
178    _t: PhantomData<T>,
179}
180
181impl<'a, T: FromTable<'a>> ListR<'a, T> {
182    pub fn new(inner: Option<TableList<'a>>) -> Self {
183        ListR { inner, _t: PhantomData }
184    }
185    #[inline]
186    pub fn len(&self) -> usize {
187        self.inner.map_or(0, |l| l.len())
188    }
189    #[inline]
190    pub fn is_empty(&self) -> bool {
191        self.len() == 0
192    }
193    #[inline]
194    pub fn get(&self, i: usize) -> Result<T, Error> {
195        match self.inner {
196            Some(l) => T::from_ref(l.get(i)?),
197            None => Err(Error::OutOfBounds),
198        }
199    }
200    pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<T, Error>> + 'a
201    where
202        T: 'a,
203    {
204        let s = ListR::<'a, T> { inner: self.inner, _t: PhantomData };
205        (0..s.len()).map(move |i| s.get(i))
206    }
207    /// Charges the list (`6 + count × max(elem_fixed_len, 1)`, which covers the elements'
208    /// fixed parts), then verifies every element.
209    #[inline]
210    pub fn verify(&self, ctx: &mut VerifyCtx) -> Result<(), Error> {
211        if let Some(l) = self.inner {
212            // max(1): a zero-width element list must still consume budget per element.
213            let per = (l.elem_fixed_len() as usize).max(1);
214            ctx.charge(l.len().saturating_mul(per).saturating_add(6))?;
215        }
216        for i in 0..self.len() {
217            self.get(i)?.verify(ctx)?;
218        }
219        Ok(())
220    }
221}
222
223/// Generated element writer descriptor. All-scalar tables push a `FixedW`.
224#[doc(hidden)]
225pub trait Elem {
226    const FIXED_LEN: u16;
227    const PTR_SLOTS: &'static [u16];
228    type W<'x>;
229    fn push_known<'x>(l: &'x mut TableListW<'_>) -> Self::W<'x>;
230    fn push_stream<'x>(l: &'x mut StreamListW<'_, '_>) -> Self::W<'x>;
231}
232
233/// Known-count typed list writer.
234pub struct ListW<'a, E: Elem>(TableListW<'a>, PhantomData<E>);
235
236impl<'a, E: Elem> ListW<'a, E> {
237    pub fn new(l: TableListW<'a>) -> Self {
238        ListW(l, PhantomData)
239    }
240    #[inline]
241    pub fn push(&mut self) -> E::W<'_> {
242        E::push_known(&mut self.0)
243    }
244}
245
246/// Unknown-count typed list writer.
247pub struct StreamW<'a, 'b, E: Elem>(StreamListW<'a, 'b>, PhantomData<E>);
248
249impl<'a, 'b, E: Elem> StreamW<'a, 'b, E> {
250    pub fn new(l: StreamListW<'a, 'b>) -> Self {
251        StreamW(l, PhantomData)
252    }
253    #[inline]
254    pub fn push(&mut self) -> E::W<'_> {
255        E::push_stream(&mut self.0)
256    }
257    pub fn close(self) {
258        self.0.close()
259    }
260}
261
262/// Generated verified views (`XVerified<'a>`). Only the generated code calls this.
263#[doc(hidden)]
264pub trait FromVerified<'a>: Sized {
265    /// # Safety
266    /// Sound only if all of the following hold (see "Invariants for the code generator"
267    /// in the module docs):
268    ///
269    /// 1. `t` was reached by a root `verify()` that succeeded on the same (immutable)
270    ///    buffer, **along the same edge** (same field of the same parent table, same list
271    ///    element) and **interpreted as the same reader type `Self`**. Being "reachable"
272    ///    is not enough: the same bytes can be pointed to by two edges as two different
273    ///    table types, and verify only validated them as the type of the edge it followed.
274    /// 2. Every `str_unchecked(off)` call in `Self`'s accessors has a matching validating
275    ///    `str(off)` call, at the same offset, in the `verify` of the checked type `Self`
276    ///    wraps (and every [`VStrList`] a matching per-element [`StrList::get`]).
277    /// 3. `Self`'s accessors never follow an edge that verify skips: union arms with a tag
278    ///    unknown to the reader, fields beyond the reader's layout, reserved fields.
279    /// 4. Every [`VListR<T>`] that `Self` hands out is built with the `T` that verify used
280    ///    for that list.
281    #[allow(unsafe_code)]
282    unsafe fn from_verified(t: TableRef<'a>) -> Self;
283}
284
285/// Typed list of verified views; an absent list reads as empty. Only obtainable from a
286/// verified view, so its elements are verified too.
287#[derive(Clone, Copy)]
288pub struct VListR<'a, T> {
289    inner: Option<TableList<'a>>,
290    _t: PhantomData<T>,
291}
292
293impl<'a, T: FromVerified<'a>> VListR<'a, T> {
294    /// # Safety
295    /// `inner` must be a list that a root `verify()` succeeded on (same immutable buffer),
296    /// reached **along the same edge** that verify followed, and verify must have
297    /// verified its elements as **exactly this `T`'s checked type** (not merely some
298    /// table type at the same bytes). Every element view `T` then relies on the
299    /// [`FromVerified::from_verified`] contract (conditions 1–4), which this caller
300    /// vouches for on `T`'s behalf.
301    #[doc(hidden)]
302    #[allow(unsafe_code)]
303    #[inline]
304    pub unsafe fn new(inner: Option<TableList<'a>>) -> Self {
305        VListR { inner, _t: PhantomData }
306    }
307    #[inline]
308    pub fn len(&self) -> usize {
309        self.inner.map_or(0, |l| l.len())
310    }
311    #[inline]
312    pub fn is_empty(&self) -> bool {
313        self.len() == 0
314    }
315    #[allow(unsafe_code)]
316    #[inline]
317    pub fn get(&self, i: usize) -> Result<T, Error> {
318        match self.inner {
319            // SAFETY: `VListR::new`'s contract says this list was verified, along this
320            // edge, with elements of `T`'s checked type; `verify()` visits every element,
321            // so element `i` satisfies `from_verified`'s contract for `T`.
322            Some(l) => Ok(unsafe { T::from_verified(l.get(i)?) }),
323            None => Err(Error::OutOfBounds),
324        }
325    }
326    pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<T, Error>> + 'a
327    where
328        T: 'a,
329    {
330        let s = VListR::<'a, T> { inner: self.inner, _t: PhantomData };
331        (0..s.len()).map(move |i| s.get(i))
332    }
333}
334
335/// `[string]` inside a verified message: [`get`](Self::get) skips UTF-8 validation. An
336/// absent list reads as empty; a `0` element pointer reads as `""` (spec §5.6).
337#[derive(Debug, Clone, Copy)]
338pub struct VStrList<'a> {
339    inner: Option<StrList<'a>>,
340}
341
342impl<'a> VStrList<'a> {
343    /// # Safety
344    /// `inner` must be a list that a root `verify()` succeeded on (same immutable buffer),
345    /// reached **along the same edge** that verify followed, and verify must have passed
346    /// this very list to [`verify_strs`] (which validates every element as UTF-8);
347    /// a list checked only by [`verify_bytes_list`] does **not** qualify (invariants 1–3
348    /// and 6 of the module docs).
349    #[doc(hidden)]
350    #[allow(unsafe_code)]
351    #[inline]
352    pub unsafe fn new(inner: Option<StrList<'a>>) -> Self {
353        VStrList { inner }
354    }
355    #[inline]
356    pub fn len(&self) -> usize {
357        self.inner.map_or(0, |l| l.len())
358    }
359    #[inline]
360    pub fn is_empty(&self) -> bool {
361        self.len() == 0
362    }
363    /// Raw bytes of element `i`.
364    #[inline]
365    pub fn get_bytes(&self, i: usize) -> Result<&'a [u8], Error> {
366        match self.inner {
367            Some(l) => l.get_bytes(i),
368            None => Err(Error::OutOfBounds),
369        }
370    }
371    /// Element `i`, without re-validating UTF-8.
372    #[allow(unsafe_code)]
373    #[inline]
374    pub fn get(&self, i: usize) -> Result<&'a str, Error> {
375        let b = self.get_bytes(i)?;
376        // SAFETY: `VStrList::new`'s contract says verify passed this very list (same edge,
377        // same immutable buffer) to `verify_strs`, which validated every element as UTF-8.
378        Ok(unsafe { core::str::from_utf8_unchecked(b) })
379    }
380    pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<&'a str, Error>> + 'a {
381        let s = *self;
382        (0..s.len()).map(move |i| s.get(i))
383    }
384}
385
386/// Verify step for a `[string]` field (spec §7.3): charges `4 + 4 × count`, then for each
387/// element validates it as UTF-8 ([`StrList::get`]) and charges `4 + len`. An absent list
388/// charges nothing. The only verify step that makes a [`VStrList`] over the list sound.
389#[doc(hidden)]
390#[inline]
391pub fn verify_strs(l: Option<StrList<'_>>, ctx: &mut VerifyCtx) -> Result<(), Error> {
392    let Some(l) = l else { return Ok(()) };
393    ctx.charge(l.len().saturating_mul(4).saturating_add(4))?;
394    for i in 0..l.len() {
395        let s = l.get(i)?;
396        ctx.charge(4 + s.len())?;
397    }
398    Ok(())
399}
400
401/// Verify step for a `[bytes]` field (spec §7.3): same charges as [`verify_strs`]
402/// (`4 + 4 × count`, then `4 + len` per element), without UTF-8 validation.
403#[doc(hidden)]
404#[inline]
405pub fn verify_bytes_list(l: Option<StrList<'_>>, ctx: &mut VerifyCtx) -> Result<(), Error> {
406    let Some(l) = l else { return Ok(()) };
407    ctx.charge(l.len().saturating_mul(4).saturating_add(4))?;
408    for i in 0..l.len() {
409        let b = l.get_bytes(i)?;
410        ctx.charge(4 + b.len())?;
411    }
412    Ok(())
413}
414
415/// Envelope step of the generated `from_bytes`: decode and check the root's schema id,
416/// returning the payload.
417#[doc(hidden)]
418#[inline]
419pub fn open_envelope(buf: &[u8], expected: u64) -> Result<&[u8], Error> {
420    let env = envelope::decode(buf)?;
421    if env.schema_id != expected {
422        return Err(Error::SchemaMismatch { expected, found: env.schema_id });
423    }
424    Ok(env.payload)
425}