graven/
read.rs

1//! Zero-copy reading.
2
3use crate::{Error, Le};
4
5/// Location of a presence bit: byte offset within the fixed part and bit shift.
6/// For `bool`, presence is `shift` and the value is `shift + 1`.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Bit {
9    pub byte: u16,
10    pub shift: u8,
11}
12
13/// A view over one table's fixed part. Never panics on malformed input.
14#[derive(Debug, Clone, Copy)]
15pub struct TableRef<'a> {
16    buf: &'a [u8],
17    /// The fixed part, pre-sliced so field reads need a single bounds check.
18    fixed: &'a [u8],
19    fixed_start: usize,
20}
21
22impl<'a> TableRef<'a> {
23    /// Table with a `[u16 fixed_len]` header at `pos`.
24    #[inline]
25    pub fn at(buf: &'a [u8], pos: usize) -> Result<Self, Error> {
26        let h = buf.get(pos..pos.checked_add(2).ok_or(Error::OutOfBounds)?).ok_or(Error::OutOfBounds)?;
27        let fixed_len = u16::read_le(h);
28        Self::inline(buf, pos + 2, fixed_len)
29    }
30
31    /// Header-less table (list element) whose fixed part starts at `fixed_start`.
32    #[inline]
33    pub fn inline(buf: &'a [u8], fixed_start: usize, fixed_len: u16) -> Result<Self, Error> {
34        let end = fixed_start.checked_add(fixed_len as usize).ok_or(Error::OutOfBounds)?;
35        let fixed = buf.get(fixed_start..end).ok_or(Error::OutOfBounds)?;
36        Ok(TableRef { buf, fixed, fixed_start })
37    }
38
39    #[inline]
40    pub fn buf(&self) -> &'a [u8] {
41        self.buf
42    }
43
44    #[inline]
45    pub fn fixed_len(&self) -> u16 {
46        self.fixed.len() as u16
47    }
48
49    /// Absolute position of the fixed part's first byte.
50    #[inline]
51    pub fn fixed_start(&self) -> usize {
52        self.fixed_start
53    }
54
55    /// The fixed part bytes (always in bounds by construction).
56    #[inline]
57    fn fixed(&self) -> &'a [u8] {
58        self.fixed
59    }
60
61    /// One byte of the fixed part; outside `fixed_len` ⇒ 0.
62    #[inline(always)]
63    pub fn fixed_byte(&self, i: u16) -> u8 {
64        self.fixed.get(i as usize).copied().unwrap_or(0)
65    }
66
67    /// Bit value; a bit byte outside `fixed_len` reads as `false` (absent).
68    #[inline]
69    pub fn bit(&self, b: Bit) -> bool {
70        match self.fixed().get(b.byte as usize) {
71            Some(v) => (v >> (b.shift & 7)) & 1 == 1,
72            None => false,
73        }
74    }
75
76    /// `Ok(true)` if the slot is fully inside `fixed_len`, `Ok(false)` if fully
77    /// outside, `Err(Truncated)` if partially covered.
78    #[inline]
79    pub fn slot_state(&self, off: u16, size: u16) -> Result<bool, Error> {
80        let (off, end) = (off as u32, off as u32 + size as u32);
81        let fl = self.fixed.len() as u32;
82        if end <= fl {
83            Ok(true)
84        } else if off >= fl {
85            Ok(false)
86        } else {
87            Err(Error::Truncated)
88        }
89    }
90
91    #[inline(always)]
92    fn slot_bytes(&self, off: u16, size: usize) -> Option<&'a [u8]> {
93        let o = off as usize;
94        self.fixed.get(o..o + size)
95    }
96
97    /// Scalar with presence bit, following the field read rule.
98    #[inline]
99    pub fn scalar<T: Le>(&self, off: u16, presence: Bit) -> Result<Option<T>, Error> {
100        if !self.slot_state(off, T::SIZE as u16)? || !self.bit(presence) {
101            return Ok(None);
102        }
103        Ok(self.slot_bytes(off, T::SIZE).map(T::read_le))
104    }
105
106    /// Scalar for an already-validated required field; any anomaly yields `default`.
107    #[inline]
108    pub fn scalar_or<T: Le>(&self, off: u16, presence: Bit, default: T) -> T {
109        match self.scalar(off, presence) {
110            Ok(Some(v)) => v,
111            _ => default,
112        }
113    }
114
115    /// Slot value ignoring presence, for required fields already validated by
116    /// the generated `read()`. Slot not fully inside `fixed_len` ⇒ `T::default()`.
117    #[inline(always)]
118    pub fn raw<T: Le>(&self, off: u16) -> T {
119        match self.slot_bytes(off, T::SIZE) {
120            Some(b) => T::read_le(b),
121            None => T::default(),
122        }
123    }
124
125    /// Bool stored as presence bit + value bit (`shift + 1`).
126    #[inline]
127    pub fn bool(&self, presence: Bit) -> Option<bool> {
128        if !self.bit(presence) {
129            return None;
130        }
131        Some(self.bit(Bit { byte: presence.byte, shift: presence.shift + 1 }))
132    }
133}
134
135// ---------------------------------------------------------------------------
136// Variable data
137// ---------------------------------------------------------------------------
138
139#[inline]
140fn read_at<T: Le>(buf: &[u8], pos: usize) -> Result<T, Error> {
141    let end = pos.checked_add(T::SIZE).ok_or(Error::OutOfBounds)?;
142    buf.get(pos..end).map(T::read_le).ok_or(Error::OutOfBounds)
143}
144
145/// Resolve the i32 pointer stored at absolute `slot_abs`. `0` ⇒ `None`.
146#[inline]
147fn resolve(buf: &[u8], slot_abs: usize) -> Result<Option<usize>, Error> {
148    let rel = read_at::<i32>(buf, slot_abs)?;
149    if rel == 0 {
150        return Ok(None);
151    }
152    let target = slot_abs as i64 + rel as i64;
153    if target < 0 || target >= buf.len() as i64 {
154        return Err(Error::BadOffset);
155    }
156    Ok(Some(target as usize))
157}
158
159/// `[u32 len][len * elem bytes]` at `pos` ⇒ (len, data slice start).
160#[inline]
161fn counted(buf: &[u8], pos: usize, elem: usize) -> Result<(usize, usize), Error> {
162    let len = read_at::<u32>(buf, pos)? as usize;
163    let data = pos + 4;
164    let bytes = len.checked_mul(elem).ok_or(Error::OutOfBounds)?;
165    let end = data.checked_add(bytes).ok_or(Error::OutOfBounds)?;
166    if end > buf.len() {
167        return Err(Error::OutOfBounds);
168    }
169    Ok((len, data))
170}
171
172#[inline]
173fn bytes_at(buf: &[u8], pos: usize) -> Result<&[u8], Error> {
174    let (len, data) = counted(buf, pos, 1)?;
175    Ok(&buf[data..data + len])
176}
177
178#[inline]
179fn str_at(buf: &[u8], pos: usize) -> Result<&str, Error> {
180    core::str::from_utf8(bytes_at(buf, pos)?).map_err(|_| Error::InvalidUtf8)
181}
182
183impl<'a> TableRef<'a> {
184    /// Absolute target of the pointer at slot `off`. Slot outside `fixed_len` or `0` ⇒ `None`.
185    #[inline]
186    pub fn ptr(&self, off: u16) -> Result<Option<usize>, Error> {
187        if !self.slot_state(off, 4)? {
188            return Ok(None);
189        }
190        resolve(self.buf, self.fixed_start + off as usize)
191    }
192
193    #[inline]
194    pub fn bytes(&self, off: u16) -> Result<Option<&'a [u8]>, Error> {
195        match self.ptr(off)? {
196            Some(p) => bytes_at(self.buf, p).map(Some),
197            None => Ok(None),
198        }
199    }
200
201    #[inline]
202    pub fn str(&self, off: u16) -> Result<Option<&'a str>, Error> {
203        match self.ptr(off)? {
204            Some(p) => str_at(self.buf, p).map(Some),
205            None => Ok(None),
206        }
207    }
208
209    /// Support for generated `*Verified` views; not part of the public API. See
210    /// [`crate::rt`] ("Invariants for the code generator") for the pattern that calls it.
211    ///
212    /// # Safety
213    /// The caller guarantees the string bytes at `off` are valid UTF-8. In generated
214    /// `*Verified` views this holds only if all of the following hold:
215    ///
216    /// 1. `self` was reached by a root `verify()` that succeeded on the same (immutable)
217    ///    buffer, along the **same edge** and as the **same reader type** whose view makes
218    ///    this call (the same bytes can be reached through two pointers as different table
219    ///    types; verify only validated the type of the edge it followed);
220    /// 2. that type's `verify` calls the validating [`str`](Self::str) at this same `off`
221    ///    (not merely charging `bytes(off).len()`);
222    /// 3. the view never reaches `self` through an edge verify skips (unknown union tags,
223    ///    fields beyond the reader's layout, reserved fields);
224    /// 4. any `VListR<T>` on the path was built with the `T` that verify used for that list.
225    #[doc(hidden)]
226    #[allow(unsafe_code)]
227    #[inline]
228    pub unsafe fn str_unchecked(&self, off: u16) -> Result<Option<&'a str>, Error> {
229        match self.bytes(off)? {
230            // SAFETY: the caller guarantees `b` is valid UTF-8 (function contract above).
231            Some(b) => Ok(Some(unsafe { core::str::from_utf8_unchecked(b) })),
232            None => Ok(None),
233        }
234    }
235
236    #[inline]
237    pub fn table(&self, off: u16) -> Result<Option<TableRef<'a>>, Error> {
238        match self.ptr(off)? {
239            Some(p) => TableRef::at(self.buf, p).map(Some),
240            None => Ok(None),
241        }
242    }
243
244    /// Union slot `[u16 tag][i32 ptr]`; tag 0 ⇒ `None`.
245    #[inline]
246    pub fn union(&self, off: u16) -> Result<Option<(u16, TableRef<'a>)>, Error> {
247        if !self.slot_state(off, 6)? {
248            return Ok(None);
249        }
250        let tag = read_at::<u16>(self.buf, self.fixed_start + off as usize)?;
251        if tag == 0 {
252            return Ok(None);
253        }
254        match resolve(self.buf, self.fixed_start + off as usize + 2)? {
255            Some(p) => Ok(Some((tag, TableRef::at(self.buf, p)?))),
256            None => Ok(None),
257        }
258    }
259
260    #[inline]
261    pub fn scalars<T: Le>(&self, off: u16) -> Result<Option<ScalarList<'a, T>>, Error> {
262        match self.ptr(off)? {
263            Some(p) => ScalarList::at(self.buf, p).map(Some),
264            None => Ok(None),
265        }
266    }
267
268    #[inline]
269    pub fn tables(&self, off: u16) -> Result<Option<TableList<'a>>, Error> {
270        match self.ptr(off)? {
271            Some(p) => TableList::at(self.buf, p).map(Some),
272            None => Ok(None),
273        }
274    }
275
276    #[inline]
277    pub fn strs(&self, off: u16) -> Result<Option<StrList<'a>>, Error> {
278        match self.ptr(off)? {
279            Some(p) => {
280                let (len, data) = counted(self.buf, p, 4)?;
281                Ok(Some(StrList { buf: self.buf, slots: data, len }))
282            }
283            None => Ok(None),
284        }
285    }
286}
287
288/// Split a batch produced by `Builder::take_frames` into messages.
289/// Each frame is `[u32 len][message]`; truncated input yields one
290/// `Err(OutOfBounds)` and stops. Never panics.
291pub fn frames(buf: &[u8]) -> Frames<'_> {
292    Frames { buf, failed: false }
293}
294
295pub struct Frames<'a> {
296    buf: &'a [u8],
297    failed: bool,
298}
299
300impl<'a> Iterator for Frames<'a> {
301    type Item = Result<&'a [u8], Error>;
302    #[inline]
303    fn next(&mut self) -> Option<Self::Item> {
304        if self.buf.is_empty() || self.failed {
305            return None;
306        }
307        let msg = (|| {
308            let len = read_at::<u32>(self.buf, 0)? as usize;
309            let end = 4usize.checked_add(len).ok_or(Error::OutOfBounds)?;
310            let m = self.buf.get(4..end).ok_or(Error::OutOfBounds)?;
311            Ok((m, end))
312        })();
313        match msg {
314            Ok((m, end)) => {
315                self.buf = &self.buf[end..];
316                Some(Ok(m))
317            }
318            Err(e) => {
319                self.failed = true;
320                Some(Err(e))
321            }
322        }
323    }
324}
325
326/// `[u32 count][count × T]`
327#[derive(Debug, Clone, Copy)]
328pub struct ScalarList<'a, T: Le> {
329    data: &'a [u8],
330    len: usize,
331    _t: core::marker::PhantomData<T>,
332}
333
334impl<'a, T: Le> ScalarList<'a, T> {
335    pub fn at(buf: &'a [u8], pos: usize) -> Result<Self, Error> {
336        let (len, data) = counted(buf, pos, T::SIZE)?;
337        Ok(ScalarList { data: &buf[data..data + len * T::SIZE], len, _t: core::marker::PhantomData })
338    }
339    #[inline]
340    pub fn len(&self) -> usize {
341        self.len
342    }
343    #[inline]
344    pub fn is_empty(&self) -> bool {
345        self.len == 0
346    }
347    #[inline]
348    pub fn get(&self, i: usize) -> Option<T> {
349        if i >= self.len {
350            return None;
351        }
352        self.data.get(i * T::SIZE..(i + 1) * T::SIZE).map(T::read_le)
353    }
354    /// Raw little-endian bytes of all elements.
355    #[inline]
356    pub fn as_bytes(&self) -> &'a [u8] {
357        self.data
358    }
359    pub fn iter(&self) -> impl ExactSizeIterator<Item = T> + 'a
360    where
361        T: 'a,
362    {
363        self.data.chunks_exact(T::SIZE).map(T::read_le)
364    }
365}
366
367/// `[u32 count][u16 elem_fixed_len][count × fixed part]`
368#[derive(Debug, Clone, Copy)]
369pub struct TableList<'a> {
370    buf: &'a [u8],
371    first: usize,
372    len: usize,
373    elem_fixed_len: u16,
374}
375
376impl<'a> TableList<'a> {
377    pub fn at(buf: &'a [u8], pos: usize) -> Result<Self, Error> {
378        let len = read_at::<u32>(buf, pos)? as usize;
379        let elem_fixed_len = read_at::<u16>(buf, pos.checked_add(4).ok_or(Error::OutOfBounds)?)?;
380        let first = pos + 6;
381        let bytes = len.checked_mul(elem_fixed_len as usize).ok_or(Error::OutOfBounds)?;
382        let end = first.checked_add(bytes).ok_or(Error::OutOfBounds)?;
383        if end > buf.len() {
384            return Err(Error::OutOfBounds);
385        }
386        Ok(TableList { buf, first, len, elem_fixed_len })
387    }
388    #[inline]
389    pub fn len(&self) -> usize {
390        self.len
391    }
392    #[inline]
393    pub fn is_empty(&self) -> bool {
394        self.len == 0
395    }
396    #[inline]
397    pub fn elem_fixed_len(&self) -> u16 {
398        self.elem_fixed_len
399    }
400    #[inline]
401    pub fn get(&self, i: usize) -> Result<TableRef<'a>, Error> {
402        if i >= self.len {
403            return Err(Error::OutOfBounds);
404        }
405        TableRef::inline(self.buf, self.first + i * self.elem_fixed_len as usize, self.elem_fixed_len)
406    }
407    pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<TableRef<'a>, Error>> + 'a {
408        let s = *self;
409        (0..s.len).map(move |i| s.get(i))
410    }
411}
412
413/// `[u32 count][count × i32 ptr]` to strings (or bytes).
414#[derive(Debug, Clone, Copy)]
415pub struct StrList<'a> {
416    buf: &'a [u8],
417    slots: usize,
418    len: usize,
419}
420
421impl<'a> StrList<'a> {
422    #[inline]
423    pub fn len(&self) -> usize {
424        self.len
425    }
426    #[inline]
427    pub fn is_empty(&self) -> bool {
428        self.len == 0
429    }
430    pub fn get_bytes(&self, i: usize) -> Result<&'a [u8], Error> {
431        if i >= self.len {
432            return Err(Error::OutOfBounds);
433        }
434        match resolve(self.buf, self.slots + i * 4)? {
435            Some(p) => bytes_at(self.buf, p),
436            None => Ok(&[]),
437        }
438    }
439    pub fn get(&self, i: usize) -> Result<&'a str, Error> {
440        core::str::from_utf8(self.get_bytes(i)?).map_err(|_| Error::InvalidUtf8)
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use crate::{Error, Le};
448
449    // fixed_len=7: [bits][u32 a @1][u16 b @5]; a=bit0, b=bit1, bool=bits2/3
450    fn sample() -> [u8; 9] {
451        [7, 0, 0b0000_1101, 42, 0, 0, 0, 0, 0]
452    }
453
454    #[test]
455    fn reads_present_scalar() {
456        let b = sample();
457        let t = TableRef::at(&b, 0).unwrap();
458        assert_eq!(t.scalar::<u32>(1, Bit { byte: 0, shift: 0 }).unwrap(), Some(42));
459    }
460
461    #[test]
462    fn absent_bit_is_none() {
463        let b = sample();
464        let t = TableRef::at(&b, 0).unwrap();
465        assert_eq!(t.scalar::<u16>(5, Bit { byte: 0, shift: 1 }).unwrap(), None);
466    }
467
468    #[test]
469    fn bool_two_bits() {
470        let b = sample();
471        let t = TableRef::at(&b, 0).unwrap();
472        assert_eq!(t.bool(Bit { byte: 0, shift: 2 }), Some(true));
473        assert_eq!(t.bool(Bit { byte: 0, shift: 4 }), None);
474    }
475
476    #[test]
477    fn slot_beyond_fixed_len_is_none() {
478        let b = sample();
479        let t = TableRef::at(&b, 0).unwrap();
480        assert_eq!(t.scalar::<u32>(7, Bit { byte: 0, shift: 4 }).unwrap(), None);
481    }
482
483    #[test]
484    fn bit_byte_beyond_fixed_len_is_none() {
485        let b = sample();
486        let t = TableRef::at(&b, 0).unwrap();
487        assert!(!t.bit(Bit { byte: 9, shift: 0 }));
488        assert_eq!(t.scalar::<u8>(1, Bit { byte: 9, shift: 0 }).unwrap(), None);
489    }
490
491    #[test]
492    fn partial_slot_is_truncated() {
493        let b = sample();
494        let t = TableRef::at(&b, 0).unwrap();
495        assert_eq!(t.slot_state(5, 4), Err(Error::Truncated));
496        assert_eq!(t.scalar::<u32>(5, Bit { byte: 0, shift: 1 }), Err(Error::Truncated));
497    }
498
499    #[test]
500    fn header_past_end_is_err() {
501        assert_eq!(TableRef::at(&[9, 0, 1], 0).err(), Some(Error::OutOfBounds));
502        assert_eq!(TableRef::at(&[9], 0).err(), Some(Error::OutOfBounds));
503        assert_eq!(TableRef::at(&[0, 0], 5).err(), Some(Error::OutOfBounds));
504    }
505
506    #[test]
507    fn scalar_or_never_panics() {
508        let b = sample();
509        let t = TableRef::at(&b, 0).unwrap();
510        assert_eq!(t.scalar_or::<u32>(1, Bit { byte: 0, shift: 0 }, 9), 42);
511        assert_eq!(t.scalar_or::<u32>(6, Bit { byte: 0, shift: 0 }, 9), 9);
512    }
513
514    fn rt<T: Le + PartialEq + core::fmt::Debug>(v: T) {
515        let mut buf = [0u8; 16];
516        v.write_le(&mut buf[..T::SIZE]);
517        assert_eq!(T::read_le(&buf[..T::SIZE]), v);
518    }
519
520    #[test]
521    fn le_roundtrip_all_types() {
522        rt(0xABu8);
523        rt(0xABCDu16);
524        rt(0xDEADBEEFu32);
525        rt(u64::MAX - 7);
526        rt(u128::MAX - 3);
527        rt(-5i8);
528        rt(i16::MIN);
529        rt(i32::MIN);
530        rt(i64::MIN);
531        rt(i128::MIN);
532        rt(1.5f32);
533        rt(-2.25f64);
534        let mut buf = [0u8; 8];
535        f64::NAN.write_le(&mut buf);
536        assert_eq!(f64::read_le(&buf).to_bits(), f64::NAN.to_bits());
537        let mut b4 = [0u8; 4];
538        0x01020304u32.write_le(&mut b4);
539        assert_eq!(b4, [4, 3, 2, 1]);
540    }
541
542    // ---- Task 2: variable data ----
543
544    /// Table with fixed_len=4 holding one i32 ptr at slot 0 (abs pos 2), followed by `tail`.
545    fn table_with_ptr(rel: i32, tail: &[u8]) -> alloc::vec::Vec<u8> {
546        let mut v = alloc::vec![4, 0];
547        v.extend_from_slice(&rel.to_le_bytes());
548        v.extend_from_slice(tail);
549        v
550    }
551
552    #[test]
553    fn string_forward_offset() {
554        // slot abs 2, tail starts at 6 => rel 4
555        let b = table_with_ptr(4, &[2, 0, 0, 0, b'o', b'i']);
556        let t = TableRef::at(&b, 0).unwrap();
557        assert_eq!(t.str(0).unwrap(), Some("oi"));
558        assert_eq!(t.bytes(0).unwrap(), Some(&b"oi"[..]));
559    }
560
561    #[test]
562    fn negative_offset_into_buffer() {
563        // string at pos 0, table at pos 6; slot abs 8 => rel -8
564        let mut b = alloc::vec![2, 0, 0, 0, b'h', b'i'];
565        b.extend_from_slice(&[4, 0]);
566        b.extend_from_slice(&(-8i32).to_le_bytes());
567        let t = TableRef::at(&b, 6).unwrap();
568        assert_eq!(t.str(0).unwrap(), Some("hi"));
569    }
570
571    #[test]
572    fn offset_before_zero_is_bad_offset() {
573        let b = table_with_ptr(-3, &[]);
574        assert_eq!(TableRef::at(&b, 0).unwrap().ptr(0), Err(Error::BadOffset));
575        let b = table_with_ptr(i32::MIN, &[]);
576        assert_eq!(TableRef::at(&b, 0).unwrap().ptr(0), Err(Error::BadOffset));
577    }
578
579    #[test]
580    fn offset_past_end_is_bad_offset() {
581        let b = table_with_ptr(4, &[]);
582        assert_eq!(TableRef::at(&b, 0).unwrap().ptr(0), Err(Error::BadOffset));
583        let b = table_with_ptr(i32::MAX, &[]);
584        assert_eq!(TableRef::at(&b, 0).unwrap().str(0), Err(Error::BadOffset));
585    }
586
587    #[test]
588    fn len_u32_max_is_out_of_bounds() {
589        let b = table_with_ptr(4, &u32::MAX.to_le_bytes());
590        let t = TableRef::at(&b, 0).unwrap();
591        assert_eq!(t.bytes(0), Err(Error::OutOfBounds));
592        assert_eq!(t.scalars::<u64>(0).err(), Some(Error::OutOfBounds));
593        assert_eq!(t.tables(0).err(), Some(Error::OutOfBounds));
594        assert_eq!(t.strs(0).err(), Some(Error::OutOfBounds));
595    }
596
597    #[test]
598    fn invalid_utf8_is_err() {
599        let b = table_with_ptr(4, &[1, 0, 0, 0, 0xFF]);
600        assert_eq!(TableRef::at(&b, 0).unwrap().str(0), Err(Error::InvalidUtf8));
601    }
602
603    #[test]
604    fn zero_offset_is_none() {
605        let b = table_with_ptr(0, &[]);
606        let t = TableRef::at(&b, 0).unwrap();
607        assert_eq!(t.ptr(0), Ok(None));
608        assert_eq!(t.str(0), Ok(None));
609        assert!(t.table(0).unwrap().is_none());
610        assert!(t.tables(0).unwrap().is_none());
611        // slot outside fixed_len also None
612        assert_eq!(t.ptr(4), Ok(None));
613        assert_eq!(t.ptr(2), Err(Error::Truncated));
614    }
615
616    #[test]
617    fn table_list_stride_uses_elem_fixed_len() {
618        // [u32 count=2][u16 elem_fixed_len=5][e0: bits=1, u32=10][e1: bits=1, u32=20]
619        let mut tail = alloc::vec![2, 0, 0, 0, 5, 0];
620        tail.extend_from_slice(&[1, 10, 0, 0, 0, 1, 20, 0, 0, 0]);
621        let b = table_with_ptr(4, &tail);
622        let l = TableRef::at(&b, 0).unwrap().tables(0).unwrap().unwrap();
623        assert_eq!(l.len(), 2);
624        assert_eq!(l.elem_fixed_len(), 5);
625        let p = Bit { byte: 0, shift: 0 };
626        assert_eq!(l.get(0).unwrap().scalar::<u32>(1, p).unwrap(), Some(10));
627        assert_eq!(l.get(1).unwrap().scalar::<u32>(1, p).unwrap(), Some(20));
628        assert!(l.get(2).is_err());
629        assert_eq!(l.iter().count(), 2);
630    }
631
632    #[test]
633    fn scalar_list_reads() {
634        let mut tail = alloc::vec![3, 0, 0, 0];
635        for v in [7u16, 8, 9] {
636            tail.extend_from_slice(&v.to_le_bytes());
637        }
638        let b = table_with_ptr(4, &tail);
639        let l = TableRef::at(&b, 0).unwrap().scalars::<u16>(0).unwrap().unwrap();
640        assert_eq!(l.len(), 3);
641        assert_eq!(l.get(1), Some(8));
642        assert_eq!(l.get(3), None);
643        assert_eq!(l.iter().collect::<alloc::vec::Vec<_>>(), [7, 8, 9]);
644    }
645
646    #[test]
647    fn str_list_reads() {
648        // [count=2][rel0][rel1]["a"]["bc"]; offsets slots at tail+4 and tail+8
649        // positions: tail starts at 6; slot0 at 10, slot1 at 14; s0 at 18, s1 at 23
650        let mut tail = alloc::vec![2, 0, 0, 0];
651        tail.extend_from_slice(&8i32.to_le_bytes());
652        tail.extend_from_slice(&9i32.to_le_bytes());
653        tail.extend_from_slice(&[1, 0, 0, 0, b'a', 2, 0, 0, 0, b'b', b'c']);
654        let b = table_with_ptr(4, &tail);
655        let l = TableRef::at(&b, 0).unwrap().strs(0).unwrap().unwrap();
656        assert_eq!(l.len(), 2);
657        assert_eq!(l.get(0), Ok("a"));
658        assert_eq!(l.get(1), Ok("bc"));
659    }
660
661    #[test]
662    fn union_unknown_tag_still_returns_tag() {
663        // fixed_len=6: [u16 tag=99][i32 rel] ; slot ptr abs pos 4 -> child at 8 => rel 4
664        let mut b = alloc::vec![6, 0, 99, 0];
665        b.extend_from_slice(&4i32.to_le_bytes());
666        b.extend_from_slice(&[0, 0]); // empty child table
667        let t = TableRef::at(&b, 0).unwrap();
668        let (tag, child) = t.union(0).unwrap().unwrap();
669        assert_eq!(tag, 99);
670        assert_eq!(child.fixed_len(), 0);
671        let none = [6u8, 0, 0, 0, 0, 0, 0, 0];
672        assert!(TableRef::at(&none, 0).unwrap().union(0).unwrap().is_none());
673    }
674
675    #[test]
676    fn nested_table() {
677        let b = table_with_ptr(4, &[1, 0, 0]);
678        let c = TableRef::at(&b, 0).unwrap().table(0).unwrap().unwrap();
679        assert_eq!(c.fixed_len(), 1);
680    }
681
682    #[test]
683    fn raw_reads_slot_without_presence_and_never_panics() {
684        let b = sample();
685        let t = TableRef::at(&b, 0).unwrap();
686        assert_eq!(t.raw::<u32>(1), 42);
687        assert_eq!(t.raw::<u32>(5), 0); // partial -> default
688        assert_eq!(t.raw::<u64>(100), 0);
689    }
690}