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;
8
9/// Converts a declared element count into a safe `Vec` capacity.
10///
11/// Returns `None` unless `count * element_size <= remaining`, i.e. unless
12/// the declared elements could actually be present in the unread input.
13/// `element_size` must be the minimum encoded size of one element and must
14/// be nonzero.
15pub fn bounded_len(count: u64, element_size: usize, remaining: usize) -> Option<usize> {
16    if element_size == 0 {
17        return None;
18    }
19    let count = usize::try_from(count).ok()?;
20    let bytes = count.checked_mul(element_size)?;
21    (bytes <= remaining).then_some(count)
22}
23
24/// A count proven to fit in the unread input.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct BoundedCount(usize);
27
28impl BoundedCount {
29    /// Returns the proven element count.
30    pub fn get(self) -> usize {
31        self.0
32    }
33}
34
35/// A bounded window over one address space.
36#[derive(Debug, Clone, Copy)]
37pub struct View<'a> {
38    bytes: &'a [u8],
39    space: SpaceId,
40    start: usize,
41    end: usize,
42    position: usize,
43}
44
45impl<'a> View<'a> {
46    /// Creates a full-window view over an entire space buffer.
47    pub(crate) fn over_space(bytes: &'a [u8], space: SpaceId) -> View<'a> {
48        View {
49            bytes,
50            space,
51            start: 0,
52            end: bytes.len(),
53            position: 0,
54        }
55    }
56
57    /// Bounded window over retained bytes that left the live space chain.
58    ///
59    /// Prefer views returned by [`DecodeContext`](super::DecodeContext). This
60    /// constructor exists for opportunistic scanners over owned payloads that
61    /// were copied out of the arena; locations report [`SpaceId::ROOT`].
62    pub fn over_retained(bytes: &'a [u8]) -> View<'a> {
63        Self::over_space(bytes, SpaceId::ROOT)
64    }
65
66    /// Returns the space this view navigates.
67    pub(crate) fn space(self) -> SpaceId {
68        self.space
69    }
70
71    /// Returns the absolute position within the space.
72    pub fn position(self) -> usize {
73        self.position
74    }
75
76    /// Returns the window's inclusive lower bound.
77    pub fn start(self) -> usize {
78        self.start
79    }
80
81    /// Returns the window's exclusive upper bound.
82    pub fn end(self) -> usize {
83        self.end
84    }
85
86    /// Returns the number of unread bytes before the window's end.
87    pub fn remaining(self) -> usize {
88        self.end.saturating_sub(self.position)
89    }
90
91    /// Returns whether all bounded bytes have been read.
92    pub fn is_empty(self) -> bool {
93        self.remaining() == 0
94    }
95
96    /// Returns this view's current source location.
97    pub fn location(self) -> SourceLocation {
98        SourceLocation {
99            space: self.space,
100            offset: self.position as u64,
101        }
102    }
103
104    /// Returns a source location at an absolute offset in this view's space.
105    pub fn location_at(self, offset: usize) -> SourceLocation {
106        SourceLocation {
107            space: self.space,
108            offset: offset as u64,
109        }
110    }
111
112    /// Returns the readable window `start..end` as a slice.
113    pub fn window(self) -> &'a [u8] {
114        self.bytes.get(self.start..self.end).unwrap_or_default()
115    }
116
117    /// Takes `count` bytes, advancing only on success.
118    pub fn take(&mut self, count: usize) -> Option<&'a [u8]> {
119        let end = self.position.checked_add(count)?;
120        if end > self.end {
121            return None;
122        }
123        let bytes = self.bytes.get(self.position..end)?;
124        self.position = end;
125        Some(bytes)
126    }
127
128    /// Takes a fixed-size byte array, advancing only on success.
129    pub fn array<const N: usize>(&mut self) -> Option<[u8; N]> {
130        self.take(N)?.try_into().ok()
131    }
132
133    /// Skips exactly `count` bytes.
134    pub fn skip(&mut self, count: usize) -> Option<()> {
135        self.take(count).map(|_| ())
136    }
137
138    /// Moves to an absolute offset, honoring the stored lower bound.
139    pub fn seek(&mut self, position: usize) -> Option<()> {
140        (self.start <= position && position <= self.end).then(|| self.position = position)
141    }
142
143    /// Reads a single byte.
144    pub fn u8(&mut self) -> Option<u8> {
145        self.array::<1>().map(|[value]| value)
146    }
147
148    /// Returns an exactly contained child window.
149    pub fn child(self, start: usize, end: usize) -> Option<View<'a>> {
150        if self.start <= start && start <= end && end <= self.end {
151            Some(View {
152                bytes: self.bytes,
153                space: self.space,
154                start,
155                end,
156                position: start,
157            })
158        } else {
159            None
160        }
161    }
162
163    /// Proves a declared element count could fit in the unread bytes.
164    pub fn counted(self, count: u64, min_element_size: usize) -> Option<BoundedCount> {
165        bounded_len(count, min_element_size, self.remaining()).map(BoundedCount)
166    }
167
168    /// Reads `count` elements of at least `element_size` encoded bytes each.
169    ///
170    /// The count is validated with [`View::counted`] before any allocation, and
171    /// the reader closure's first failure aborts the read.
172    pub fn read_counted<T>(
173        &mut self,
174        count: u64,
175        element_size: usize,
176        mut read: impl FnMut(&mut Self) -> Option<T>,
177    ) -> Option<Vec<T>> {
178        let count = self.counted(count, element_size)?.get();
179        let mut values = Vec::with_capacity(count);
180        for _ in 0..count {
181            values.push(read(self)?);
182        }
183        Some(values)
184    }
185
186    /// Decodes `count` UTF-16LE code units from the current position.
187    ///
188    /// Strict `from_utf16`; unpaired surrogates and a truncated window yield
189    /// `None`. Does not advance on failure.
190    pub fn utf16_le(&mut self, count: usize) -> Option<String> {
191        let units = self.read_counted(u64::try_from(count).ok()?, 2, View::u16_le)?;
192        String::from_utf16(&units).ok()
193    }
194
195    /// Decodes `count` UTF-16LE code units at `offset` and returns the string
196    /// and end offset.
197    ///
198    /// Sequential readers should keep a live `View` and call [`View::utf16_le`]
199    /// instead.
200    pub fn utf16le_at(bytes: &[u8], offset: usize, count: usize) -> Option<(String, usize)> {
201        let mut view = View::over_retained(bytes);
202        view.seek(offset)?;
203        let units = view.read_counted(u64::try_from(count).ok()?, 2, View::u16_le)?;
204        Some((String::from_utf16(&units).ok()?, view.position()))
205    }
206
207    /// Builds an unexpected-eof error from the view's current state.
208    fn eof(self, needed: u64) -> ParseError {
209        ParseError {
210            location: self.location(),
211            kind: ParseErrorKind::UnexpectedEof {
212                needed,
213                remaining: self.remaining() as u64,
214            },
215            operation: "required_read",
216        }
217    }
218
219    /// Required-read mirror of [`View::take`].
220    pub fn req_take(&mut self, count: usize) -> Result<&'a [u8], ParseError> {
221        match self.take(count) {
222            Some(bytes) => Ok(bytes),
223            None => Err(self.eof(count as u64)),
224        }
225    }
226
227    /// Required-read mirror of [`View::u8`].
228    pub fn req_u8(&mut self) -> Result<u8, ParseError> {
229        match self.u8() {
230            Some(value) => Ok(value),
231            None => Err(self.eof(1)),
232        }
233    }
234}
235
236macro_rules! view_readers {
237    ($(($probe:ident, $req:ident, $at:ident, $ty:ty, $conv:ident, $size:literal)),* $(,)?) => {
238        impl View<'_> {
239            $(
240                #[doc = concat!("Probe read of a `", stringify!($ty), "` via `", stringify!($conv), "`.")]
241                pub fn $probe(&mut self) -> Option<$ty> {
242                    self.array::<$size>().map(<$ty>::$conv)
243                }
244
245                #[doc = concat!("Required-read mirror of [`View::", stringify!($probe), "`].")]
246                pub fn $req(&mut self) -> Result<$ty, ParseError> {
247                    match self.array::<$size>() {
248                        Some(bytes) => Ok(<$ty>::$conv(bytes)),
249                        None => Err(self.eof($size)),
250                    }
251                }
252
253                #[doc = concat!(
254                    "Offset probe of a `",
255                    stringify!($ty),
256                    "` over retained bytes. Sequential readers should keep a live `View` and call [`View::",
257                    stringify!($probe),
258                    "`] instead."
259                )]
260                pub fn $at(bytes: &[u8], offset: usize) -> Option<$ty> {
261                    let mut view = View::over_retained(bytes);
262                    view.seek(offset)?;
263                    view.$probe()
264                }
265            )*
266        }
267    };
268}
269
270view_readers!(
271    (u16_le, req_u16_le, u16_le_at, u16, from_le_bytes, 2),
272    (i16_le, req_i16_le, i16_le_at, i16, from_le_bytes, 2),
273    (u32_le, req_u32_le, u32_le_at, u32, from_le_bytes, 4),
274    (i32_le, req_i32_le, i32_le_at, i32, from_le_bytes, 4),
275    (u64_le, req_u64_le, u64_le_at, u64, from_le_bytes, 8),
276    (i64_le, req_i64_le, i64_le_at, i64, from_le_bytes, 8),
277    (f32_le, req_f32_le, f32_le_at, f32, from_le_bytes, 4),
278    (f64_le, req_f64_le, f64_le_at, f64, from_le_bytes, 8),
279    (u16_be, req_u16_be, u16_be_at, u16, from_be_bytes, 2),
280    (i16_be, req_i16_be, i16_be_at, i16, from_be_bytes, 2),
281    (u32_be, req_u32_be, u32_be_at, u32, from_be_bytes, 4),
282    (i32_be, req_i32_be, i32_be_at, i32, from_be_bytes, 4),
283    (u64_be, req_u64_be, u64_be_at, u64, from_be_bytes, 8),
284    (i64_be, req_i64_be, i64_be_at, i64, from_be_bytes, 8),
285    (f32_be, req_f32_be, f32_be_at, f32, from_be_bytes, 4),
286    (f64_be, req_f64_be, f64_be_at, f64, from_be_bytes, 8),
287);
288
289#[cfg(test)]
290mod tests {
291    use super::{bounded_len, BoundedCount, View};
292    use crate::decode::space::SpaceId;
293
294    #[test]
295    fn read_counted_allocates_only_plausible_lengths() {
296        let payload = [1u8, 0, 0, 0, 2, 0, 0, 0];
297        let mut view = View::over_space(&payload, SpaceId::ROOT);
298        let values = view
299            .read_counted(2, 4, View::u32_le)
300            .expect("two fixture values");
301        assert_eq!(values, [1, 2]);
302        let mut view = View::over_space(&payload, SpaceId::ROOT);
303        assert_eq!(
304            view.read_counted(u64::from(u32::MAX), 4, View::u32_le),
305            None
306        );
307    }
308
309    #[test]
310    fn counted_rejects_impossible_counts() {
311        let payload = [0u8; 40];
312        let view = View::over_space(&payload, SpaceId::ROOT);
313        assert_eq!(view.counted(10, 4).map(BoundedCount::get), Some(10));
314        assert_eq!(view.counted(11, 4), None);
315        assert_eq!(bounded_len(u64::MAX, 20, usize::MAX), None);
316    }
317
318    #[test]
319    fn seek_honors_lower_bound() {
320        let payload = [0u8; 8];
321        let mut view = View::over_space(&payload, SpaceId::ROOT)
322            .child(2, 6)
323            .expect("valid child");
324        assert_eq!(view.seek(0), None);
325        assert_eq!(view.seek(2), Some(()));
326        assert_eq!(view.seek(6), Some(()));
327        assert_eq!(view.seek(7), None);
328    }
329
330    #[test]
331    fn offset_helpers_match_live_reads() {
332        let mut bytes = Vec::new();
333        bytes.extend_from_slice(&0x0102_u16.to_le_bytes());
334        bytes.extend_from_slice(&0x0304_u16.to_be_bytes());
335        bytes.extend_from_slice(&(-5_i16).to_be_bytes());
336        bytes.extend_from_slice(&1.5_f32.to_le_bytes());
337        assert_eq!(View::u16_le_at(&bytes, 0), Some(0x0102));
338        assert_eq!(View::u16_be_at(&bytes, 2), Some(0x0304));
339        assert_eq!(View::i16_be_at(&bytes, 4), Some(-5));
340        assert_eq!(View::f32_le_at(&bytes, 6), Some(1.5));
341        assert_eq!(View::u16_le_at(&bytes, bytes.len()), None);
342        let mut view = View::over_space(&bytes, SpaceId::ROOT);
343        assert_eq!(view.u16_le(), Some(0x0102));
344        assert_eq!(view.u16_be(), Some(0x0304));
345        assert_eq!(view.i16_be(), Some(-5));
346        assert_eq!(view.f32_le(), Some(1.5));
347    }
348
349    #[test]
350    fn utf16le_matches_le_helper() {
351        assert_eq!(
352            View::utf16le_at(b"A\0B\0", 0, 2),
353            Some(("AB".to_string(), 4))
354        );
355        assert_eq!(View::utf16le_at(b"A\0B\0", 0, 3), None);
356        let mut view = View::over_space(b"A\0B\0", SpaceId::ROOT);
357        assert_eq!(view.utf16_le(2), Some("AB".to_string()));
358        assert_eq!(view.position(), 4);
359    }
360}