graven/
write.rs

1//! Forward-only writing into pooled `BytesMut`.
2
3use crate::pool::BufPool;
4use alloc::vec::Vec;
5use crate::envelope::{self, HEADER_LEN};
6use crate::{Bit, Error, Le};
7use bytes::{Buf, Bytes, BytesMut};
8
9/// Owns one pooled buffer. Build a message with `root()`, then:
10/// - `finished()`: borrow it until the next `root()` (no atomics);
11/// - `seal()` … `take_frames()`: batch length-prefixed frames into one `Bytes`
12///   (one refcount per batch — the fast path for senders);
13/// - `flush()`: one owned `Bytes` per message (one refcount per message);
14/// - `finish()`: like `flush()`, and the buffer goes back to the pool.
15///
16/// Every message is written after a 4-byte slot that `seal()` fills with its
17/// length, so frames are `[u32 len][message]` with no extra copy.
18///
19/// A builder made with [`Builder::new_enveloped`] reserves a
20/// [`HEADER_LEN`]-byte slot instead, which `flush_envelope()` /
21/// `seal_envelope()` fill with an envelope header (the CRC is appended after
22/// the message) — again without copying the payload. `finished()` / `flush()`
23/// still return the bare message in either mode; `seal()` only works in the
24/// default mode and `seal_envelope()` / `flush_envelope()` only in the
25/// enveloped one (otherwise: panic "builder mode mismatch").
26pub struct Builder<'p> {
27    pool: &'p dyn BufPool,
28    buf: Option<BytesMut>,
29    /// Aux buffers for streaming lists, reused across lists and messages.
30    stash: Vec<BytesMut>,
31    chunk: usize,
32    /// End of the sealed frames at the front of `buf`.
33    sealed: usize,
34    /// Start of the current (unsealed) message; `None` when there is none.
35    msg_start: Option<usize>,
36    /// Bytes reserved before each message: 4 (frame length) or `HEADER_LEN` (envelope).
37    prefix: usize,
38}
39
40const FRAME_PREFIX: usize = 4;
41
42/// Start a message after the sealed frames: drop any unsealed message, top up
43/// the chunk when no frames are pending, write the length slot.
44#[inline]
45fn begin_msg(main: &mut BytesMut, sealed: usize, chunk: usize, prefix: usize) -> usize {
46    main.truncate(sealed);
47    // Reserve a fresh chunk only when running low and nothing is pending: by
48    // now earlier messages have usually been dropped, so `reserve` reclaims
49    // the old allocation instead of allocating.
50    if sealed == 0 && main.capacity() < chunk / 4 {
51        main.reserve(chunk);
52    }
53    // The default (frame) mode runs exactly the old code plus one predictable
54    // compare; the envelope mode widens the slot out of line.
55    main.extend_from_slice(&[0; FRAME_PREFIX]);
56    if prefix != FRAME_PREFIX {
57        widen_slot(main);
58    }
59    sealed + prefix
60}
61
62#[cold]
63#[inline(never)]
64fn widen_slot(main: &mut BytesMut) {
65    main.extend_from_slice(&[0; HEADER_LEN - FRAME_PREFIX]);
66}
67
68#[cold]
69#[inline(never)]
70#[track_caller]
71fn mode_mismatch() -> ! {
72    panic!("builder mode mismatch")
73}
74
75impl<'p> Builder<'p> {
76    #[inline]
77    pub fn new(pool: &'p dyn BufPool) -> Self {
78        Self::with_prefix(pool, FRAME_PREFIX)
79    }
80
81    /// A builder whose messages can be emitted as envelopes
82    /// ([`crate::envelope`]) with `flush_envelope()` / `seal_envelope()`
83    /// without copying: a `HEADER_LEN`-byte slot is reserved before each one.
84    #[inline]
85    pub fn new_enveloped(pool: &'p dyn BufPool) -> Self {
86        Self::with_prefix(pool, HEADER_LEN)
87    }
88
89    #[inline]
90    fn with_prefix(pool: &'p dyn BufPool, prefix: usize) -> Self {
91        Builder {
92            pool,
93            buf: Some(pool.take()),
94            stash: Vec::new(),
95            chunk: pool.chunk_size(),
96            sealed: 0,
97            msg_start: None,
98            prefix,
99        }
100    }
101
102    /// Start the root table of a new message (an unsealed previous message is discarded).
103    #[inline]
104    pub fn root(&mut self, fixed_len: u16) -> TableW<'_> {
105        let main = self.buf.as_mut().expect("builder already finished");
106        let ms = begin_msg(main, self.sealed, self.chunk, self.prefix);
107        self.msg_start = Some(ms);
108        let start = alloc_table(main, fixed_len);
109        TableW { main, aux: None, start, pool: self.pool, stash: &mut self.stash }
110    }
111
112    /// Root table with only scalar fields (no pointers) and compile-time size `N`.
113    #[inline]
114    pub fn root_fixed<const N: usize>(&mut self) -> FixedW<'_, N> {
115        let main = self.buf.as_mut().expect("builder already finished");
116        let ms = begin_msg(main, self.sealed, self.chunk, self.prefix);
117        self.msg_start = Some(ms);
118        let start = alloc_table(main, N as u16);
119        FixedW::new(&mut main[start..start + N])
120    }
121
122    #[inline]
123    fn current(&self) -> &[u8] {
124        let buf = self.buf.as_ref().expect("builder already finished");
125        match self.msg_start {
126            Some(ms) => &buf[ms..],
127            None => &[],
128        }
129    }
130
131    /// Borrow the current message without freezing it (no refcount traffic).
132    /// Valid until the next `root()` — the equivalent of FlatBuffers'
133    /// `finished_data()`.
134    #[inline]
135    pub fn finished(&self) -> Result<&[u8], Error> {
136        let m = self.current();
137        if m.len() > i32::MAX as usize {
138            return Err(Error::TooLarge);
139        }
140        Ok(m)
141    }
142
143    /// Seal the current message as a frame `[u32 len][message]` in the
144    /// pending batch. No atomics; a no-op when there is no current message.
145    #[inline]
146    pub fn seal(&mut self) -> Result<(), Error> {
147        if self.prefix != FRAME_PREFIX {
148            mode_mismatch();
149        }
150        let Some(ms) = self.msg_start.take() else { return Ok(()) };
151        let buf = self.buf.as_mut().expect("builder already finished");
152        let len = buf.len() - ms;
153        if len > i32::MAX as usize {
154            buf.truncate(self.sealed);
155            return Err(Error::TooLarge);
156        }
157        buf[ms - 4..ms].copy_from_slice(&(len as u32).to_le_bytes());
158        self.sealed = buf.len();
159        Ok(())
160    }
161
162    /// Bytes of sealed frames pending in the batch (to ship by size).
163    #[inline]
164    pub fn sealed_len(&self) -> usize {
165        self.sealed
166    }
167
168    /// Freeze every sealed frame as one `Bytes` (one refcount for the whole
169    /// batch) and start an empty batch. An unsealed message is discarded.
170    /// Split the result back into messages with [`crate::frames`].
171    #[inline]
172    pub fn take_frames(&mut self) -> Bytes {
173        let buf = self.buf.as_mut().expect("builder already finished");
174        buf.truncate(self.sealed);
175        self.sealed = 0;
176        self.msg_start = None;
177        buf.split().freeze()
178    }
179
180    /// Freeze the current message alone (without its length slot) and keep
181    /// the builder. One refcount per message. Panics if sealed frames are
182    /// pending (call `take_frames` first).
183    #[inline]
184    pub fn flush(&mut self) -> Result<Bytes, Error> {
185        assert!(self.sealed == 0, "flush: sealed frames pending; call take_frames first");
186        let buf = self.buf.as_mut().expect("builder already finished");
187        let Some(ms) = self.msg_start.take() else {
188            buf.clear();
189            return Ok(Bytes::new());
190        };
191        if buf.len() - ms > i32::MAX as usize {
192            buf.clear();
193            return Err(Error::TooLarge);
194        }
195        let mut msg = buf.split().freeze();
196        msg.advance(ms); // skip the unused length slot: pointer bump, no atomics
197        Ok(msg)
198    }
199
200    /// Write the envelope header into the slot before the message at `ms` and
201    /// append the CRC. `buf` must end at the end of the message.
202    #[inline]
203    fn close_envelope(buf: &mut BytesMut, ms: usize, schema_id: u64) {
204        let len = buf.len() - ms;
205        buf[ms - HEADER_LEN..ms].copy_from_slice(&envelope::header(schema_id, len as u32));
206        let crc = crate::crc32c(&buf[ms - HEADER_LEN..]);
207        buf.extend_from_slice(&crc.to_le_bytes());
208    }
209
210    /// Seal the current message as an envelope in the pending batch, so
211    /// `take_frames()` returns concatenated envelopes (split them with
212    /// [`envelope::envelopes`]). A no-op when there is no current message.
213    /// Panics with "builder mode mismatch" unless built with `new_enveloped`.
214    #[inline]
215    pub fn seal_envelope(&mut self, schema_id: u64) -> Result<(), Error> {
216        if self.prefix != HEADER_LEN {
217            mode_mismatch();
218        }
219        let Some(ms) = self.msg_start.take() else { return Ok(()) };
220        let buf = self.buf.as_mut().expect("builder already finished");
221        if buf.len() - ms > i32::MAX as usize {
222            buf.truncate(self.sealed);
223            return Err(Error::TooLarge);
224        }
225        Self::close_envelope(buf, ms, schema_id);
226        self.sealed = buf.len();
227        Ok(())
228    }
229
230    /// Freeze the current message as one envelope (header + message + CRC)
231    /// and keep the builder; the payload is not copied. Returns an empty
232    /// `Bytes` when there is no current message. Panics with "builder mode
233    /// mismatch" unless built with `new_enveloped`, and if sealed frames are
234    /// pending (call `take_frames` first).
235    #[inline]
236    pub fn flush_envelope(&mut self, schema_id: u64) -> Result<Bytes, Error> {
237        if self.prefix != HEADER_LEN {
238            mode_mismatch();
239        }
240        assert!(self.sealed == 0, "flush_envelope: sealed frames pending; call take_frames first");
241        let buf = self.buf.as_mut().expect("builder already finished");
242        let Some(ms) = self.msg_start.take() else {
243            buf.clear();
244            return Ok(Bytes::new());
245        };
246        if buf.len() - ms > i32::MAX as usize {
247            buf.clear();
248            return Err(Error::TooLarge);
249        }
250        Self::close_envelope(buf, ms, schema_id);
251        let mut env = buf.split().freeze();
252        env.advance(ms - HEADER_LEN); // 0 today (sealed == 0); kept symmetric with `flush`
253        Ok(env)
254    }
255
256    /// Freeze the message; the buffer goes back to the pool.
257    #[inline]
258    pub fn finish(mut self) -> Result<Bytes, Error> {
259        self.flush()
260    }
261}
262
263impl Drop for Builder<'_> {
264    fn drop(&mut self) {
265        if let Some(b) = self.buf.take() {
266            self.pool.give(b);
267        }
268        for b in self.stash.drain(..) {
269            self.pool.give(b);
270        }
271    }
272}
273
274/// Append `[u16 fixed_len][zeros]`; returns the fixed part start.
275#[inline]
276fn alloc_table(main: &mut BytesMut, fixed_len: u16) -> usize {
277    let pos = main.len();
278    main.resize(pos + 2 + fixed_len as usize, 0);
279    main[pos..pos + 2].copy_from_slice(&fixed_len.to_le_bytes());
280    pos + 2
281}
282
283/// Bytes of `n` elements of `efl` bytes each; panics beyond `i32::MAX` (checked
284/// in `u64`, so it cannot wrap on 32-bit targets) before anything is allocated.
285#[inline]
286fn list_size(n: u32, efl: u64) -> usize {
287    let size = n as u64 * efl;
288    assert!(size <= i32::MAX as u64, "list too large: {n} × {efl} bytes exceeds i32::MAX");
289    size as usize
290}
291
292/// `first + size` without `usize` overflow (on 32-bit targets, `main` near 2 GiB plus a list
293/// of up to `i32::MAX` bytes would exceed `usize::MAX`). Same programmer-error panic.
294#[inline]
295fn list_end(first: usize, size: usize) -> usize {
296    first.checked_add(size).unwrap_or_else(|| panic!("list too large: buffer offset {first} + {size} bytes overflows usize"))
297}
298
299#[inline]
300fn put<T: Le>(main: &mut BytesMut, v: T) {
301    let mut a = [0u8; 16];
302    v.write_le(&mut a[..T::SIZE]);
303    main.extend_from_slice(&a[..T::SIZE]);
304}
305
306/// Writer for a table whose fields are all scalars (no pointers). The fixed
307/// part is a `[u8; N]` known at compile time, so field writes with constant
308/// offsets compile without bounds checks.
309pub struct FixedW<'a, const N: usize> {
310    fixed: &'a mut [u8; N],
311}
312
313impl<'a, const N: usize> FixedW<'a, N> {
314    #[inline(always)]
315    fn new(s: &'a mut [u8]) -> Self {
316        FixedW { fixed: s.try_into().expect("FixedW: element size mismatch (writer bug)") }
317    }
318
319    #[inline(always)]
320    pub fn set<T: Le>(&mut self, off: u16, presence: Bit, v: T) {
321        let i = off as usize;
322        v.write_le(&mut self.fixed[i..i + T::SIZE]);
323        self.fixed[presence.byte as usize] |= 1 << presence.shift;
324    }
325
326    #[inline(always)]
327    pub fn set_bool(&mut self, presence: Bit, v: bool) {
328        let b = &mut self.fixed[presence.byte as usize];
329        *b |= 1 << presence.shift;
330        if v {
331            *b |= 1 << (presence.shift + 1);
332        } else {
333            *b &= !(1 << (presence.shift + 1));
334        }
335    }
336}
337
338/// Writer for one table. The fixed part lives in `aux` when it is a streaming
339/// list element, otherwise in `main`; variable data always goes to `main`.
340///
341/// # Panics
342///
343/// Writer panics are programmer errors, never a consequence of data read from
344/// outside: pushing more than the `n` elements reserved for a list, reserving a
345/// list larger than `i32::MAX` bytes (2 GiB), or using a `Builder` after
346/// `finish`.
347pub struct TableW<'b> {
348    main: &'b mut BytesMut,
349    aux: Option<&'b mut BytesMut>,
350    start: usize,
351    pool: &'b dyn BufPool,
352    stash: &'b mut Vec<BytesMut>,
353}
354
355impl<'b> TableW<'b> {
356    #[inline]
357    fn fixed_buf(&mut self) -> &mut BytesMut {
358        match self.aux.as_deref_mut() {
359            Some(a) => a,
360            None => self.main,
361        }
362    }
363
364    #[inline]
365    fn set_bit(&mut self, b: Bit) {
366        let i = self.start + b.byte as usize;
367        self.fixed_buf()[i] |= 1 << b.shift;
368    }
369
370    #[inline]
371    fn clear_bit(&mut self, b: Bit) {
372        let i = self.start + b.byte as usize;
373        self.fixed_buf()[i] &= !(1 << b.shift);
374    }
375
376    /// Store pointer at slot `off` targeting absolute `target` in `main`.
377    /// In an aux buffer (streaming list element) the slot temporarily holds
378    /// `target + 1` (never 0) and is converted to a relative offset on close.
379    #[inline]
380    fn patch(&mut self, off: u16, target: usize) {
381        let slot = self.start + off as usize;
382        let v = if self.aux.is_some() { (target as i64 + 1) as i32 } else { (target as i64 - slot as i64) as i32 };
383        v.write_le(&mut self.fixed_buf()[slot..slot + 4]);
384    }
385
386    #[inline]
387    pub fn set<T: Le>(&mut self, off: u16, presence: Bit, v: T) {
388        let i = self.start + off as usize;
389        v.write_le(&mut self.fixed_buf()[i..i + T::SIZE]);
390        self.set_bit(presence);
391    }
392
393    #[inline]
394    pub fn set_bool(&mut self, presence: Bit, v: bool) {
395        self.set_bit(presence);
396        let value = Bit { byte: presence.byte, shift: presence.shift + 1 };
397        if v { self.set_bit(value) } else { self.clear_bit(value) }
398    }
399
400    #[inline]
401    pub fn set_bytes(&mut self, off: u16, b: &[u8]) {
402        let pos = self.main.len();
403        self.main.reserve(4 + b.len());
404        put(self.main, b.len() as u32);
405        self.main.extend_from_slice(b);
406        self.patch(off, pos);
407    }
408
409    #[inline]
410    pub fn set_str(&mut self, off: u16, s: &str) {
411        self.set_bytes(off, s.as_bytes());
412    }
413
414    pub fn set_scalars<T: Le>(&mut self, off: u16, v: &[T]) {
415        let pos = self.main.len();
416        let n = 4 + v.len() * T::SIZE;
417        self.main.resize(pos + n, 0);
418        (v.len() as u32).write_le(&mut self.main[pos..pos + 4]);
419        for (chunk, x) in self.main[pos + 4..pos + n].chunks_exact_mut(T::SIZE).zip(v) {
420            x.write_le(chunk);
421        }
422        self.patch(off, pos);
423    }
424
425    /// `[string]`: same layout as [`set_bytes_list`](Self::set_bytes_list).
426    pub fn set_strs<'s>(&mut self, off: u16, v: impl ExactSizeIterator<Item = &'s str>) {
427        self.set_bytes_list(off, v.map(str::as_bytes));
428    }
429
430    /// `[bytes]`: `[u32 count][count × i32 offset]`, then each `[u32 len][bytes]` in order.
431    pub fn set_bytes_list<'s>(&mut self, off: u16, v: impl ExactSizeIterator<Item = &'s [u8]>) {
432        let pos = self.main.len();
433        let n = v.len();
434        self.main.resize(pos + 4 + 4 * n, 0);
435        (n as u32).write_le(&mut self.main[pos..pos + 4]);
436        for (i, b) in v.enumerate() {
437            let slot = pos + 4 + 4 * i;
438            let target = self.main.len();
439            put(self.main, b.len() as u32);
440            self.main.extend_from_slice(b);
441            ((target - slot) as i32).write_le(&mut self.main[slot..slot + 4]);
442        }
443        self.patch(off, pos);
444    }
445
446    /// Nested table appended to `main`.
447    pub fn table(&mut self, off: u16, fixed_len: u16) -> TableW<'_> {
448        let pos = self.main.len();
449        let start = alloc_table(self.main, fixed_len);
450        self.patch(off, pos);
451        TableW { main: &mut *self.main, aux: None, start, pool: self.pool, stash: &mut *self.stash }
452    }
453
454    /// Union slot `[u16 tag][i32 ptr]` with a nested table variant.
455    pub fn union(&mut self, off: u16, tag: u16, fixed_len: u16) -> TableW<'_> {
456        let i = self.start + off as usize;
457        tag.write_le(&mut self.fixed_buf()[i..i + 2]);
458        self.table(off + 2, fixed_len)
459    }
460
461    /// Nested all-scalar table of compile-time size `N` (same bytes as
462    /// [`table`](Self::table)`(off, N)`), written through a `FixedW`.
463    #[inline]
464    pub fn table_fixed<const N: usize>(&mut self, off: u16) -> FixedW<'_, N> {
465        let pos = self.main.len();
466        let start = alloc_table(self.main, N as u16);
467        self.patch(off, pos);
468        FixedW::new(&mut self.main[start..start + N])
469    }
470
471    /// Union slot with an all-scalar variant of compile-time size `N` (same bytes as
472    /// [`union`](Self::union)`(off, tag, N)`).
473    #[inline]
474    pub fn union_fixed<const N: usize>(&mut self, off: u16, tag: u16) -> FixedW<'_, N> {
475        let i = self.start + off as usize;
476        tag.write_le(&mut self.fixed_buf()[i..i + 2]);
477        self.table_fixed(off + 2)
478    }
479
480    /// List of `n` tables, fixed parts reserved contiguously in `main`.
481    pub fn tables(&mut self, off: u16, n: u32, elem_fixed_len: u16) -> TableListW<'_> {
482        let size = list_size(n, elem_fixed_len as u64);
483        let pos = self.main.len();
484        let first = pos + 6;
485        self.main.resize(list_end(first, size), 0);
486        elem_fixed_len.write_le(&mut self.main[pos + 4..pos + 6]);
487        self.patch(off, pos);
488        TableListW {
489            main: &mut *self.main,
490            pool: self.pool,
491            stash: &mut *self.stash,
492            pos,
493            first,
494            efl: elem_fixed_len,
495            cap: n,
496            count: 0,
497        }
498    }
499
500    /// List of `n` all-scalar tables of compile-time size `N`. The element
501    /// region is split into `[u8; N]` chunks once, so `push` is an iterator
502    /// step with no per-element slicing or bounds checks.
503    #[inline]
504    pub fn tables_fixed<const N: usize>(&mut self, off: u16, n: u32) -> FixedListW<'_, N> {
505        let size = list_size(n, N as u64);
506        let pos = self.main.len();
507        let first = pos + 6;
508        let end = list_end(first, size);
509        self.main.resize(end, 0);
510        (N as u16).write_le(&mut self.main[pos + 4..pos + 6]);
511        self.patch(off, pos);
512        let (head, region) = self.main[pos..end].split_at_mut(6);
513        let (chunks, _) = region.as_chunks_mut::<N>();
514        FixedListW { count_slot: (&mut head[..4]).try_into().unwrap(), elems: chunks.iter_mut(), count: 0 }
515    }
516
517    /// List of tables of unknown length. Element fixed parts are built in a
518    /// pooled aux buffer and copied on close; `ptr_slots` lists every i32
519    /// pointer offset inside an element's fixed part (fixed up on close).
520    pub fn tables_streaming(&mut self, off: u16, elem_fixed_len: u16, ptr_slots: &'static [u16]) -> StreamListW<'_, 'b> {
521        let aux = match self.stash.pop() {
522            Some(a) => a,
523            None => self.pool.take(),
524        };
525        StreamListW { parent: self, off, aux, efl: elem_fixed_len, ptr_slots, count: 0, closed: false }
526    }
527
528    /// Scalar list of unknown length appended directly to `main`.
529    pub fn scalars_streaming<T: Le>(&mut self, off: u16) -> ScalarListW<'_, T> {
530        let pos = self.main.len();
531        self.main.resize(pos + 4, 0);
532        self.patch(off, pos);
533        ScalarListW { main: &mut *self.main, pos, count: 0, _t: core::marker::PhantomData }
534    }
535}
536
537/// Known-count list writer. On drop the count is set to the number pushed.
538pub struct TableListW<'a> {
539    main: &'a mut BytesMut,
540    pool: &'a dyn BufPool,
541    stash: &'a mut Vec<BytesMut>,
542    pos: usize,
543    first: usize,
544    efl: u16,
545    cap: u32,
546    count: u32,
547}
548
549impl TableListW<'_> {
550    /// Next element. Panics if more than the reserved `n` are pushed (writer bug).
551    #[inline]
552    pub fn push(&mut self) -> TableW<'_> {
553        assert!(self.count < self.cap, "TableListW: pushed more elements than reserved");
554        let start = self.first + self.count as usize * self.efl as usize;
555        self.count += 1;
556        TableW { main: &mut *self.main, aux: None, start, pool: self.pool, stash: &mut *self.stash }
557    }
558}
559
560impl TableListW<'_> {
561    /// Next element as an all-scalar `FixedW` (`N` must equal `elem_fixed_len`).
562    #[inline]
563    pub fn push_fixed<const N: usize>(&mut self) -> FixedW<'_, N> {
564        assert!(self.count < self.cap, "TableListW: pushed more elements than reserved");
565        let start = self.first + self.count as usize * self.efl as usize;
566        self.count += 1;
567        FixedW::new(&mut self.main[start..start + N])
568    }
569}
570
571impl Drop for TableListW<'_> {
572    #[inline]
573    fn drop(&mut self) {
574        let p = self.pos;
575        self.count.write_le(&mut self.main[p..p + 4]);
576    }
577}
578
579/// Known-count list of all-scalar elements (see `TableW::tables_fixed`).
580/// On drop the count is set to the number pushed.
581pub struct FixedListW<'a, const N: usize> {
582    count_slot: &'a mut [u8; 4],
583    elems: core::slice::IterMut<'a, [u8; N]>,
584    count: u32,
585}
586
587impl<const N: usize> FixedListW<'_, N> {
588    /// Next element. Panics if more than the reserved `n` are pushed (writer bug).
589    #[inline(always)]
590    pub fn push(&mut self) -> FixedW<'_, N> {
591        let fixed = self.elems.next().expect("FixedListW: pushed more elements than reserved");
592        self.count += 1;
593        FixedW { fixed }
594    }
595}
596
597impl<const N: usize> Drop for FixedListW<'_, N> {
598    #[inline]
599    fn drop(&mut self) {
600        *self.count_slot = self.count.to_le_bytes();
601    }
602}
603
604/// Unknown-count table list writer; finalized by `close()` or on drop.
605pub struct StreamListW<'a, 'b> {
606    parent: &'a mut TableW<'b>,
607    off: u16,
608    aux: BytesMut,
609    efl: u16,
610    ptr_slots: &'static [u16],
611    count: u32,
612    closed: bool,
613}
614
615impl<'b> StreamListW<'_, 'b> {
616    #[inline]
617    pub fn push(&mut self) -> TableW<'_> {
618        let start = self.aux.len();
619        self.aux.resize(start + self.efl as usize, 0);
620        self.count += 1;
621        TableW {
622            main: &mut *self.parent.main,
623            aux: Some(&mut self.aux),
624            start,
625            pool: self.parent.pool,
626            stash: &mut *self.parent.stash,
627        }
628    }
629
630    /// Next element as an all-scalar `FixedW` (`N` must equal `elem_fixed_len`).
631    #[inline]
632    pub fn push_fixed<const N: usize>(&mut self) -> FixedW<'_, N> {
633        let start = self.aux.len();
634        self.aux.resize(start + self.efl as usize, 0);
635        self.count += 1;
636        FixedW::new(&mut self.aux[start..start + N])
637    }
638
639    pub fn close(mut self) {
640        self.finalize();
641    }
642
643    fn finalize(&mut self) {
644        if self.closed {
645            return;
646        }
647        self.closed = true;
648        let main = &mut *self.parent.main;
649        let header = main.len();
650        put(main, self.count);
651        put(main, self.efl);
652        let base = main.len();
653        main.extend_from_slice(&self.aux);
654        let efl = self.efl as usize;
655        for i in 0..self.count as usize {
656            for &slot in self.ptr_slots {
657                let p = base + i * efl + slot as usize;
658                let stored = i32::read_le(&main[p..p + 4]);
659                if stored != 0 {
660                    let rel = (stored as i64 - 1) - p as i64;
661                    (rel as i32).write_le(&mut main[p..p + 4]);
662                }
663            }
664        }
665        self.parent.patch(self.off, header);
666        let mut aux = core::mem::take(&mut self.aux);
667        aux.clear();
668        self.parent.stash.push(aux);
669    }
670}
671
672impl Drop for StreamListW<'_, '_> {
673    fn drop(&mut self) {
674        self.finalize();
675    }
676}
677
678/// Unknown-count scalar list writer; count is written on drop.
679pub struct ScalarListW<'a, T: Le> {
680    main: &'a mut BytesMut,
681    pos: usize,
682    count: u32,
683    _t: core::marker::PhantomData<T>,
684}
685
686impl<T: Le> ScalarListW<'_, T> {
687    #[inline]
688    pub fn push(&mut self, v: T) {
689        put(self.main, v);
690        self.count += 1;
691    }
692}
693
694impl<T: Le> Drop for ScalarListW<'_, T> {
695    #[inline]
696    fn drop(&mut self) {
697        let p = self.pos;
698        self.count.write_le(&mut self.main[p..p + 4]);
699    }
700}
701
702#[cfg(all(test, feature = "std"))]
703mod tests {
704    use super::*;
705    use crate::pool::{BufPool, SimplePool};
706    use crate::{Bit, TableRef};
707    use bytes::BytesMut;
708    use core::cell::Cell;
709
710    const B0: Bit = Bit { byte: 0, shift: 0 };
711    const B1: Bit = Bit { byte: 0, shift: 1 };
712    const BB: Bit = Bit { byte: 0, shift: 2 }; // bool: bits 2,3
713
714    // layout: [bits @0][u64 @1][i16 @9][ptr str @11][ptr bytes @15][ptr table @19][union @23] = 29
715    const FL: u16 = 29;
716
717    #[test]
718    fn roundtrip_scalars_bool_str_bytes() {
719        let pool = SimplePool::new(64);
720        let mut b = Builder::new(&pool);
721        {
722            let mut t = b.root(FL);
723            t.set::<u64>(1, B0, 0xDEAD_BEEF_0000_0001);
724            t.set::<i16>(9, B1, -7);
725            t.set_bool(BB, true);
726            t.set_str(11, "olá");
727            t.set_bytes(15, &[1, 2, 3]);
728        }
729        let msg = b.finish().unwrap();
730        let t = TableRef::at(&msg, 0).unwrap();
731        assert_eq!(t.fixed_len(), FL);
732        assert_eq!(t.scalar::<u64>(1, B0).unwrap(), Some(0xDEAD_BEEF_0000_0001));
733        assert_eq!(t.scalar::<i16>(9, B1).unwrap(), Some(-7));
734        assert_eq!(t.bool(BB), Some(true));
735        assert_eq!(t.str(11).unwrap(), Some("olá"));
736        assert_eq!(t.bytes(15).unwrap(), Some(&[1u8, 2, 3][..]));
737        assert!(t.table(19).unwrap().is_none());
738        assert!(t.union(23).unwrap().is_none());
739    }
740
741    #[test]
742    fn bool_false_is_present() {
743        let pool = SimplePool::new(64);
744        let mut b = Builder::new(&pool);
745        b.root(FL).set_bool(BB, false);
746        let msg = b.finish().unwrap();
747        assert_eq!(TableRef::at(&msg, 0).unwrap().bool(BB), Some(false));
748    }
749
750    #[test]
751    fn nested_table_and_union_roundtrip() {
752        let pool = SimplePool::new(64);
753        let mut b = Builder::new(&pool);
754        {
755            let mut t = b.root(FL);
756            {
757                let mut c = t.table(19, 5);
758                c.set::<u32>(1, B0, 77);
759            }
760            t.set::<u64>(1, B0, 5);
761            let mut u = t.union(23, 3, 5);
762            u.set::<u32>(1, B0, 99);
763        }
764        let msg = b.finish().unwrap();
765        let t = TableRef::at(&msg, 0).unwrap();
766        assert_eq!(t.scalar::<u64>(1, B0).unwrap(), Some(5));
767        let c = t.table(19).unwrap().unwrap();
768        assert_eq!(c.fixed_len(), 5);
769        assert_eq!(c.scalar::<u32>(1, B0).unwrap(), Some(77));
770        let (tag, u) = t.union(23).unwrap().unwrap();
771        assert_eq!(tag, 3);
772        assert_eq!(u.scalar::<u32>(1, B0).unwrap(), Some(99));
773    }
774
775    #[test]
776    fn empty_string_present_vs_absent() {
777        let pool = SimplePool::new(64);
778        let mut b = Builder::new(&pool);
779        {
780            let mut t = b.root(FL);
781            t.set_str(11, "");
782            t.set_scalars::<u32>(19, &[]);
783        }
784        let msg = b.finish().unwrap();
785        let t = TableRef::at(&msg, 0).unwrap();
786        assert_eq!(t.str(11).unwrap(), Some(""));
787        assert_eq!(t.bytes(15).unwrap(), None);
788        assert_eq!(t.scalars::<u32>(19).unwrap().unwrap().len(), 0);
789    }
790
791    #[test]
792    fn scalars_and_strs_roundtrip() {
793        let pool = SimplePool::new(64);
794        let mut b = Builder::new(&pool);
795        {
796            let mut t = b.root(FL);
797            t.set_scalars::<u16>(11, &[1, 2, 3]);
798            t.set_strs(15, ["a", "", "ccc"].into_iter());
799        }
800        let msg = b.finish().unwrap();
801        let t = TableRef::at(&msg, 0).unwrap();
802        let s = t.scalars::<u16>(11).unwrap().unwrap();
803        assert_eq!(s.iter().collect::<Vec<_>>(), [1, 2, 3]);
804        let l = t.strs(15).unwrap().unwrap();
805        assert_eq!((l.get(0), l.get(1), l.get(2)), (Ok("a"), Ok(""), Ok("ccc")));
806    }
807
808    #[test]
809    fn old_bytes_survive_pool_reuse() {
810        let pool = SimplePool::new(64);
811        let mut b = Builder::new(&pool);
812        b.root(FL).set_str(11, "first");
813        let m1 = b.finish().unwrap();
814        let snapshot = m1.to_vec();
815        let mut b = Builder::new(&pool);
816        b.root(FL).set_str(11, "SECOND-LONGER-MESSAGE");
817        let m2 = b.finish().unwrap();
818        assert_eq!(&m1[..], &snapshot[..]);
819        assert_eq!(TableRef::at(&m1, 0).unwrap().str(11).unwrap(), Some("first"));
820        assert_eq!(TableRef::at(&m2, 0).unwrap().str(11).unwrap(), Some("SECOND-LONGER-MESSAGE"));
821    }
822
823    pub(crate) struct CountingPool {
824        pub takes: Cell<usize>,
825        pub gives: Cell<usize>,
826    }
827    impl CountingPool {
828        pub fn new() -> Self {
829            CountingPool { takes: Cell::new(0), gives: Cell::new(0) }
830        }
831    }
832    impl BufPool for CountingPool {
833        fn take(&self) -> BytesMut {
834            self.takes.set(self.takes.get() + 1);
835            BytesMut::with_capacity(64)
836        }
837        fn give(&self, _b: BytesMut) {
838            self.gives.set(self.gives.get() + 1);
839        }
840    }
841
842    #[test]
843    fn dropped_builder_returns_buffer() {
844        let pool = CountingPool::new();
845        {
846            let mut b = Builder::new(&pool);
847            b.root(FL).set::<u64>(1, B0, 1);
848        }
849        assert_eq!(pool.takes.get(), 1);
850        assert_eq!(pool.gives.get(), 1);
851        let b = Builder::new(&pool);
852        let _ = b.finish();
853        assert_eq!(pool.gives.get(), 2);
854    }
855
856    // ---- Task 4: lists of tables ----
857    // element layout: [bits @0][u32 v @1][ptr memo @5][ptr child @9][union @13][ptr inner list @19] = 23
858    const EFL: u16 = 23;
859    const EPTRS: &[u16] = &[5, 9, 15, 19];
860
861    fn read_elem(e: TableRef<'_>) -> (u32, Option<String>) {
862        (e.scalar::<u32>(1, B0).unwrap().unwrap(), e.str(5).unwrap().map(String::from))
863    }
864
865    #[test]
866    fn known_count_list_roundtrip() {
867        let pool = SimplePool::new(64);
868        let mut b = Builder::new(&pool);
869        {
870            let mut t = b.root(FL);
871            let mut l = t.tables(11, 3, EFL);
872            for i in 0..3u32 {
873                let mut e = l.push();
874                e.set::<u32>(1, B0, i);
875                e.set_str(5, &format!("m{i}"));
876            }
877        }
878        let msg = b.finish().unwrap();
879        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
880        let got: Vec<_> = l.iter().map(|e| read_elem(e.unwrap())).collect();
881        assert_eq!(got, [(0, Some("m0".into())), (1, Some("m1".into())), (2, Some("m2".into()))]);
882    }
883
884    #[test]
885    fn known_count_closed_short_fixes_count() {
886        let pool = SimplePool::new(64);
887        let mut b = Builder::new(&pool);
888        {
889            let mut t = b.root(FL);
890            let mut l = t.tables(11, 5, EFL);
891            l.push().set::<u32>(1, B0, 1);
892            l.push().set::<u32>(1, B0, 2);
893        }
894        let msg = b.finish().unwrap();
895        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
896        assert_eq!(l.len(), 2);
897        assert_eq!(read_elem(l.get(1).unwrap()).0, 2);
898    }
899
900    #[test]
901    fn streaming_list_with_strings_offsets_fixed() {
902        let pool = SimplePool::new(64);
903        let mut b = Builder::new(&pool);
904        {
905            let mut t = b.root(FL);
906            t.set::<u64>(1, B0, 42);
907            let mut l = t.tables_streaming(11, EFL, EPTRS);
908            for i in 0..10u32 {
909                let mut e = l.push();
910                e.set::<u32>(1, B0, i * 10);
911                if i % 3 != 0 {
912                    e.set_str(5, &format!("memo-{i}"));
913                }
914            }
915            l.close();
916            t.set_str(15, "later");
917        }
918        let msg = b.finish().unwrap();
919        let t = TableRef::at(&msg, 0).unwrap();
920        assert_eq!(t.str(15).unwrap(), Some("later"));
921        let l = t.tables(11).unwrap().unwrap();
922        assert_eq!(l.len(), 10);
923        for (i, e) in l.iter().enumerate() {
924            let (v, memo) = read_elem(e.unwrap());
925            assert_eq!(v, i as u32 * 10);
926            let want = if i % 3 != 0 { Some(format!("memo-{i}")) } else { None };
927            assert_eq!(memo, want);
928        }
929    }
930
931    #[test]
932    fn streaming_elem_with_nested_table_and_union() {
933        let pool = SimplePool::new(64);
934        let mut b = Builder::new(&pool);
935        {
936            let mut t = b.root(FL);
937            let mut l = t.tables_streaming(11, EFL, EPTRS);
938            for i in 0..4u32 {
939                let mut e = l.push();
940                e.set_str(5, "m");
941                e.table(9, 5).set::<u32>(1, B0, 100 + i);
942                e.union(13, 2, 5).set::<u32>(1, B0, 200 + i);
943            }
944        }
945        let msg = b.finish().unwrap();
946        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
947        for (i, e) in l.iter().enumerate() {
948            let e = e.unwrap();
949            assert_eq!(e.str(5).unwrap(), Some("m"));
950            assert_eq!(e.table(9).unwrap().unwrap().scalar::<u32>(1, B0).unwrap(), Some(100 + i as u32));
951            let (tag, u) = e.union(13).unwrap().unwrap();
952            assert_eq!(tag, 2);
953            assert_eq!(u.scalar::<u32>(1, B0).unwrap(), Some(200 + i as u32));
954        }
955    }
956
957    #[test]
958    fn streaming_inside_streaming() {
959        let pool = SimplePool::new(64);
960        let mut b = Builder::new(&pool);
961        {
962            let mut t = b.root(FL);
963            let mut outer = t.tables_streaming(11, EFL, EPTRS);
964            for i in 0..3u32 {
965                let mut e = outer.push();
966                e.set::<u32>(1, B0, i);
967                let mut inner = e.tables_streaming(19, EFL, EPTRS);
968                for j in 0..=i {
969                    let mut ie = inner.push();
970                    ie.set::<u32>(1, B0, i * 10 + j);
971                    ie.set_str(5, &format!("{i}.{j}"));
972                }
973                inner.close();
974                e.set_str(5, &format!("o{i}"));
975            }
976        }
977        let msg = b.finish().unwrap();
978        let outer = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
979        assert_eq!(outer.len(), 3);
980        for (i, e) in outer.iter().enumerate() {
981            let e = e.unwrap();
982            assert_eq!(read_elem(e), (i as u32, Some(format!("o{i}"))));
983            let inner = e.tables(19).unwrap().unwrap();
984            assert_eq!(inner.len(), i + 1);
985            for (j, ie) in inner.iter().enumerate() {
986                assert_eq!(read_elem(ie.unwrap()), ((i * 10 + j) as u32, Some(format!("{i}.{j}"))));
987            }
988        }
989    }
990
991    #[test]
992    fn streaming_list_dropped_without_close() {
993        fn fill(t: &mut TableW<'_>) -> Result<(), ()> {
994            let mut l = t.tables_streaming(11, EFL, EPTRS);
995            for i in 0..5u32 {
996                let mut e = l.push();
997                e.set::<u32>(1, B0, i);
998                e.set_str(5, "x");
999                if i == 2 {
1000                    return Err(());
1001                }
1002            }
1003            Ok(())
1004        }
1005        let pool = CountingPool::new();
1006        let mut b = Builder::new(&pool);
1007        {
1008            let mut t = b.root(FL);
1009            assert!(fill(&mut t).is_err());
1010        }
1011        let msg = b.finish().unwrap();
1012        assert_eq!(pool.takes.get(), 2);
1013        assert_eq!(pool.gives.get(), 2);
1014        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
1015        assert_eq!(l.len(), 3);
1016        assert_eq!(read_elem(l.get(2).unwrap()), (2, Some("x".into())));
1017    }
1018
1019    #[test]
1020    fn streaming_scalars_roundtrip() {
1021        let pool = SimplePool::new(64);
1022        let mut b = Builder::new(&pool);
1023        {
1024            let mut t = b.root(FL);
1025            let mut l = t.scalars_streaming::<u64>(11);
1026            for i in 0..7 {
1027                l.push(i * i);
1028            }
1029            drop(l);
1030            t.set_str(15, "fim");
1031        }
1032        let msg = b.finish().unwrap();
1033        let t = TableRef::at(&msg, 0).unwrap();
1034        let l = t.scalars::<u64>(11).unwrap().unwrap();
1035        assert_eq!(l.iter().collect::<Vec<_>>(), [0, 1, 4, 9, 16, 25, 36]);
1036        assert_eq!(t.str(15).unwrap(), Some("fim"));
1037    }
1038
1039    #[test]
1040    fn empty_streaming_list_is_present() {
1041        let pool = SimplePool::new(64);
1042        let mut b = Builder::new(&pool);
1043        b.root(FL).tables_streaming(11, EFL, EPTRS).close();
1044        let msg = b.finish().unwrap();
1045        assert_eq!(TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap().len(), 0);
1046    }
1047
1048    // ---- FixedW: all-scalar tables with compile-time size ----
1049    #[test]
1050    fn fixed_writer_in_known_list_and_root() {
1051        let pool = SimplePool::new(64);
1052        let mut b = Builder::new(&pool);
1053        {
1054            let mut t = b.root(FL);
1055            let mut l = t.tables(11, 2, 5);
1056            for i in 0..2u32 {
1057                let mut e = l.push_fixed::<5>();
1058                e.set::<u32>(1, B0, 10 + i);
1059            }
1060        }
1061        let msg = b.finish().unwrap();
1062        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
1063        assert_eq!(l.get(1).unwrap().scalar::<u32>(1, B0).unwrap(), Some(11));
1064
1065        let mut b = Builder::new(&pool);
1066        {
1067            let mut r = b.root_fixed::<9>();
1068            r.set::<u64>(1, B0, 7);
1069            r.set_bool(B1, true);
1070        }
1071        let msg = b.finish().unwrap();
1072        let t = TableRef::at(&msg, 0).unwrap();
1073        assert_eq!(t.fixed_len(), 9);
1074        assert_eq!(t.scalar::<u64>(1, B0).unwrap(), Some(7));
1075        assert_eq!(t.bool(B1), Some(true));
1076    }
1077
1078    #[test]
1079    fn fixed_writer_in_streaming_list() {
1080        let pool = SimplePool::new(64);
1081        let mut b = Builder::new(&pool);
1082        {
1083            let mut t = b.root(FL);
1084            let mut l = t.tables_streaming(11, 5, &[]);
1085            for i in 0..3u32 {
1086                l.push_fixed::<5>().set::<u32>(1, B0, i);
1087            }
1088        }
1089        let msg = b.finish().unwrap();
1090        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
1091        assert_eq!(l.len(), 3);
1092        assert_eq!(l.get(2).unwrap().scalar::<u32>(1, B0).unwrap(), Some(2));
1093    }
1094
1095    // ---- borrowed finish + fixed-element lists ----
1096    #[test]
1097    fn finished_borrows_current_message_and_next_root_resets() {
1098        let pool = SimplePool::new(64);
1099        let mut b = Builder::new(&pool);
1100        b.root(FL).set::<u64>(1, B0, 1);
1101        let first = b.finished().unwrap().to_vec();
1102        assert_eq!(TableRef::at(&first, 0).unwrap().scalar::<u64>(1, B0).unwrap(), Some(1));
1103        b.root(FL).set::<u64>(1, B0, 2);
1104        let second = b.finished().unwrap();
1105        assert_eq!(second.len(), first.len());
1106        assert_eq!(TableRef::at(second, 0).unwrap().scalar::<u64>(1, B0).unwrap(), Some(2));
1107    }
1108
1109    #[test]
1110    fn fixed_list_roundtrip_and_short_count() {
1111        let pool = SimplePool::new(64);
1112        let mut b = Builder::new(&pool);
1113        {
1114            let mut t = b.root(FL);
1115            let mut l = t.tables_fixed::<5>(11, 4);
1116            for i in 0..3u32 {
1117                l.push().set::<u32>(1, B0, 100 + i);
1118            }
1119        }
1120        let msg = b.finish().unwrap();
1121        let l = TableRef::at(&msg, 0).unwrap().tables(11).unwrap().unwrap();
1122        assert_eq!((l.len(), l.elem_fixed_len()), (3, 5));
1123        assert_eq!(l.get(2).unwrap().scalar::<u32>(1, B0).unwrap(), Some(102));
1124    }
1125
1126    #[test]
1127    #[should_panic(expected = "list too large")]
1128    fn list_end_overflow_panics() {
1129        // On 64-bit targets the buffer never gets close; the arithmetic is the same that overflows on 32 bits.
1130        let _ = list_end(usize::MAX - 3, 8);
1131    }
1132
1133    #[test]
1134    fn list_end_in_range() {
1135        assert_eq!(list_end(6, 10), 16);
1136    }
1137
1138    #[test]
1139    #[should_panic(expected = "more elements than reserved")]
1140    fn fixed_list_overflow_panics() {
1141        let pool = SimplePool::new(64);
1142        let mut b = Builder::new(&pool);
1143        let mut t = b.root(FL);
1144        let mut l = t.tables_fixed::<5>(11, 1);
1145        l.push();
1146        l.push();
1147    }
1148
1149    // ---- length-prefixed frame batches ----
1150    #[test]
1151    fn sealed_frames_roundtrip_in_one_bytes() {
1152        let pool = SimplePool::new(256);
1153        let mut b = Builder::new(&pool);
1154        for i in 0..3u64 {
1155            let mut t = b.root(FL);
1156            t.set::<u64>(1, B0, i);
1157            t.set_str(11, &"x".repeat(i as usize));
1158            b.seal().unwrap();
1159        }
1160        let total: usize = (0..3).map(|i| 4 + 2 + FL as usize + 4 + i).sum();
1161        assert_eq!(b.sealed_len(), total);
1162        let batch = b.take_frames();
1163        assert_eq!(b.sealed_len(), 0);
1164        let msgs: Vec<&[u8]> = crate::frames(&batch).map(|f| f.unwrap()).collect();
1165        assert_eq!(msgs.len(), 3);
1166        for (i, m) in msgs.iter().enumerate() {
1167            let t = TableRef::at(m, 0).unwrap();
1168            assert_eq!(t.scalar::<u64>(1, B0).unwrap(), Some(i as u64));
1169            assert_eq!(t.str(11).unwrap().unwrap().len(), i);
1170        }
1171        // the builder is empty again and keeps working
1172        b.root(FL).set::<u64>(1, B0, 9);
1173        b.seal().unwrap();
1174        let next = b.take_frames();
1175        assert_eq!(crate::frames(&next).count(), 1);
1176        assert_eq!(crate::frames(&batch).count(), 3, "earlier batch intact");
1177    }
1178
1179    #[test]
1180    fn unsealed_message_is_discarded_by_next_root() {
1181        let pool = SimplePool::new(256);
1182        let mut b = Builder::new(&pool);
1183        b.root(FL).set::<u64>(1, B0, 1);
1184        b.seal().unwrap();
1185        b.root(FL).set::<u64>(1, B0, 2); // abandoned
1186        b.root(FL).set::<u64>(1, B0, 3);
1187        b.seal().unwrap();
1188        let batch = b.take_frames();
1189        let vals: Vec<_> = crate::frames(&batch)
1190            .map(|f| TableRef::at(f.unwrap(), 0).unwrap().scalar::<u64>(1, B0).unwrap().unwrap())
1191            .collect();
1192        assert_eq!(vals, [1, 3]);
1193    }
1194
1195    #[test]
1196    fn take_frames_excludes_unsealed_tail() {
1197        let pool = SimplePool::new(256);
1198        let mut b = Builder::new(&pool);
1199        b.root(FL).set::<u64>(1, B0, 1);
1200        b.seal().unwrap();
1201        b.root(FL).set::<u64>(1, B0, 2); // not sealed
1202        let batch = b.take_frames();
1203        assert_eq!(crate::frames(&batch).count(), 1);
1204    }
1205
1206    #[test]
1207    fn frames_iterator_rejects_truncated_input() {
1208        let mut v = 10u32.to_le_bytes().to_vec();
1209        v.extend_from_slice(&[0; 4]);
1210        let r: Vec<_> = crate::frames(&v).collect();
1211        assert_eq!(r, [Err(Error::OutOfBounds)]);
1212        assert_eq!(crate::frames(&[1, 0]).collect::<Vec<_>>(), [Err(Error::OutOfBounds)]);
1213        assert_eq!(crate::frames(&[]).count(), 0);
1214    }
1215
1216    #[test]
1217    #[should_panic(expected = "sealed frames pending")]
1218    fn flush_with_pending_frames_panics() {
1219        let pool = SimplePool::new(256);
1220        let mut b = Builder::new(&pool);
1221        b.root(FL);
1222        b.seal().unwrap();
1223        b.root(FL);
1224        let _ = b.flush();
1225    }
1226}