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}