ridl_rt/flatbuffers.rs
1//! Shared reading and writing for the FlatBuffers payload encoding.
2//!
3//! This module is gated by the `flatbuffers` cargo feature. It holds the parts
4//! of a FlatBuffers codec that are the same for every payload type, so that a
5//! generated `Payload<FlatBuffers>` implementation carries only what its own
6//! shape decides: which fields it has, where the projection put them, and what
7//! its typl constraints are.
8//!
9//! It takes no dependency. The FlatBuffers runtime crate that ADR-0020
10//! decision 5 permits under this feature is not used here, and nothing in this
11//! module allocates. That decision's 2026-10-03 amendment excludes planus from
12//! the permission: no planus crate may be a dependency of this crate.
13//!
14//! # What is here, and what is not
15//!
16//! Reading: the byte-order reads, the vtable walk, and the string and vector
17//! headers — everything a generated `verify` needs to walk a buffer it does
18//! not trust. Writing: [`Builder`], which builds a buffer from the end of a
19//! caller's slice downwards.
20//!
21//! **This module decides no layout.** [`Builder::push_table`] is handed a
22//! slot, an offset and a table size and writes the bytes they describe; it
23//! does not choose them. Which slot a field takes is a fact of the
24//! projection, which the size bound and the two emitters all read. A field's
25//! offset inside its table and the table's size are the codec emitter's,
26//! because no other emitter can observe them — a `.fbs` schema states no
27//! offsets — and the bound charges enough alignment slack per slot to hold
28//! whatever order that emitter writes a table's fields in.
29//!
30//! [`Builder::push_offset_vector`] arrived with that emitter, for a vector of
31//! strings and of tables. A helper for a union has not been needed: a union's
32//! wrapper table and a non-table arm's box are both ordinary tables
33//! (ADR-0019 decisions 1 and 2), and [`Builder::push_table`] writes them.
34//!
35//! # Alignment, and why reads are safe at any alignment
36//!
37//! Every scalar is read by copying its bytes into an array and calling
38//! `from_le_bytes`, so no read dereferences an unaligned pointer and a buffer
39//! may arrive at any alignment, which is what a transport hands over. A
40//! generated `verify` therefore never produces `Malformed::Unaligned`.
41//!
42//! The writer still aligns what it writes, because a reader that is not this
43//! one — a `flatc`-generated accessor, or another language's runtime — does
44//! read in place.
45
46use core::mem::size_of;
47use core::str;
48
49use crate::payload::{EncodeError, Malformed};
50
51/// The size of a `uoffset_t`, of an `soffset_t`, and of a vector's or a
52/// string's length prefix.
53pub const OFFSET_SIZE: usize = 4;
54
55/// The size of a `voffset_t`, one vtable entry.
56pub const VOFFSET_SIZE: usize = 2;
57
58/// The bytes a vtable carries before its first entry: its own size, then the
59/// size of the table it describes.
60pub const VTABLE_HEADER: usize = 4;
61
62fn at(buf: &[u8], from: usize, len: usize) -> Result<&[u8], Malformed> {
63 buf.get(from..)
64 .and_then(|rest| rest.get(..len))
65 .ok_or(Malformed::OutOfBounds)
66}
67
68macro_rules! read_scalar {
69 ($name:ident, $ty:ty, $what:literal) => {
70 #[doc = concat!("Reads the little-endian `", $what, "` at `from`.")]
71 ///
72 /// The bytes are copied before they are read, so `from` needs no
73 /// alignment.
74 pub fn $name(buf: &[u8], from: usize) -> Result<$ty, Malformed> {
75 let mut bytes = [0u8; size_of::<$ty>()];
76 bytes.copy_from_slice(at(buf, from, size_of::<$ty>())?);
77 Ok(<$ty>::from_le_bytes(bytes))
78 }
79 };
80}
81
82read_scalar!(read_u8, u8, "u8");
83read_scalar!(read_i8, i8, "i8");
84read_scalar!(read_u16, u16, "u16");
85read_scalar!(read_i16, i16, "i16");
86read_scalar!(read_u32, u32, "u32");
87read_scalar!(read_i32, i32, "i32");
88read_scalar!(read_u64, u64, "u64");
89read_scalar!(read_i64, i64, "i64");
90read_scalar!(read_f32, f32, "f32");
91read_scalar!(read_f64, f64, "f64");
92
93/// Reads the FlatBuffers boolean at `from`: zero is false, anything else is
94/// true.
95pub fn read_bool(buf: &[u8], from: usize) -> Result<bool, Malformed> {
96 Ok(read_u8(buf, from)? != 0)
97}
98
99/// Follows the `uoffset_t` stored at `from` and returns the position it names.
100///
101/// A `uoffset_t` is unsigned and relative to its own position, so it always
102/// points forward.
103pub fn follow(buf: &[u8], from: usize) -> Result<usize, Malformed> {
104 let relative = u64::from(read_u32(buf, from)?);
105 let target = from as u64 + relative;
106 if target >= buf.len() as u64 {
107 return Err(Malformed::OutOfBounds);
108 }
109 Ok(target as usize)
110}
111
112/// The position of the root table: the `uoffset_t` at the start of the buffer.
113pub fn root(buf: &[u8]) -> Result<usize, Malformed> {
114 follow(buf, 0)
115}
116
117/// The position of field `slot` of the table at `table`, or `None` when the
118/// table does not carry it.
119///
120/// An absent field is not an error: a FlatBuffers table omits a field whose
121/// value equals its declared default, and a vtable shorter than `slot` omits
122/// every field from there on. Whether an absent field is legal for the type is
123/// the generated `verify`'s question, not this one's.
124///
125/// `width` is the field's size in bytes — four for an offset. The field must
126/// lie wholly inside the table the vtable describes, so a vtable that points a
127/// field across the table's end is rejected here rather than read from bytes
128/// belonging to another object.
129pub fn field(
130 buf: &[u8],
131 table: usize,
132 slot: u16,
133 width: usize,
134) -> Result<Option<usize>, Malformed> {
135 // The table's first field is a signed offset backwards to its vtable.
136 // The arithmetic is done in i64 so that it cannot wrap on any target.
137 let soffset = read_i32(buf, table)?;
138 let vtable = table as i64 - i64::from(soffset);
139 if vtable < 0 || vtable as u64 >= buf.len() as u64 {
140 return Err(Malformed::OutOfBounds);
141 }
142 let vtable = vtable as usize;
143
144 let vtable_bytes = usize::from(read_u16(buf, vtable)?);
145 if vtable_bytes < VTABLE_HEADER {
146 return Err(Malformed::OutOfBounds);
147 }
148 // The whole vtable must lie inside the buffer, so that a walk of it is
149 // bounded by the buffer rather than by its own declared size.
150 at(buf, vtable, vtable_bytes)?;
151 let table_bytes = usize::from(read_u16(buf, vtable + VOFFSET_SIZE)?);
152
153 let entry = vtable + VTABLE_HEADER + usize::from(slot) * VOFFSET_SIZE;
154 if entry + VOFFSET_SIZE > vtable + vtable_bytes {
155 return Ok(None);
156 }
157 let offset = usize::from(read_u16(buf, entry)?);
158 if offset == 0 {
159 return Ok(None);
160 }
161 // The field must lie wholly inside the table the vtable describes: one
162 // that starts inside it and ends past it would otherwise be read from
163 // whatever follows the table in the buffer.
164 if offset < OFFSET_SIZE || offset + width > table_bytes {
165 return Err(Malformed::OutOfBounds);
166 }
167 let position = table as u64 + offset as u64;
168 if position >= buf.len() as u64 {
169 return Err(Malformed::OutOfBounds);
170 }
171 Ok(Some(position as usize))
172}
173
174/// The string the `uoffset_t` at `from` names, checked for UTF-8.
175pub fn string(buf: &[u8], from: usize) -> Result<&str, Malformed> {
176 let start = follow(buf, from)?;
177 let len = read_u32(buf, start)? as usize;
178 let bytes = at(buf, start + OFFSET_SIZE, len)?;
179 // A FlatBuffers string carries a terminating zero after its bytes, so a
180 // reader can hand it to C. Accepting a buffer without one would let
181 // `verify` pass bytes a C consumer runs off the end of.
182 if at(buf, start + OFFSET_SIZE + len, 1)?[0] != 0 {
183 return Err(Malformed::OutOfBounds);
184 }
185 str::from_utf8(bytes).map_err(|_| Malformed::Utf8)
186}
187
188/// A vector's length and the position of its first element.
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190pub struct Vector {
191 /// The number of elements.
192 pub len: usize,
193 /// The position of element zero.
194 pub first: usize,
195}
196
197impl Vector {
198 /// The position of element `index`, which the caller has checked against
199 /// [`Vector::len`].
200 pub fn element(&self, index: usize, stride: usize) -> usize {
201 self.first + index * stride
202 }
203}
204
205/// The vector the `uoffset_t` at `from` names, whose elements are `stride`
206/// bytes each.
207///
208/// The whole element span is checked against the buffer here, so a walk over
209/// the elements is bounded by one check rather than by one per element.
210pub fn vector(buf: &[u8], from: usize, stride: usize) -> Result<Vector, Malformed> {
211 let start = follow(buf, from)?;
212 let len = read_u32(buf, start)? as usize;
213 let first = start + OFFSET_SIZE;
214 let span = len.checked_mul(stride).ok_or(Malformed::OutOfBounds)?;
215 at(buf, first, span)?;
216 Ok(Vector { len, first })
217}
218
219/// The position of an object in a buffer under construction, measured
220/// backwards from the end of the builder's slice.
221///
222/// It is not an offset into the finished buffer. A `Pos` is only meaningful to
223/// the [`Builder`] that produced it, and passing one to a different builder is
224/// a programming error that `debug_assert` catches.
225#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
226pub struct Pos(usize);
227
228/// A value written into a table field.
229#[derive(Clone, Copy, Debug, PartialEq)]
230pub enum Field {
231 /// A boolean, one byte.
232 Bool(bool),
233 /// An unsigned byte.
234 U8(u8),
235 /// A signed byte.
236 I8(i8),
237 /// An unsigned 16-bit integer.
238 U16(u16),
239 /// A signed 16-bit integer.
240 I16(i16),
241 /// An unsigned 32-bit integer.
242 U32(u32),
243 /// A signed 32-bit integer.
244 I32(i32),
245 /// An unsigned 64-bit integer.
246 U64(u64),
247 /// A signed 64-bit integer.
248 I64(i64),
249 /// A 32-bit float.
250 F32(f32),
251 /// A 64-bit float.
252 F64(f64),
253 /// A `uoffset_t` to an object already written: a string, a vector or a
254 /// table.
255 Offset(Pos),
256}
257
258impl Field {
259 /// The bytes this value occupies in a table.
260 pub fn size(&self) -> usize {
261 match self {
262 Field::Bool(_) | Field::U8(_) | Field::I8(_) => 1,
263 Field::U16(_) | Field::I16(_) => 2,
264 Field::U32(_) | Field::I32(_) | Field::F32(_) | Field::Offset(_) => 4,
265 Field::U64(_) | Field::I64(_) | Field::F64(_) => 8,
266 }
267 }
268}
269
270/// One field of a table, at the slot and offset the projection assigned it.
271#[derive(Clone, Copy, Debug, PartialEq)]
272pub struct TableField {
273 /// The vtable slot, which is the field's position in declaration order.
274 pub slot: u16,
275 /// The field's offset from the start of the table.
276 pub offset: u16,
277 /// The value.
278 pub value: Field,
279}
280
281/// Builds a FlatBuffers buffer from the end of a caller's slice downwards.
282///
283/// A FlatBuffers buffer is built back to front: a child is written before the
284/// parent that names it, and the root offset is written last and sits at the
285/// start of the finished buffer. The builder therefore fills the slice from
286/// its end, and [`Builder::finish`] returns the finished buffer as a subslice
287/// of the slice ending at its end — which is why `Encoded.bytes` is a subslice
288/// rather than a prefix.
289///
290/// Nothing here allocates. The caller's slice is the only storage, and a write
291/// that does not fit returns [`EncodeError::Capacity`] with the bytes the
292/// encoding needed so far.
293pub struct Builder<'a> {
294 out: &'a mut [u8],
295 used: usize,
296}
297
298impl<'a> Builder<'a> {
299 /// A builder over `out`.
300 pub fn new(out: &'a mut [u8]) -> Self {
301 Builder { out, used: 0 }
302 }
303
304 /// The bytes written so far, including alignment padding.
305 pub fn used(&self) -> usize {
306 self.used
307 }
308
309 /// Reserves `size` bytes for an object aligned to `align`, and returns its
310 /// position and the bytes to fill, in address order.
311 ///
312 /// The reserved bytes and any padding are zeroed, so a buffer the caller
313 /// handed over dirty does not leak its old contents into the encoding and
314 /// two encodes of one value produce the same bytes.
315 pub fn reserve(&mut self, size: usize, align: usize) -> Result<(Pos, &mut [u8]), EncodeError> {
316 self.reserve_skewed(size, align, 0)
317 }
318
319 /// As [`Builder::reserve`], but puts the byte `skew` bytes into the object
320 /// on the `align` boundary, rather than its first byte.
321 ///
322 /// A vector needs this: its elements carry the alignment and they sit
323 /// behind a four-byte length prefix. Aligning the prefix instead would
324 /// leave a vector of eight-byte elements on a four-byte boundary, which
325 /// this builder's own reads survive — they copy before they read — and a
326 /// reader that loads in place does not.
327 fn reserve_skewed(
328 &mut self,
329 size: usize,
330 align: usize,
331 skew: usize,
332 ) -> Result<(Pos, &mut [u8]), EncodeError> {
333 debug_assert!(align.is_power_of_two(), "an alignment is a power of two");
334 let capacity = self.out.len();
335 let unpadded = match self.used.checked_add(size) {
336 Some(n) => n,
337 None => {
338 return Err(EncodeError::Capacity {
339 needed: usize::MAX,
340 available: capacity,
341 })
342 }
343 };
344 // An object at position `p` starts at offset `total - p` of the
345 // finished buffer, and `finish` makes `total` a multiple of the
346 // buffer's alignment. Padding so that `p` is congruent to `skew`
347 // modulo `align` therefore puts the object's byte `skew` on an
348 // `align` boundary in the finished buffer.
349 let padding = (align + (skew % align) - (unpadded % align)) % align;
350 let needed = match unpadded.checked_add(padding) {
351 Some(n) => n,
352 None => {
353 return Err(EncodeError::Capacity {
354 needed: usize::MAX,
355 available: capacity,
356 })
357 }
358 };
359 if needed > capacity {
360 return Err(EncodeError::Capacity {
361 needed,
362 available: capacity,
363 });
364 }
365 self.used = needed;
366 let start = capacity - needed;
367 self.out[start..start + size + padding].fill(0);
368 Ok((Pos(needed), &mut self.out[start..start + size]))
369 }
370
371 fn delta(from: Pos, to: Pos) -> usize {
372 debug_assert!(
373 to < from,
374 "a uoffset points forward, so its target is written before it and by this builder"
375 );
376 from.0.saturating_sub(to.0)
377 }
378}
379
380macro_rules! push_scalar {
381 ($name:ident, $ty:ty, $what:literal) => {
382 #[doc = concat!("Writes a little-endian `", $what, "`, aligned to its own size.")]
383 pub fn $name(&mut self, value: $ty) -> Result<Pos, EncodeError> {
384 let (position, bytes) = self.reserve(size_of::<$ty>(), size_of::<$ty>())?;
385 bytes.copy_from_slice(&value.to_le_bytes());
386 Ok(position)
387 }
388 };
389}
390
391impl<'a> Builder<'a> {
392 push_scalar!(push_u8, u8, "u8");
393 push_scalar!(push_i8, i8, "i8");
394 push_scalar!(push_u16, u16, "u16");
395 push_scalar!(push_i16, i16, "i16");
396 push_scalar!(push_u32, u32, "u32");
397 push_scalar!(push_i32, i32, "i32");
398 push_scalar!(push_u64, u64, "u64");
399 push_scalar!(push_i64, i64, "i64");
400 push_scalar!(push_f32, f32, "f32");
401 push_scalar!(push_f64, f64, "f64");
402
403 /// Writes a `uoffset_t` naming `target`.
404 pub fn push_offset(&mut self, target: Pos) -> Result<Pos, EncodeError> {
405 let (position, bytes) = self.reserve(OFFSET_SIZE, OFFSET_SIZE)?;
406 let delta = Builder::delta(position, target) as u32;
407 bytes.copy_from_slice(&delta.to_le_bytes());
408 Ok(position)
409 }
410
411 /// Writes a string: its length, its bytes, and the terminating zero a
412 /// FlatBuffers string carries so that a reader can hand it to C.
413 pub fn push_string(&mut self, value: &str) -> Result<Pos, EncodeError> {
414 let len = value.len();
415 let size = OFFSET_SIZE + len + 1;
416 let (position, bytes) = self.reserve(size, OFFSET_SIZE)?;
417 bytes[..OFFSET_SIZE].copy_from_slice(&(len as u32).to_le_bytes());
418 bytes[OFFSET_SIZE..OFFSET_SIZE + len].copy_from_slice(value.as_bytes());
419 // The terminator is already zero: `reserve` zeroes what it hands back.
420 Ok(position)
421 }
422
423 /// Writes a vector of scalars that the caller has already encoded, each
424 /// `stride` bytes, in element order.
425 ///
426 /// When `stride` is eight the buffer's own alignment must be at least
427 /// eight, so [`Builder::finish`] is passed eight or more; otherwise the
428 /// elements are aligned within the buffer and the buffer is not.
429 pub fn push_vector(&mut self, elements: &[u8], stride: usize) -> Result<Pos, EncodeError> {
430 debug_assert!(stride > 0, "an element occupies at least one byte");
431 debug_assert!(
432 elements.len() % stride == 0,
433 "the element bytes are a whole number of elements"
434 );
435 let count = elements.len() / stride;
436 let size = OFFSET_SIZE + elements.len();
437 // The elements carry the alignment, and they start `OFFSET_SIZE`
438 // bytes into the object, behind the length prefix. For a stride of
439 // four or less the prefix's own alignment already covers them.
440 let (align, skew) = if stride > OFFSET_SIZE {
441 (stride, OFFSET_SIZE)
442 } else {
443 (OFFSET_SIZE, 0)
444 };
445 let (position, bytes) = self.reserve_skewed(size, align, skew)?;
446 bytes[..OFFSET_SIZE].copy_from_slice(&(count as u32).to_le_bytes());
447 bytes[OFFSET_SIZE..].copy_from_slice(elements);
448 Ok(position)
449 }
450
451 /// Writes a vector of `uoffset_t`s naming objects already written, in
452 /// element order.
453 ///
454 /// A vector of strings, of tables, or of anything else the projection
455 /// places out of line is written this way: the elements are pushed first,
456 /// and their positions are handed over here. Each offset is relative to
457 /// its own position in the vector, which is why the caller cannot compute
458 /// them itself — a [`Pos`] carries no arithmetic outside this module.
459 pub fn push_offset_vector(&mut self, targets: &[Pos]) -> Result<Pos, EncodeError> {
460 let capacity = self.out.len();
461 let count = targets.len();
462 let size = match count
463 .checked_add(1)
464 .and_then(|slots| slots.checked_mul(OFFSET_SIZE))
465 {
466 Some(size) => size,
467 None => {
468 return Err(EncodeError::Capacity {
469 needed: usize::MAX,
470 available: capacity,
471 })
472 }
473 };
474 let (position, bytes) = self.reserve(size, OFFSET_SIZE)?;
475 bytes[..OFFSET_SIZE].copy_from_slice(&(count as u32).to_le_bytes());
476 for (index, target) in targets.iter().enumerate() {
477 let at = OFFSET_SIZE * (index + 1);
478 // Element `index` sits `at` bytes into the object, and so that
479 // many bytes closer to the end of the buffer than the object's
480 // own position.
481 let here = Pos(position.0 - at);
482 let delta = Builder::delta(here, *target) as u32;
483 bytes[at..at + OFFSET_SIZE].copy_from_slice(&delta.to_le_bytes());
484 }
485 Ok(position)
486 }
487
488 /// Writes a table and the vtable that describes it.
489 ///
490 /// `size`, `align`, `slots` and each field's `offset` are the projection's
491 /// facts about the type, not this builder's: it writes the layout it is
492 /// given. `slots` is the number of vtable entries, which is the number of
493 /// fields the type declares, whether or not each one is present here.
494 ///
495 /// A field the caller leaves out of `fields` is absent from the buffer,
496 /// which is how a FlatBuffers table carries a field equal to its default.
497 pub fn push_table(
498 &mut self,
499 size: usize,
500 align: usize,
501 slots: u16,
502 fields: &[TableField],
503 ) -> Result<Pos, EncodeError> {
504 debug_assert!(
505 size >= OFFSET_SIZE,
506 "a table begins with the offset to its vtable"
507 );
508 // A vtable states its own size and its table's size as `u16`, so a
509 // table larger than `u16::MAX` is not representable. The projection
510 // guarantees it does not happen: a type whose table does not fit has
511 // no finite size bound, and the Rust backend refuses it at generation time
512 // with a diagnostic. This assertion catches a projection that stops
513 // upholding that.
514 debug_assert!(
515 size <= usize::from(u16::MAX),
516 "a table's size is stated in its vtable as a u16"
517 );
518 let table = {
519 let (table, bytes) = self.reserve(size, align)?;
520 for f in fields {
521 let from = usize::from(f.offset);
522 let width = f.value.size();
523 debug_assert!(
524 from >= OFFSET_SIZE && from + width <= size,
525 "a field lies inside the table and after the vtable offset"
526 );
527 let slot = &mut bytes[from..from + width];
528 match f.value {
529 Field::Bool(v) => slot.copy_from_slice(&u8::from(v).to_le_bytes()),
530 Field::U8(v) => slot.copy_from_slice(&v.to_le_bytes()),
531 Field::I8(v) => slot.copy_from_slice(&v.to_le_bytes()),
532 Field::U16(v) => slot.copy_from_slice(&v.to_le_bytes()),
533 Field::I16(v) => slot.copy_from_slice(&v.to_le_bytes()),
534 Field::U32(v) => slot.copy_from_slice(&v.to_le_bytes()),
535 Field::I32(v) => slot.copy_from_slice(&v.to_le_bytes()),
536 Field::U64(v) => slot.copy_from_slice(&v.to_le_bytes()),
537 Field::I64(v) => slot.copy_from_slice(&v.to_le_bytes()),
538 Field::F32(v) => slot.copy_from_slice(&v.to_le_bytes()),
539 Field::F64(v) => slot.copy_from_slice(&v.to_le_bytes()),
540 Field::Offset(target) => {
541 // The field's own position: the table starts at
542 // `table`, and a field `offset` bytes further along is
543 // that many bytes closer to the end of the buffer.
544 let here = Pos(table.0 - from);
545 let delta = Builder::delta(here, target) as u32;
546 slot.copy_from_slice(&delta.to_le_bytes());
547 }
548 }
549 }
550 table
551 };
552
553 let vtable_bytes = VTABLE_HEADER + usize::from(slots) * VOFFSET_SIZE;
554 debug_assert!(
555 vtable_bytes <= usize::from(u16::MAX),
556 "a vtable's size is stated in its first field as a u16"
557 );
558 let vtable = {
559 let (vtable, bytes) = self.reserve(vtable_bytes, VOFFSET_SIZE)?;
560 bytes[..VOFFSET_SIZE].copy_from_slice(&(vtable_bytes as u16).to_le_bytes());
561 bytes[VOFFSET_SIZE..VTABLE_HEADER].copy_from_slice(&(size as u16).to_le_bytes());
562 for f in fields {
563 debug_assert!(f.slot < slots, "a field's slot is one the vtable carries");
564 let entry = VTABLE_HEADER + usize::from(f.slot) * VOFFSET_SIZE;
565 bytes[entry..entry + VOFFSET_SIZE].copy_from_slice(&f.offset.to_le_bytes());
566 }
567 vtable
568 };
569
570 // The table names its vtable with a signed offset backwards. The
571 // vtable was written after the table, so it lies at a lower address
572 // and the offset is positive.
573 let soffset = (vtable.0 - table.0) as i32;
574 let start = self.out.len() - table.0;
575 self.out[start..start + OFFSET_SIZE].copy_from_slice(&soffset.to_le_bytes());
576 Ok(table)
577 }
578
579 /// Writes the root offset and returns the finished buffer: a subslice of
580 /// the caller's slice ending at its end.
581 ///
582 /// `align` is the buffer's alignment, the largest any object in it needs.
583 /// The finished buffer's length is a multiple of it, which is what makes
584 /// every object's own alignment hold once the root sits at offset zero.
585 pub fn finish(mut self, root: Pos, align: usize) -> Result<&'a [u8], EncodeError> {
586 let align = if align > OFFSET_SIZE {
587 align
588 } else {
589 OFFSET_SIZE
590 };
591 let position = {
592 let (position, bytes) = self.reserve(OFFSET_SIZE, align)?;
593 let delta = Builder::delta(position, root) as u32;
594 bytes.copy_from_slice(&delta.to_le_bytes());
595 position
596 };
597 let out: &'a [u8] = self.out;
598 Ok(&out[out.len() - position.0..])
599 }
600}