Skip to main content

cadmpeg_core/decode/
view.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Bounded navigation over one address space.
3#![warn(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
4
5use super::error::SourceLocation;
6use super::probe::{ParseError, ParseErrorKind};
7use super::space::SpaceId;
8use crate::bytes::{
9    assemble_f32_be, assemble_f32_le, assemble_f64_be, assemble_f64_le, assemble_u16_be,
10    assemble_u16_le, assemble_u24_be, assemble_u24_le, assemble_u32_be, assemble_u32_le,
11    assemble_u64_be, assemble_u64_le,
12};
13
14/// Converts a declared element count into a safe `Vec` capacity.
15///
16/// Returns `None` unless `count * element_size <= remaining`, i.e. unless
17/// the declared elements could actually be present in the unread input.
18/// `element_size` must be the minimum encoded size of one element and must
19/// be nonzero.
20pub fn bounded_len(count: u64, element_size: usize, remaining: usize) -> Option<usize> {
21    if element_size == 0 {
22        return None;
23    }
24    let count = usize::try_from(count).ok()?;
25    let bytes = count.checked_mul(element_size)?;
26    (bytes <= remaining).then_some(count)
27}
28
29// The proof `index_from_u32` rests on. It justifies that one widening and
30// nothing else.
31const _: () = assert!(
32    usize::BITS >= 32,
33    "a target whose usize is narrower than 32 bits cannot hold a u32 index"
34);
35
36/// Widens a 32-bit count, identifier or index to a `usize` index.
37///
38/// A `const` assertion beside this function admits only targets whose `usize`
39/// holds every `u32`, so the widening is exact, it is total, and it states no
40/// refusal. A count that must also fit the unread input goes through
41/// [`bounded_len`] instead.
42pub const fn index_from_u32(value: u32) -> usize {
43    #[cfg(target_pointer_width = "32")]
44    {
45        usize::from_ne_bytes(value.to_ne_bytes())
46    }
47    #[cfg(target_pointer_width = "64")]
48    {
49        let bytes = value.to_ne_bytes();
50        #[cfg(target_endian = "little")]
51        {
52            usize::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0])
53        }
54        #[cfg(target_endian = "big")]
55        {
56            usize::from_ne_bytes([0, 0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3]])
57        }
58    }
59}
60
61// The proof `u64_from_index` rests on. It justifies that one widening and
62// nothing else.
63const _: () = assert!(
64    usize::BITS <= 64,
65    "a target whose usize is wider than 64 bits cannot hold every index in a u64"
66);
67
68/// Widens an in-memory length, index or capacity to a `u64`.
69///
70/// A `const` assertion beside this function admits only targets whose `u64`
71/// holds every `usize`, so the widening is exact, it is total, and it states
72/// no refusal. It is the counterpart of [`index_from_u32`]: it carries a
73/// figure that already exists in memory into the wider type a work budget, a
74/// byte offset or a limit report is stated in. It proves nothing about a
75/// declared count read from input; such a count goes through [`bounded_len`].
76pub const fn u64_from_index(value: usize) -> u64 {
77    #[cfg(target_pointer_width = "64")]
78    {
79        u64::from_ne_bytes(value.to_ne_bytes())
80    }
81    #[cfg(target_pointer_width = "32")]
82    {
83        let bytes = value.to_ne_bytes();
84        #[cfg(target_endian = "little")]
85        {
86            u64::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0])
87        }
88        #[cfg(target_endian = "big")]
89        {
90            u64::from_ne_bytes([0, 0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3]])
91        }
92    }
93}
94
95/// Narrows an offset stated in a wire `u64` to an index into bytes held in
96/// memory.
97///
98/// `None` states that no index of this target names the offset, so the offset
99/// names no byte of any payload the decoder holds. That is a refusal about the
100/// offset, not about the payload's length: a reader that also needs the offset
101/// to be inside a window tests the window itself.
102///
103/// It is the counterpart of [`u64_from_index`], which carries an index that
104/// already exists in memory into the wider type a stored offset is stated in.
105#[must_use]
106pub fn index_from_u64(offset: u64) -> Option<usize> {
107    usize::try_from(offset).ok()
108}
109
110/// Narrows the position of one item already held in memory to the `u32` an IR
111/// identifier, order or index field is stated in.
112///
113/// This is the one rule for a population that outgrows an IR id width, and
114/// every lane that meets that shape applies it. The population cannot be
115/// widened: the IR field's width is the constraint, not the decoder's. A
116/// position the field cannot state is therefore a refusal, and a refusal names
117/// the instance and the lane it happened in — a `LossNote` at that lane, or
118/// the lane's own refusal type. It is never a whole-file `Err`, because one
119/// lane that outgrew its id width says nothing about the rest of the file, and
120/// never a silent `continue`, because a dropped instance no one names is a
121/// loss no report carries. `None` is that refusal; the caller states it.
122///
123/// A lane stated in an `i32` rather than a `u32` applies the same rule at that
124/// width.
125///
126/// This is not [`bounded_len`]: the position already exists in memory, so
127/// `None` states nothing about the input's own declared counts.
128#[must_use]
129pub fn id_from_index(index: usize) -> Option<u32> {
130    u32::try_from(index).ok()
131}
132
133/// A count proven to fit in the unread input.
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct BoundedCount(usize);
136
137impl BoundedCount {
138    /// Returns the proven element count.
139    pub fn get(self) -> usize {
140        self.0
141    }
142}
143
144/// A bounded window over one address space.
145///
146/// The readable bytes are held as the slice itself, so [`View::window`] is a
147/// field read with no range to recompute, and the bytes the cursor has not
148/// passed are held as a suffix of that slice, so [`View::unread`],
149/// [`View::remaining`] and [`View::take`] are slice operations with no offset
150/// to recompute. `start` and `end` place the window in the space:
151/// `end - start == window.len()`, and `unread` is a suffix of `window` for
152/// every view. The two constructors are the only places those bounds are
153/// proven — `over_space` takes a whole buffer, and `child` carves the
154/// sub-slice with the same `get` that bounds the child.
155///
156/// `end` restates `start + window.len()`. This file denies
157/// `clippy::arithmetic_side_effects` and `usize` states no total addition, so
158/// every spelling of [`View::end`] that derives the bound is either that
159/// denied `+` or a `checked_add` whose `None` no view can reach. The bound is
160/// therefore stored, and `over_space` and `child` are the only places it is
161/// proven.
162#[derive(Debug, Clone, Copy)]
163pub struct View<'a> {
164    window: &'a [u8],
165    unread: &'a [u8],
166    space: SpaceId,
167    start: usize,
168    end: usize,
169}
170
171impl<'a> View<'a> {
172    /// Creates a full-window view over an entire space buffer.
173    pub(super) fn over_space(bytes: &'a [u8], space: SpaceId) -> View<'a> {
174        View {
175            window: bytes,
176            unread: bytes,
177            space,
178            start: 0,
179            end: bytes.len(),
180        }
181    }
182
183    /// Bounded window over retained bytes that left the live space chain.
184    ///
185    /// Prefer views returned by [`DecodeContext`](super::DecodeContext). This
186    /// constructor exists for opportunistic scanners over owned payloads that
187    /// were copied out of the arena; locations report [`SpaceId::ROOT`].
188    pub fn over_retained(bytes: &'a [u8]) -> View<'a> {
189        Self::over_space(bytes, SpaceId::ROOT)
190    }
191
192    /// Returns the space this view navigates.
193    pub(super) fn space(self) -> SpaceId {
194        self.space
195    }
196
197    /// Returns the absolute position within the space.
198    pub fn position(self) -> usize {
199        // The unread bytes are the window suffix at or after the position, so
200        // their count is the distance from the position to `end` and never
201        // passes it. `abs_diff` is total at that distance.
202        self.end.abs_diff(self.remaining())
203    }
204
205    /// Returns the window offset of the current position.
206    ///
207    /// Every view holds the unread bytes as a suffix of the window, so the
208    /// bytes already read are the rest of it. `abs_diff` is total at that
209    /// count: `unread` is never longer than `window`.
210    pub fn read_len(self) -> usize {
211        self.window.len().abs_diff(self.unread.len())
212    }
213
214    /// Returns the window's inclusive lower bound.
215    pub fn start(self) -> usize {
216        self.start
217    }
218
219    /// Returns the window's exclusive upper bound.
220    pub fn end(self) -> usize {
221        self.end
222    }
223
224    /// Returns the window offset of an absolute position in this space.
225    ///
226    /// `None` is the lower-bound refusal [`View::child`] and [`View::seek`]
227    /// state: a position under this window's own start names no byte of the
228    /// window.
229    fn offset_of(self, position: usize) -> Option<usize> {
230        position.checked_sub(self.start)
231    }
232
233    /// Returns the unread bytes of the window.
234    ///
235    /// The cursor is the split of the window, so this is a field read.
236    pub fn unread(self) -> &'a [u8] {
237        self.unread
238    }
239
240    /// Returns the number of unread bytes before the window's end.
241    pub fn remaining(self) -> usize {
242        self.unread.len()
243    }
244
245    /// Returns whether all bounded bytes have been read.
246    pub fn is_empty(self) -> bool {
247        self.unread.is_empty()
248    }
249
250    /// Returns this view's current source location.
251    pub fn location(self) -> SourceLocation {
252        SourceLocation {
253            space: self.space,
254            offset: u64_from_index(self.position()),
255        }
256    }
257
258    /// Returns a source location at an absolute offset in this view's space.
259    pub fn location_at(self, offset: usize) -> SourceLocation {
260        SourceLocation {
261            space: self.space,
262            offset: u64_from_index(offset),
263        }
264    }
265
266    /// Returns the readable window `start..end` as a slice.
267    pub fn window(self) -> &'a [u8] {
268        self.window
269    }
270
271    /// Takes `count` bytes, advancing only on success.
272    ///
273    /// The unread slice is the upper bound: `split_at_checked` refuses a count
274    /// the unread bytes cannot serve, so no separate end test stands beside
275    /// it, and the cursor moves only when the split holds.
276    pub fn take(&mut self, count: usize) -> Option<&'a [u8]> {
277        let (bytes, unread) = self.unread.split_at_checked(count)?;
278        self.unread = unread;
279        Some(bytes)
280    }
281
282    /// Takes a fixed-size byte array, advancing only on success.
283    pub fn array<const N: usize>(&mut self) -> Option<[u8; N]> {
284        self.take(N)?.try_into().ok()
285    }
286
287    /// Skips exactly `count` bytes.
288    pub fn skip(&mut self, count: usize) -> Option<()> {
289        self.take(count).map(|_| ())
290    }
291
292    /// Moves to an absolute offset, honoring the stored lower bound.
293    ///
294    /// The window states both bounds: `offset_of` refuses a position under the
295    /// window's own start, and `get` refuses one past its end.
296    pub fn seek(&mut self, position: usize) -> Option<()> {
297        self.unread = self.window.get(self.offset_of(position)?..)?;
298        Some(())
299    }
300
301    /// Moves the cursor to the window's exclusive upper bound.
302    ///
303    /// This is the one seek the window proves at construction. `seek(end)`
304    /// reads `offset_of(end)`, which is the window length, and the suffix of a
305    /// slice at its own length is the empty slice, so neither bound of `seek`
306    /// can refuse it. The suffix is taken from `window` itself: `split_at` is
307    /// total for every `mid <= len` by its own contract, and the `mid` here is
308    /// that length, so the invariant that `unread` is a suffix of `window`
309    /// holds by derivation and the move states no refusal to thread.
310    /// `remaining` is then 0, `read_len` the whole window, and `position` the
311    /// stored `end`.
312    pub fn seek_to_end(&mut self) {
313        self.unread = self.window.split_at(self.window.len()).1;
314    }
315
316    /// Reads a single byte.
317    pub fn u8(&mut self) -> Option<u8> {
318        self.array::<1>().map(|[value]| value)
319    }
320
321    /// Returns an exactly contained child window.
322    ///
323    /// The containment test is the sub-slice itself: `offset_of` refuses a
324    /// lower bound under this window's own, and `get` refuses an inverted
325    /// range or an upper bound past this window's end.
326    pub fn child(self, start: usize, end: usize) -> Option<View<'a>> {
327        let window = self
328            .window
329            .get(self.offset_of(start)?..self.offset_of(end)?)?;
330        Some(View {
331            window,
332            unread: window,
333            space: self.space,
334            start,
335            end,
336        })
337    }
338
339    /// Takes `count` bytes as an exactly contained child window, advancing only
340    /// on success.
341    ///
342    /// This is [`View::take`] for a reader that needs the taken bytes as a
343    /// bounded window rather than a slice: the same `split_at_checked` states
344    /// the one refusal, and the cursor moves only when the split holds. The
345    /// child window is those bytes in this view's own space, so
346    /// [`View::start`] of the result is the position the take began at,
347    /// [`View::end`] is the position it reached, and [`View::location`] of the
348    /// result is that beginning. No bound is recomputed against the parent and
349    /// no sum is formed, so the operation is total wherever the take is.
350    fn take_child(&mut self, count: usize) -> Option<View<'a>> {
351        let start = self.position();
352        let window = self.take(count)?;
353        Some(View {
354            window,
355            unread: window,
356            space: self.space,
357            start,
358            end: self.position(),
359        })
360    }
361
362    /// Proves a declared element count could fit in the unread bytes.
363    pub fn counted(self, count: u64, min_element_size: usize) -> Option<BoundedCount> {
364        bounded_len(count, min_element_size, self.remaining()).map(BoundedCount)
365    }
366
367    /// Builds an unexpected-eof error from the view's current state.
368    fn eof(self, needed: u64) -> ParseError {
369        ParseError {
370            location: self.location(),
371            kind: ParseErrorKind::UnexpectedEof { needed },
372            operation: "required_read",
373        }
374    }
375
376    /// Required-read mirror of [`View::take`].
377    pub fn req_take(&mut self, count: usize) -> Result<&'a [u8], ParseError> {
378        match self.take(count) {
379            Some(bytes) => Ok(bytes),
380            None => Err(self.eof(u64_from_index(count))),
381        }
382    }
383
384    /// Takes `count` bytes as a bounded child window, or states the truncation.
385    ///
386    /// The child window carries the same bounds and space as the optional take
387    /// this mirrors: [`View::start`] of the result is the position the take
388    /// began at and [`View::end`] is the position it reached. The view states
389    /// the same located truncation as [`View::req_take`] for the same count,
390    /// and leaves the view unmoved when it does.
391    pub fn req_take_child(&mut self, count: usize) -> Result<View<'a>, ParseError> {
392        match self.take_child(count) {
393            Some(view) => Ok(view),
394            None => Err(self.eof(u64_from_index(count))),
395        }
396    }
397
398    /// Required-read mirror of [`View::u8`].
399    pub fn req_u8(&mut self) -> Result<u8, ParseError> {
400        match self.u8() {
401            Some(value) => Ok(value),
402            None => Err(self.eof(1)),
403        }
404    }
405}
406
407const fn decode_i16_le(bytes: [u8; 2]) -> i16 {
408    // endian-exception: reconstructed-scalar
409    i16::from_le_bytes(bytes)
410}
411
412const fn decode_i32_le(bytes: [u8; 4]) -> i32 {
413    // endian-exception: reconstructed-scalar
414    i32::from_le_bytes(bytes)
415}
416
417const fn decode_i64_le(bytes: [u8; 8]) -> i64 {
418    // endian-exception: reconstructed-scalar
419    i64::from_le_bytes(bytes)
420}
421
422const fn decode_i16_be(bytes: [u8; 2]) -> i16 {
423    // endian-exception: reconstructed-scalar
424    i16::from_be_bytes(bytes)
425}
426
427macro_rules! view_readers {
428    ($(($probe:ident, [$($req:ident)?], $at:ident, $ty:ty, $decode:ident, $size:literal)),* $(,)?) => {
429        impl View<'_> {
430            $(
431                #[doc = concat!("Probe read of a `", stringify!($ty), "` in the serialized byte order.")]
432                pub fn $probe(&mut self) -> Option<$ty> {
433                    self.array::<$size>().map($decode)
434                }
435
436                $(
437                #[doc = concat!("Required-read mirror of [`View::", stringify!($probe), "`].")]
438                pub fn $req(&mut self) -> Result<$ty, ParseError> {
439                    match self.array::<$size>() {
440                        Some(bytes) => Ok($decode(bytes)),
441                        None => Err(self.eof($size)),
442                    }
443                }
444                )?
445
446                #[doc = concat!(
447                    "Offset probe of a `",
448                    stringify!($ty),
449                    "` over retained bytes. Sequential readers should keep a live `View` and call [`View::",
450                    stringify!($probe),
451                    "`] instead."
452                )]
453                pub fn $at(bytes: &[u8], offset: usize) -> Option<$ty> {
454                    let mut view = View::over_retained(bytes);
455                    view.seek(offset)?;
456                    view.$probe()
457                }
458            )*
459        }
460    };
461}
462
463view_readers!(
464    (u16_le, [req_u16_le], u16_le_at, u16, assemble_u16_le, 2),
465    (i16_le, [req_i16_le], i16_le_at, i16, decode_i16_le, 2),
466    (u32_le, [req_u32_le], u32_le_at, u32, assemble_u32_le, 4),
467    (i32_le, [req_i32_le], i32_le_at, i32, decode_i32_le, 4),
468    (u64_le, [req_u64_le], u64_le_at, u64, assemble_u64_le, 8),
469    (i64_le, [req_i64_le], i64_le_at, i64, decode_i64_le, 8),
470    (f32_le, [req_f32_le], f32_le_at, f32, assemble_f32_le, 4),
471    (f64_le, [req_f64_le], f64_le_at, f64, assemble_f64_le, 8),
472    (u16_be, [], u16_be_at, u16, assemble_u16_be, 2),
473    (i16_be, [], i16_be_at, i16, decode_i16_be, 2),
474    (u32_be, [req_u32_be], u32_be_at, u32, assemble_u32_be, 4),
475    (u64_be, [], u64_be_at, u64, assemble_u64_be, 8),
476    (f32_be, [req_f32_be], f32_be_at, f32, assemble_f32_be, 4),
477    (f64_be, [], f64_be_at, f64, assemble_f64_be, 8),
478);
479
480impl View<'_> {
481    /// Reads a three-byte little-endian integer from retained bytes.
482    pub fn u24_le_at(bytes: &[u8], offset: usize) -> Option<u32> {
483        let end = offset.checked_add(3)?;
484        let bytes = bytes.get(offset..end)?.try_into().ok()?;
485        Some(assemble_u24_le(bytes))
486    }
487
488    /// Reads a three-byte big-endian integer from retained bytes.
489    pub fn u24_be_at(bytes: &[u8], offset: usize) -> Option<u32> {
490        let end = offset.checked_add(3)?;
491        let bytes = bytes.get(offset..end)?.try_into().ok()?;
492        Some(assemble_u24_be(bytes))
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use super::{
499        bounded_len, id_from_index, index_from_u32, index_from_u64, u64_from_index, BoundedCount,
500        ParseError, ParseErrorKind, SourceLocation, View,
501    };
502    use crate::decode::space::SpaceId;
503
504    #[test]
505    #[cfg(target_pointer_width = "64")]
506    fn an_in_memory_position_past_the_id_width_states_no_identifier() {
507        assert_eq!(id_from_index(0), Some(0));
508        assert_eq!(id_from_index(index_from_u32(u32::MAX)), Some(u32::MAX));
509        assert_eq!(id_from_index(index_from_u32(u32::MAX) + 1), None);
510    }
511
512    /// The widening and the narrowing agree on every index the target holds,
513    /// and an offset past the index width states no index.
514    #[test]
515    #[cfg(target_pointer_width = "64")]
516    fn an_offset_past_the_index_width_states_no_index() {
517        assert_eq!(index_from_u64(0), Some(0));
518        assert_eq!(index_from_u64(u64_from_index(usize::MAX)), Some(usize::MAX));
519        assert_eq!(
520            index_from_u64(u64::from(u32::MAX)),
521            Some(index_from_u32(u32::MAX))
522        );
523    }
524
525    #[test]
526    fn a_u32_index_widens_to_the_same_value() {
527        assert_eq!(index_from_u32(0), 0);
528        assert_eq!(
529            index_from_u32(u32::MAX),
530            usize::try_from(u32::MAX).expect("u32 fits usize")
531        );
532    }
533
534    /// The widening is exact at the widest index the target can hold, so the
535    /// conversion states no refusal and needs no sentinel.
536    #[test]
537    fn an_index_widens_to_the_same_value_and_back() {
538        assert_eq!(u64_from_index(0), 0);
539        assert_eq!(u64_from_index(1), 1);
540        let widest = u64_from_index(usize::MAX);
541        assert_eq!(widest, u64::try_from(usize::MAX).expect("usize fits u64"));
542        assert_eq!(usize::try_from(widest), Ok(usize::MAX));
543    }
544
545    #[test]
546    fn counted_reads_only_plausible_lengths() {
547        let payload = [1u8, 0, 0, 0, 2, 0, 0, 0];
548        let mut view = View::over_space(&payload, SpaceId::ROOT);
549        assert_eq!(view.counted(2, 4).map(BoundedCount::get), Some(2));
550        let mut values = [0; 2];
551        for value in &mut values {
552            *value = view.u32_le().expect("two fixture values");
553        }
554        assert_eq!(values, [1, 2]);
555        let view = View::over_space(&payload, SpaceId::ROOT);
556        assert_eq!(view.counted(u64::from(u32::MAX), 4), None);
557    }
558
559    #[test]
560    fn counted_rejects_impossible_counts() {
561        let payload = [0u8; 40];
562        let view = View::over_space(&payload, SpaceId::ROOT);
563        assert_eq!(view.counted(10, 4).map(BoundedCount::get), Some(10));
564        assert_eq!(view.counted(11, 4), None);
565        assert_eq!(bounded_len(u64::MAX, 20, usize::MAX), None);
566    }
567
568    /// The window is the slice the constructor proved, not a range recomputed
569    /// against a wider buffer: `window()` reads the field a constructor stored.
570    #[test]
571    fn the_window_is_the_slice_the_constructor_proved() {
572        let payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
573        let view = View::over_space(&payload, SpaceId::ROOT);
574        assert_eq!(view.window(), payload.as_slice());
575        assert_eq!(view.start(), 0);
576        assert_eq!(view.end(), payload.len());
577        let mut child = view.child(2, 6).expect("contained child");
578        assert_eq!(child.window(), payload.get(2..6).expect("fixture range"));
579        assert_eq!(child.start(), 2);
580        assert_eq!(child.end(), 6);
581        assert_eq!(child.position(), 2);
582        assert_eq!(child.remaining(), 4);
583        assert_eq!(child.take(2), payload.get(2..4));
584        assert_eq!(child.position(), 4);
585        assert_eq!(child.remaining(), 2);
586        assert_eq!(child.window(), payload.get(2..6).expect("fixture range"));
587        assert_eq!(child.take(3), None);
588        assert_eq!(child.position(), 4);
589        let inner = view.child(2, 6).expect("contained child");
590        assert!(inner.child(1, 6).is_none(), "lower bound under the window");
591        assert!(inner.child(2, 7).is_none(), "upper bound past the window");
592        assert!(inner.child(5, 3).is_none(), "inverted range");
593        assert_eq!(inner.child(3, 5).map(View::window), payload.get(3..5));
594    }
595
596    /// The cursor is the split of the window: the unread bytes are the suffix
597    /// the reader has not passed, and the bytes already read are the rest.
598    #[test]
599    fn the_unread_bytes_are_the_window_suffix_the_cursor_has_not_passed() {
600        let payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
601        let mut view = View::over_space(&payload, SpaceId::ROOT)
602            .child(2, 7)
603            .expect("contained child");
604        assert_eq!(view.unread(), payload.get(2..7).expect("fixture range"));
605        assert_eq!(view.read_len(), 0);
606        assert_eq!(view.remaining(), 5);
607        assert_eq!(view.position(), 2);
608        assert_eq!(view.take(3), payload.get(2..5));
609        assert_eq!(view.unread(), payload.get(5..7).expect("fixture range"));
610        assert_eq!(view.read_len(), 3);
611        assert_eq!(view.remaining(), 2);
612        assert_eq!(view.position(), 5);
613        assert_eq!(view.take(3), None, "a refused take states no bytes");
614        assert_eq!(view.unread(), payload.get(5..7).expect("fixture range"));
615        assert_eq!(
616            view.position(),
617            5,
618            "a refused take does not move the cursor"
619        );
620        assert_eq!(view.seek(7), Some(()));
621        assert!(view.unread().is_empty());
622        assert!(view.is_empty());
623        assert_eq!(view.read_len(), 5);
624        assert_eq!(view.position(), 7);
625        assert_eq!(view.seek(2), Some(()));
626        assert_eq!(view.read_len(), 0);
627        assert_eq!(view.remaining(), 5);
628        assert_eq!(view.window(), payload.get(2..7).expect("fixture range"));
629    }
630
631    /// The window's placement and its cursor, as one comparable value: start,
632    /// end, position and remaining.
633    fn bounds(view: View<'_>) -> (usize, usize, usize, usize) {
634        (view.start(), view.end(), view.position(), view.remaining())
635    }
636
637    /// The taken bytes are the child window, placed at the positions the take
638    /// moved between, and a zero-length take states the empty window there.
639    #[test]
640    fn take_child_states_the_taken_range_as_a_window() {
641        let payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
642        let mut view = View::over_space(&payload, SpaceId::ROOT);
643        assert_eq!(view.seek(2), Some(()));
644        assert_eq!(view.take_child(3).map(View::window), payload.get(2..5));
645        assert_eq!(view.position(), 5, "the take advanced by its own count");
646        assert_eq!(view.remaining(), 3);
647        assert_eq!(view.seek(2), Some(()));
648        assert_eq!(
649            view.take_child(3).map(bounds),
650            Some((2, 5, 2, 3)),
651            "the window is the taken range and its cursor is at the start"
652        );
653        assert_eq!(
654            view.take_child(3).map(|window| window.location().offset),
655            Some(u64_from_index(5)),
656            "the result's location is the position the take began at"
657        );
658        assert_eq!(view.position(), 8);
659        assert_eq!(view.take_child(0).map(bounds), Some((8, 8, 8, 0)));
660        assert_eq!(view.take_child(0).map(View::window), payload.get(8..8));
661        assert_eq!(view.position(), 8, "a zero-length take does not advance");
662        assert_eq!(
663            view.req_take_child(0).map(bounds),
664            Ok((8, 8, 8, 0)),
665            "the required mirror takes the empty window at the end"
666        );
667    }
668
669    /// A taken window is an ordinary view of the same space: it carves children
670    /// and takes further windows at the space's own positions.
671    #[test]
672    fn a_taken_window_carves_its_own_children() {
673        let payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
674        let mut view = View::over_space(&payload, SpaceId::ROOT);
675        assert_eq!(view.skip(1), Some(()));
676        let outer = view.take_child(6);
677        assert_eq!(outer.map(bounds), Some((1, 7, 1, 6)));
678        assert_eq!(
679            outer.map(|window| window.child(3, 6).map(bounds)),
680            Some(Some((3, 6, 3, 3))),
681            "a child of a taken window states the space's own bounds"
682        );
683        assert_eq!(
684            outer.map(|mut window| {
685                window.skip(2)?;
686                window.take_child(3).map(View::window)
687            }),
688            Some(payload.get(3..6)),
689            "a taken window of a taken window is the same bytes"
690        );
691        assert_eq!(
692            outer.map(|window| window.child(0, 6).map(bounds)),
693            Some(None),
694            "a lower bound under the taken window is refused"
695        );
696    }
697
698    /// The count is bounded by the unread bytes, exactly as in `take`, and the
699    /// required mirror states the located truncation `req_take` states.
700    #[test]
701    fn a_take_child_past_the_window_leaves_the_view_unmoved() {
702        let payload = [0u8; 8];
703        let mut view = View::over_space(&payload, SpaceId::ROOT);
704        assert_eq!(view.seek(6), Some(()));
705        assert_eq!(view.take_child(3).map(bounds), None);
706        assert_eq!(
707            view.position(),
708            6,
709            "a refused take does not move the cursor"
710        );
711        assert_eq!(view.remaining(), 2);
712        assert_eq!(
713            view.req_take_child(3).map(bounds),
714            Err(ParseError {
715                location: SourceLocation {
716                    space: SpaceId::ROOT,
717                    offset: 6,
718                },
719                kind: ParseErrorKind::UnexpectedEof { needed: 3 },
720                operation: "required_read",
721            })
722        );
723        assert_eq!(view.position(), 6);
724        assert_eq!(
725            view.req_take_child(3).map(bounds).err(),
726            view.req_take(3).err(),
727            "the same located truncation the required take states"
728        );
729        assert_eq!(view.take_child(2).map(bounds), Some((6, 8, 6, 2)));
730    }
731
732    #[test]
733    fn seek_honors_lower_bound() {
734        let payload = [0u8; 8];
735        let mut view = View::over_space(&payload, SpaceId::ROOT)
736            .child(2, 6)
737            .expect("valid child");
738        assert_eq!(view.seek(0), None);
739        assert_eq!(view.seek(2), Some(()));
740        assert_eq!(view.seek(6), Some(()));
741        assert_eq!(view.seek(7), None);
742    }
743
744    #[test]
745    fn seek_to_end_lands_on_the_window_bound_with_nothing_unread() {
746        let payload = [0u8; 8];
747        let mut view = View::over_space(&payload, SpaceId::ROOT)
748            .child(2, 6)
749            .expect("valid child");
750        assert_eq!(view.u8(), Some(0));
751        view.seek_to_end();
752        assert_eq!(view.position(), view.end());
753        assert_eq!(view.remaining(), 0);
754        assert!(view.is_empty());
755        assert_eq!(view.read_len(), view.window().len());
756        assert!(view.unread().is_empty());
757    }
758
759    #[test]
760    fn offset_helpers_match_live_reads() {
761        let mut bytes = Vec::new();
762        bytes.extend_from_slice(&0x0102_u16.to_le_bytes());
763        bytes.extend_from_slice(&0x0304_u16.to_be_bytes());
764        bytes.extend_from_slice(&(-5_i16).to_be_bytes());
765        bytes.extend_from_slice(&1.5_f32.to_le_bytes());
766        assert_eq!(View::u16_le_at(&bytes, 0), Some(0x0102));
767        assert_eq!(View::u16_be_at(&bytes, 2), Some(0x0304));
768        assert_eq!(View::i16_be_at(&bytes, 4), Some(-5));
769        assert_eq!(View::f32_le_at(&bytes, 6), Some(1.5));
770        assert_eq!(View::u16_le_at(&bytes, bytes.len()), None);
771        assert_eq!(View::u24_le_at(&[0x02, 0x01, 0x00], 0), Some(0x0102));
772        assert_eq!(View::u24_be_at(&[0x01, 0x02, 0x03], 0), Some(0x0001_0203));
773        assert_eq!(View::u24_le_at(&[0, 1], 0), None);
774        let mut view = View::over_space(&bytes, SpaceId::ROOT);
775        assert_eq!(view.u16_le(), Some(0x0102));
776        assert_eq!(view.u16_be(), Some(0x0304));
777        assert_eq!(view.i16_be(), Some(-5));
778        assert_eq!(view.f32_le(), Some(1.5));
779    }
780}