Skip to main content

verit_core/
wire.rs

1//! Public low-level wire primitives for **generated code** (see
2//! [`crate::codegen`]). Everything here is bounds-checked and `unsafe`-free;
3//! generated readers/writers compose these with offsets computed at
4//! generation time from the deterministic layout algorithm.
5//!
6//! Nothing in this module is needed for normal (dynamic) use of the library.
7
8use crate::encode::{FLAG_INLINE_SCHEMA, HEADER_LEN, MESSAGE_MAGIC};
9use crate::error::{Error, Result};
10use crate::message::Budget;
11
12// ---------------------------------------------------------------------------
13// Traversal-budget helpers (the wire spec §5.2) for generated readers
14// ---------------------------------------------------------------------------
15
16/// Charge `bytes` against an optional traversal budget. `None` (the trusted,
17/// unbounded path) is a no-op the optimizer removes.
18#[inline]
19pub fn charge(budget: Option<&Budget>, bytes: u64) -> Result<()> {
20    match budget {
21        Some(b) => b.charge(bytes),
22        None => Ok(()),
23    }
24}
25
26/// [`read_str`] with the payload charged against the budget *before* it is
27/// touched, so a bounded read can never exceed its budget even transiently.
28pub fn read_str_budgeted<'b>(buf: &'b [u8], slot: u64, budget: Option<&Budget>) -> Result<&'b str> {
29    let off = read_u32(buf, slot)? as u64;
30    let len = read_u32(buf, off)? as u64;
31    charge(budget, 8 + len)?;
32    let bytes = read_slice(buf, off + 4, len)?;
33    std::str::from_utf8(bytes).map_err(|_| Error::BadUtf8)
34}
35
36/// [`read_bytes`] with the payload charged against the budget first.
37pub fn read_bytes_budgeted<'b>(
38    buf: &'b [u8],
39    slot: u64,
40    budget: Option<&Budget>,
41) -> Result<&'b [u8]> {
42    let off = read_u32(buf, slot)? as u64;
43    let len = read_u32(buf, off)? as u64;
44    charge(budget, 8 + len)?;
45    read_slice(buf, off + 4, len)
46}
47
48// ---------------------------------------------------------------------------
49// Write side
50// ---------------------------------------------------------------------------
51
52/// Start a message buffer: header, optional inline schema, padding to 8.
53pub fn message_header(
54    schema_id: u128,
55    inline_schema: Option<&[u8]>,
56    capacity_hint: usize,
57) -> Result<Vec<u8>> {
58    let mut buf = Vec::with_capacity(capacity_hint.max(HEADER_LEN));
59    buf.extend_from_slice(MESSAGE_MAGIC);
60    let flags: u16 = if inline_schema.is_some() {
61        FLAG_INLINE_SCHEMA
62    } else {
63        0
64    };
65    buf.extend_from_slice(&flags.to_le_bytes());
66    buf.extend_from_slice(&0u16.to_le_bytes()); // reserved
67    buf.extend_from_slice(&schema_id.to_le_bytes()); // 16-byte schema id
68    buf.extend_from_slice(&0u32.to_le_bytes()); // root offset, patched by finish
69    let schema_len = inline_schema.map(|s| s.len()).unwrap_or(0);
70    let schema_len = u32::try_from(schema_len).map_err(|_| Error::MessageTooLarge)?;
71    buf.extend_from_slice(&schema_len.to_le_bytes());
72    if let Some(s) = inline_schema {
73        buf.extend_from_slice(s);
74    }
75    while buf.len() % 8 != 0 {
76        buf.push(0);
77    }
78    Ok(buf)
79}
80
81/// Patch the root offset into the header.
82pub fn finish_message(buf: &mut [u8], root_offset: u32) {
83    buf[24..28].copy_from_slice(&root_offset.to_le_bytes());
84}
85
86pub fn pos(buf: &[u8]) -> Result<u32> {
87    u32::try_from(buf.len()).map_err(|_| Error::MessageTooLarge)
88}
89
90pub fn pad_to(buf: &mut Vec<u8>, align: u32) -> Result<u32> {
91    let p = pos(buf)?;
92    let target = (p as u64 + align as u64 - 1) & !(align as u64 - 1);
93    let target = u32::try_from(target).map_err(|_| Error::MessageTooLarge)?;
94    buf.resize(buf.len() + (target - p) as usize, 0);
95    Ok(target)
96}
97
98/// Reserve a zeroed, aligned block and return its absolute offset.
99pub fn alloc_block(buf: &mut Vec<u8>, size: u32, align: u32) -> Result<u32> {
100    let base = pad_to(buf, align)?;
101    buf.resize(buf.len() + size as usize, 0);
102    pos(buf)?;
103    Ok(base)
104}
105
106/// Reserve `n` zeroed bytes at the current position, returning their offset.
107pub fn alloc_bytes(buf: &mut Vec<u8>, n: usize) -> Result<u32> {
108    let base = pos(buf)?;
109    buf.resize(buf.len().checked_add(n).ok_or(Error::MessageTooLarge)?, 0);
110    pos(buf)?;
111    Ok(base)
112}
113
114/// Set presence bit `pos` in the bitmap at the start of a struct block.
115pub fn set_presence_bit(buf: &mut [u8], base: u32, bit: usize) -> Result<()> {
116    let at = base as usize + bit / 8;
117    let b = buf.get_mut(at).ok_or(Error::OutOfBounds)?;
118    *b |= 1 << (bit % 8);
119    Ok(())
120}
121
122fn put(buf: &mut [u8], at: u32, bytes: &[u8]) -> Result<()> {
123    let start = at as usize;
124    let end = start.checked_add(bytes.len()).ok_or(Error::OutOfBounds)?;
125    buf.get_mut(start..end)
126        .ok_or(Error::OutOfBounds)?
127        .copy_from_slice(bytes);
128    Ok(())
129}
130
131macro_rules! put_fns {
132    ($($name:ident: $ty:ty),* $(,)?) => {$(
133        pub fn $name(buf: &mut [u8], at: u32, v: $ty) -> Result<()> {
134            put(buf, at, &v.to_le_bytes())
135        }
136    )*};
137}
138put_fns!(put_u16: u16, put_u32: u32, put_u64: u64, put_i16: i16, put_i32: i32, put_i64: i64, put_f32: f32, put_f64: f64);
139
140pub fn put_u8(buf: &mut [u8], at: u32, v: u8) -> Result<()> {
141    put(buf, at, &[v])
142}
143
144pub fn put_i8(buf: &mut [u8], at: u32, v: i8) -> Result<()> {
145    put(buf, at, &[v as u8])
146}
147
148pub fn put_bool(buf: &mut [u8], at: u32, v: bool) -> Result<()> {
149    put(buf, at, &[v as u8])
150}
151
152pub fn patch_u32(buf: &mut [u8], at: u32, v: u32) -> Result<()> {
153    put_u32(buf, at, v)
154}
155
156/// Append a length-prefixed blob (string/bytes payload), returning its offset.
157pub fn write_blob(buf: &mut Vec<u8>, bytes: &[u8]) -> Result<u32> {
158    let len = u32::try_from(bytes.len()).map_err(|_| Error::MessageTooLarge)?;
159    let off = pad_to(buf, 4)?;
160    buf.extend_from_slice(&len.to_le_bytes());
161    buf.extend_from_slice(bytes);
162    pos(buf)?;
163    Ok(off)
164}
165
166/// Write a list header (count, then padding so elements start aligned).
167/// Returns the list offset to patch into the referencing slot. Elements
168/// (`count * stride` bytes) must be appended immediately after.
169pub fn begin_list(buf: &mut Vec<u8>, count: u32, elem_align: u32) -> Result<u32> {
170    let off = pad_to(buf, 4)?;
171    buf.extend_from_slice(&count.to_le_bytes());
172    pad_to(buf, elem_align)?;
173    Ok(off)
174}
175
176macro_rules! push_fns {
177    ($($name:ident: $ty:ty),* $(,)?) => {$(
178        pub fn $name(buf: &mut Vec<u8>, v: $ty) {
179            buf.extend_from_slice(&v.to_le_bytes());
180        }
181    )*};
182}
183push_fns!(push_u16: u16, push_u32: u32, push_u64: u64, push_i16: i16, push_i32: i32, push_i64: i64, push_f32: f32, push_f64: f64);
184
185pub fn push_u8(buf: &mut Vec<u8>, v: u8) {
186    buf.push(v);
187}
188
189pub fn push_i8(buf: &mut Vec<u8>, v: i8) {
190    buf.push(v as u8);
191}
192
193pub fn push_bool(buf: &mut Vec<u8>, v: bool) {
194    buf.push(v as u8);
195}
196
197// Bulk list-write fast path: append a whole slice of scalar elements in one
198// pass. The region is reserved once (a single `resize`, so no per-element
199// capacity check), then each element is written little-endian into its fixed
200// chunk. On a little-endian target the body lowers to a bulk copy — this is
201// what closes the element-at-a-time gap on large scalar lists. Produces exactly
202// the same bytes as pushing elements one at a time.
203macro_rules! push_slice_fns {
204    ($($name:ident: $ty:ty = $n:literal),* $(,)?) => {$(
205        pub fn $name(buf: &mut Vec<u8>, vals: &[$ty]) {
206            let start = buf.len();
207            buf.resize(start + vals.len() * $n, 0);
208            for (chunk, v) in buf[start..].chunks_exact_mut($n).zip(vals) {
209                chunk.copy_from_slice(&v.to_le_bytes());
210            }
211        }
212    )*};
213}
214push_slice_fns!(
215    push_u16_slice: u16 = 2, push_u32_slice: u32 = 4, push_u64_slice: u64 = 8,
216    push_i16_slice: i16 = 2, push_i32_slice: i32 = 4, push_i64_slice: i64 = 8,
217    push_f32_slice: f32 = 4, push_f64_slice: f64 = 8,
218);
219
220pub fn push_u8_slice(buf: &mut Vec<u8>, vals: &[u8]) {
221    buf.extend_from_slice(vals);
222}
223
224pub fn push_i8_slice(buf: &mut Vec<u8>, vals: &[i8]) {
225    let start = buf.len();
226    buf.resize(start + vals.len(), 0);
227    for (b, &v) in buf[start..].iter_mut().zip(vals) {
228        *b = v as u8;
229    }
230}
231
232pub fn push_bool_slice(buf: &mut Vec<u8>, vals: &[bool]) {
233    let start = buf.len();
234    buf.resize(start + vals.len(), 0);
235    for (b, &v) in buf[start..].iter_mut().zip(vals) {
236        *b = v as u8;
237    }
238}
239
240// ---------------------------------------------------------------------------
241// Read side
242// ---------------------------------------------------------------------------
243
244pub fn read_slice(buf: &[u8], at: u64, len: u64) -> Result<&[u8]> {
245    let start = usize::try_from(at).map_err(|_| Error::OutOfBounds)?;
246    let len = usize::try_from(len).map_err(|_| Error::OutOfBounds)?;
247    let end = start.checked_add(len).ok_or(Error::OutOfBounds)?;
248    buf.get(start..end).ok_or(Error::OutOfBounds)
249}
250
251macro_rules! read_fns {
252    ($($name:ident: $ty:ty = $n:literal),* $(,)?) => {$(
253        pub fn $name(buf: &[u8], at: u64) -> Result<$ty> {
254            Ok(<$ty>::from_le_bytes(read_slice(buf, at, $n)?.try_into().unwrap()))
255        }
256    )*};
257}
258read_fns!(
259    read_u16: u16 = 2, read_u32: u32 = 4, read_u64: u64 = 8,
260    read_i16: i16 = 2, read_i32: i32 = 4, read_i64: i64 = 8,
261    read_f32: f32 = 4, read_f64: f64 = 8,
262);
263
264pub fn read_u8(buf: &[u8], at: u64) -> Result<u8> {
265    Ok(read_slice(buf, at, 1)?[0])
266}
267
268pub fn read_i8(buf: &[u8], at: u64) -> Result<i8> {
269    Ok(read_u8(buf, at)? as i8)
270}
271
272pub fn read_bool(buf: &[u8], at: u64) -> Result<bool> {
273    Ok(read_u8(buf, at)? != 0)
274}
275
276/// Follow a u32 offset slot at `slot` to a length-prefixed UTF-8 string.
277pub fn read_str(buf: &[u8], slot: u64) -> Result<&str> {
278    let bytes = read_bytes(buf, slot)?;
279    std::str::from_utf8(bytes).map_err(|_| Error::BadUtf8)
280}
281
282/// Follow a u32 offset slot at `slot` to a length-prefixed blob.
283pub fn read_bytes(buf: &[u8], slot: u64) -> Result<&[u8]> {
284    let off = read_u32(buf, slot)? as u64;
285    let len = read_u32(buf, off)? as u64;
286    read_slice(buf, off + 4, len)
287}
288
289/// Follow a u32 offset slot at `slot` to a list header. Returns
290/// (elements base, count); elements are `stride` apart per the schema layout.
291pub fn list_header(buf: &[u8], slot: u64, elem_align: u32) -> Result<(u64, u32)> {
292    let off = read_u32(buf, slot)? as u64;
293    let count = read_u32(buf, off)?;
294    let a = elem_align as u64;
295    let elems = (off + 4 + a - 1) & !(a - 1);
296    Ok((elems, count))
297}