Skip to main content

netgauze_parse_utils/
reader.rs

1// Copyright (C) 2026-present The NetGauze Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::error::ParseError;
17
18/// A zero-copy, forward-only reader over a borrowed byte slice.
19///
20/// `SliceReader` is `Copy`, and that is deliberate and load-bearing: copying
21/// the reader is a zero-cost checkpoint for speculative parsing / backtracking.
22///
23/// ```ignore
24/// let save = reader;                     // checkpoint: just a pointer + usize
25/// if try_parse(&mut reader).is_err() {
26///     reader = save;                     // rewind; nothing was consumed
27/// }
28/// ```
29///
30/// `offset` is the absolute position from the start of the *original* buffer,
31/// so errors and sub-readers report positions that stay meaningful across
32/// nested parses.
33#[derive(Debug, Copy, Clone, Eq, PartialEq)]
34pub struct SliceReader<'a> {
35    buf: &'a [u8],
36    offset: usize,
37}
38
39impl<'a> SliceReader<'a> {
40    #[inline(always)]
41    pub fn new(buf: &'a [u8]) -> Self {
42        Self { buf, offset: 0 }
43    }
44
45    #[cfg(test)]
46    pub fn new_with_offset(offset: usize, buf: &'a [u8]) -> Self {
47        Self { buf, offset }
48    }
49
50    #[inline(always)]
51    pub fn offset(&self) -> usize {
52        self.offset
53    }
54
55    #[inline(always)]
56    pub fn remaining(&self) -> usize {
57        self.buf.len()
58    }
59
60    #[inline(always)]
61    pub fn is_empty(&self) -> bool {
62        self.buf.is_empty()
63    }
64
65    /// The remaining, not-yet-read bytes.
66    #[inline(always)]
67    pub fn as_slice(&self) -> &'a [u8] {
68        self.buf
69    }
70
71    #[inline(always)]
72    pub fn read_u8(&mut self) -> Result<u8, ParseError> {
73        match self.buf.split_first() {
74            Some((&v, rest)) => {
75                self.buf = rest;
76                self.offset += 1;
77                Ok(v)
78            }
79            None => Err(ParseError::eof(self.offset, 1, 0)),
80        }
81    }
82
83    /// Read a fixed-size array.
84    #[inline(always)]
85    pub fn read_array<const N: usize>(&mut self) -> Result<[u8; N], ParseError> {
86        match self.buf.split_first_chunk::<N>() {
87            Some((chunk, rest)) => {
88                self.buf = rest;
89                self.offset += N;
90                Ok(*chunk)
91            }
92            None => Err(ParseError::eof(self.offset, N, self.buf.len())),
93        }
94    }
95
96    #[inline(always)]
97    pub fn read_u16_be(&mut self) -> Result<u16, ParseError> {
98        self.read_array::<2>().map(u16::from_be_bytes)
99    }
100
101    #[inline(always)]
102    pub fn read_u32_be(&mut self) -> Result<u32, ParseError> {
103        self.read_array::<4>().map(u32::from_be_bytes)
104    }
105
106    #[inline(always)]
107    pub fn read_u64_be(&mut self) -> Result<u64, ParseError> {
108        self.read_array::<8>().map(u64::from_be_bytes)
109    }
110
111    #[inline(always)]
112    pub fn read_u128_be(&mut self) -> Result<u128, ParseError> {
113        self.read_array::<16>().map(u128::from_be_bytes)
114    }
115
116    #[inline(always)]
117    pub fn read_i32_be(&mut self) -> Result<i32, ParseError> {
118        self.read_array::<4>().map(i32::from_be_bytes)
119    }
120
121    #[inline(always)]
122    pub fn read_i64_be(&mut self) -> Result<i64, ParseError> {
123        self.read_array::<8>().map(i64::from_be_bytes)
124    }
125
126    #[inline(always)]
127    pub fn read_f32_be(&mut self) -> Result<f32, ParseError> {
128        self.read_array::<4>().map(f32::from_be_bytes)
129    }
130
131    #[inline(always)]
132    pub fn read_f64_be(&mut self) -> Result<f64, ParseError> {
133        self.read_array::<8>().map(f64::from_be_bytes)
134    }
135
136    /// Read `len` bytes as a borrowed slice.
137    ///
138    /// Non-panicking even for an attacker-controlled `len`: `split_at_checked`
139    /// returns `None` on overrun rather than panicking or overflowing. Callers
140    /// needing owned, zero-copy data should `slice_ref` the result off the
141    /// parent `Bytes`; callers needing an owned copy can `.to_vec()`.
142    #[inline]
143    pub fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], ParseError> {
144        match self.buf.split_at_checked(len) {
145            Some((head, tail)) => {
146                self.buf = tail;
147                self.offset += len;
148                Ok(head)
149            }
150            None => Err(ParseError::eof(self.offset, len, self.buf.len())),
151        }
152    }
153
154    /// Zero-copy sub-reader over the next `len` bytes, with the correct
155    /// absolute offset carried into it for nested error reporting.
156    #[inline]
157    pub fn take_slice(&mut self, len: usize) -> Result<SliceReader<'a>, ParseError> {
158        let start = self.offset;
159        self.read_bytes(len)
160            .map(|buf| SliceReader { buf, offset: start })
161    }
162
163    #[inline(always)]
164    pub fn peek_u8(&mut self) -> Result<u8, ParseError> {
165        match self.buf.split_first() {
166            Some((&v, _rest)) => Ok(v),
167            None => Err(ParseError::eof(self.offset, 1, 0)),
168        }
169    }
170
171    #[inline(always)]
172    pub fn peek_u16_be(&self) -> Result<u16, ParseError> {
173        self.peek_array::<2>().map(u16::from_be_bytes)
174    }
175
176    #[inline(always)]
177    pub fn peek_u32_be(&self) -> Result<u32, ParseError> {
178        self.peek_array::<4>().map(u32::from_be_bytes)
179    }
180
181    /// Peek a fixed-size array without advancing. Takes `&self`.
182    #[inline(always)]
183    pub fn peek_array<const N: usize>(&self) -> Result<[u8; N], ParseError> {
184        self.buf
185            .first_chunk::<N>()
186            .copied()
187            .ok_or_else(|| ParseError::eof(self.offset, N, self.buf.len()))
188    }
189
190    /// Read `len` bytes and left-align them into a zero-padded `[u8; N]`.
191    #[inline]
192    pub fn read_padded<const N: usize>(&mut self, len: usize) -> Result<[u8; N], ParseError> {
193        if len > N {
194            return Err(ParseError::invalid_padding_length(self.offset, len, N));
195        }
196        let src = self.read_bytes(len)?;
197        let mut out = [0u8; N];
198        out[..len].copy_from_slice(src);
199        Ok(out)
200    }
201
202    /// Read a big-endian unsigned integer carried in `len` octets, where `len`
203    /// may be smaller than the type's natural width.
204    ///
205    /// This is the reduced-size encoding of IPFIX (RFC 7011 Section 6.2): a
206    /// value whose abstract type is `unsigned32` may travel in 1 to 4
207    /// octets, and the octets are the *low-order* end of the value. Callers
208    /// wanting a narrower type cast the result down, having already bounded
209    /// `len` themselves.
210    ///
211    /// `len` above 4 cannot be represented and is rejected rather than
212    /// truncated.
213    #[inline]
214    pub fn read_unsigned32_be(&mut self, len: usize) -> Result<u32, ParseError> {
215        Ok(match len {
216            0 => 0,
217            1 => u32::from(self.read_array::<1>()?[0]),
218            2 => u32::from(u16::from_be_bytes(self.read_array::<2>()?)),
219            3 => {
220                let b = self.read_array::<3>()?;
221                u32::from_be_bytes([0, b[0], b[1], b[2]])
222            }
223            4 => u32::from_be_bytes(self.read_array::<4>()?),
224            _ => return Err(ParseError::invalid_padding_length(self.offset, len, 4)),
225        })
226    }
227    /// Read a big-endian unsigned integer carried in `len` octets, where `len`
228    /// may be smaller than the type's natural width.
229    ///
230    /// This is the reduced-size encoding of IPFIX (RFC 7011 Section 6.2): a
231    /// value whose abstract type is `unsigned64` may travel in 1 to 8
232    /// octets, and the octets are the *low-order* end of the value. Callers
233    /// wanting a narrower type cast the result down, having already bounded
234    /// `len` themselves.
235    ///
236    /// `len` above 8 cannot be represented and is rejected rather than
237    /// truncated.
238    #[inline]
239    pub fn read_unsigned64_be(&mut self, len: usize) -> Result<u64, ParseError> {
240        Ok(match len {
241            0 => 0,
242            1 => u64::from(self.read_array::<1>()?[0]),
243            2 => u64::from(u16::from_be_bytes(self.read_array::<2>()?)),
244            3 => {
245                let b = self.read_array::<3>()?;
246                u64::from(u32::from_be_bytes([0, b[0], b[1], b[2]]))
247            }
248            4 => u64::from(u32::from_be_bytes(self.read_array::<4>()?)),
249            5 => {
250                let b = self.read_array::<5>()?;
251                u64::from_be_bytes([0, 0, 0, b[0], b[1], b[2], b[3], b[4]])
252            }
253            6 => {
254                let b = self.read_array::<6>()?;
255                u64::from_be_bytes([0, 0, b[0], b[1], b[2], b[3], b[4], b[5]])
256            }
257            7 => {
258                let b = self.read_array::<7>()?;
259                u64::from_be_bytes([0, b[0], b[1], b[2], b[3], b[4], b[5], b[6]])
260            }
261            8 => u64::from_be_bytes(self.read_array::<8>()?),
262            _ => return Err(ParseError::invalid_padding_length(self.offset, len, 8)),
263        })
264    }
265
266    /// Read a big-endian two's-complement signed integer carried in `len`
267    /// octets, sign-extending it to the full width.
268    ///
269    /// The 32-bit-capped counterpart of [`Self::read_signed64_be`] /
270    /// signed twin of [`Self::read_unsigned32_be`].
271    #[inline]
272    pub fn read_signed32_be(&mut self, len: usize) -> Result<i32, ParseError> {
273        // Read right-aligned and zero-filled (constant-size per arm, see
274        // read_unsigned32_be), then sign-extend by shifting the value's own
275        // top bit up to bit 63 and arithmetic-shifting back down.
276        let v = self.read_unsigned32_be(len)? as i32;
277        let shift = ((4 - len) * 8) & 31; // len == 0 -> 32 & 31 == 0 -> yields 0
278        Ok((v << shift) >> shift)
279    }
280
281    /// Read a big-endian two's-complement signed integer carried in `len`
282    /// octets, sign-extending it to the full width.
283    ///
284    /// The signed counterpart of [`Self::read_unsigned64_be`]. The sign is
285    /// taken from the top bit of the first octet read, so a value shortened
286    /// under IPFIX reduced-size encoding keeps its sign.
287    #[inline]
288    pub fn read_signed64_be(&mut self, len: usize) -> Result<i64, ParseError> {
289        // Read right-aligned and zero-filled (constant-size per arm, see
290        // read_unsigned32_be), then sign-extend by shifting the value's own
291        // top bit up to bit 63 and arithmetic-shifting back down.
292        let v = self.read_unsigned64_be(len)? as i64;
293        let shift = ((8 - len) * 8) & 63; // len == 0 -> 64 & 63 == 0 -> yields 0
294        Ok((v << shift) >> shift)
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301
302    #[test]
303    fn reads_advance_and_track_offset() {
304        let data = [0x00, 0x01, 0x02, 0x03, 0x04];
305        let mut r = SliceReader::new(&data);
306        assert_eq!(r.read_u8(), Ok(0x00));
307        assert_eq!(r.read_u16_be(), Ok(0x0102));
308        assert_eq!(r.offset(), 3);
309        assert_eq!(r.remaining(), 2);
310    }
311
312    #[test]
313    fn eof_reports_offset_needed_available() {
314        let data = [0xAA, 0xBB];
315        let mut r = SliceReader::new(&data);
316        let _ = r.read_u8().unwrap();
317        let err = r.read_u32_be();
318        assert_eq!(err, Err(ParseError::eof(1, 4, 1)));
319        assert_eq!(err.map_err(|x| x.is_incomplete()), Err(true));
320    }
321
322    #[test]
323    fn copy_gives_free_checkpoint() {
324        let data = [1, 2, 3, 4];
325        let mut r = SliceReader::new(&data);
326        let save = r; // checkpoint
327        let _ = r.read_u16_be().unwrap();
328        assert_eq!(r.offset(), 2);
329        r = save; // rewind
330        assert_eq!(r.offset(), 0);
331    }
332
333    #[test]
334    fn peek_does_not_advance() {
335        let data = [0x12, 0x34];
336        let r = SliceReader::new(&data);
337        assert_eq!(r.peek_u16_be(), Ok(0x1234));
338        assert_eq!(r.offset(), 0);
339    }
340
341    #[test]
342    fn take_slice_carries_absolute_offset() {
343        let data = [0, 1, 2, 3, 4, 5];
344        let mut r = SliceReader::new(&data);
345        let _ = r.read_u16_be().unwrap();
346        let sub = r.take_slice(3);
347        assert_eq!(sub, Ok(SliceReader::new_with_offset(2, &[2, 3, 4])));
348    }
349
350    #[test]
351    fn len_equal_to_n_reads_full_no_padding() {
352        let data = [0xDE, 0xAD, 0xBE, 0xEF];
353        let mut r = SliceReader::new(&data);
354        let out = r.read_padded::<4>(4);
355        assert_eq!(out, Ok([0xDE, 0xAD, 0xBE, 0xEF]));
356        assert_eq!(r.offset(), 4);
357        assert_eq!(r.remaining(), 0);
358    }
359
360    #[test]
361    fn short_len_left_aligns_and_zero_pads_tail() {
362        let data = [0xAA, 0xBB, 0xCC];
363        let mut r = SliceReader::new(&data);
364        let out = r.read_padded::<4>(2);
365        assert_eq!(out, Ok([0xAA, 0xBB, 0x00, 0x00]));
366        // advances by `len`, NOT by N — the untouched byte is still readable
367        assert_eq!(r.offset(), 2);
368        assert_eq!(r.as_slice(), &[0xCC]);
369    }
370
371    #[test]
372    fn zero_len_yields_all_zeros_without_advancing() {
373        let data = [0x11, 0x22];
374        let mut r = SliceReader::new(&data);
375        let out = r.read_padded::<4>(0);
376        assert_eq!(out, Ok([0, 0, 0, 0]));
377        assert_eq!(r.offset(), 0);
378        assert_eq!(r.remaining(), 2);
379    }
380
381    #[test]
382    fn len_within_n_but_buffer_too_short_is_eof() {
383        let data = [0xAA]; // only one byte available
384        let mut r = SliceReader::new(&data);
385        let err = r.read_padded::<4>(3);
386        assert_eq!(err, Err(ParseError::eof(0, 3, 1)));
387        assert_eq!(r.offset(), 0); // read_bytes failed before advancing
388    }
389
390    #[test]
391    fn capacity_error_reports_current_offset() {
392        let data = [0x00, 0x00, 0xAA, 0xBB, 0xCC];
393        let mut r = SliceReader::new(&data);
394        let _ = r.read_u16_be().unwrap(); // advance to offset 2
395        let err = r.read_padded::<2>(5);
396        assert_eq!(err, Err(ParseError::invalid_padding_length(2, 5, 2)));
397        assert_eq!(r.offset(), 2); // read_bytes failed before advancing
398    }
399
400    #[test]
401    fn uint_be_right_aligns_shortened_values() {
402        // the same value 0x0000ABCD carried in 2, 3 and 4 octets
403        for wire in [
404            &[0xAB, 0xCD][..],
405            &[0x00, 0xAB, 0xCD],
406            &[0x00, 0x00, 0xAB, 0xCD],
407        ] {
408            let mut r = SliceReader::new(wire);
409            assert_eq!(r.read_unsigned64_be(wire.len()), Ok(0xABCD));
410            assert_eq!(r.remaining(), 0);
411        }
412    }
413
414    #[test]
415    fn uint_be_full_width_matches_fixed_read() {
416        let data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF];
417        let mut a = SliceReader::new(&data);
418        let mut b = SliceReader::new(&data);
419        assert_eq!(a.read_unsigned64_be(8), b.read_u64_be());
420    }
421
422    #[test]
423    fn uint_be_zero_len_reads_nothing() {
424        let data = [0xAA];
425        let mut r = SliceReader::new(&data);
426        assert_eq!(r.read_unsigned64_be(0), Ok(0));
427        assert_eq!(r.offset(), 0);
428    }
429
430    #[test]
431    fn uint_be_rejects_len_beyond_width() {
432        let data = [0u8; 16];
433        let mut r = SliceReader::new(&data);
434        assert_eq!(
435            r.read_unsigned64_be(9),
436            Err(ParseError::invalid_padding_length(0, 9, 8))
437        );
438        assert_eq!(r.offset(), 0); // rejected before consuming
439    }
440
441    #[test]
442    fn uint_be_short_buffer_is_eof() {
443        let data = [0xAA, 0xBB];
444        let mut r = SliceReader::new(&data);
445        assert_eq!(r.read_unsigned64_be(4), Err(ParseError::eof(0, 4, 2)));
446    }
447
448    #[test]
449    fn int_be_sign_extends_shortened_negative() {
450        // -2 (0xFE) shortened to one octet must widen to -2, not 254
451        let data = [0xFE];
452        let mut r = SliceReader::new(&data);
453        assert_eq!(r.read_signed64_be(1), Ok(-2));
454    }
455
456    #[test]
457    fn int_be_keeps_positive_values_unsigned() {
458        let data = [0x7F];
459        let mut r = SliceReader::new(&data);
460        assert_eq!(r.read_signed64_be(1), Ok(127));
461    }
462
463    #[test]
464    fn int_be_full_width_matches_fixed_read() {
465        let data = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE];
466        let mut a = SliceReader::new(&data);
467        let mut b = SliceReader::new(&data);
468        assert_eq!(a.read_signed64_be(8), b.read_i64_be());
469        assert_eq!(a.read_signed64_be(8), Err(ParseError::eof(8, 8, 0)));
470    }
471
472    #[test]
473    fn int_be_zero_len_is_not_negative() {
474        let data = [0xFF];
475        let mut r = SliceReader::new(&data);
476        // no octet read means no sign bit to extend
477        assert_eq!(r.read_signed64_be(0), Ok(0));
478        assert_eq!(r.offset(), 0);
479    }
480
481    #[test]
482    fn uint32_be_right_aligns_shortened_values() {
483        // the same value 0x0000ABCD carried in 2, 3 and 4 octets
484        for wire in [
485            &[0xAB, 0xCD][..],
486            &[0x00, 0xAB, 0xCD],
487            &[0x00, 0x00, 0xAB, 0xCD],
488        ] {
489            let mut r = SliceReader::new(wire);
490            assert_eq!(r.read_unsigned32_be(wire.len()), Ok(0xABCD));
491            assert_eq!(r.remaining(), 0);
492        }
493    }
494
495    #[test]
496    fn uint32_be_full_width_matches_fixed_read() {
497        let data = [0x01, 0x23, 0x45, 0x67];
498        let mut a = SliceReader::new(&data);
499        let mut b = SliceReader::new(&data);
500        assert_eq!(a.read_unsigned32_be(4), b.read_u32_be());
501    }
502
503    #[test]
504    fn uint32_be_zero_len_reads_nothing() {
505        let data = [0xAA];
506        let mut r = SliceReader::new(&data);
507        assert_eq!(r.read_unsigned32_be(0), Ok(0));
508        assert_eq!(r.offset(), 0);
509    }
510
511    #[test]
512    fn uint32_be_rejects_len_beyond_width() {
513        let data = [0u8; 16];
514        let mut r = SliceReader::new(&data);
515        assert_eq!(
516            r.read_unsigned32_be(5),
517            Err(ParseError::invalid_padding_length(0, 5, 4))
518        );
519        assert_eq!(r.offset(), 0); // rejected before consuming
520    }
521
522    #[test]
523    fn uint32_be_short_buffer_is_eof() {
524        let data = [0xAA];
525        let mut r = SliceReader::new(&data);
526        assert_eq!(r.read_unsigned32_be(4), Err(ParseError::eof(0, 4, 1)));
527    }
528
529    #[test]
530    fn int32_be_sign_extends_shortened_negative() {
531        // -2 (0xFE) shortened to one octet must widen to -2, not 254
532        let data = [0xFE];
533        let mut r = SliceReader::new(&data);
534        assert_eq!(r.read_signed32_be(1), Ok(-2));
535    }
536
537    #[test]
538    fn int32_be_keeps_positive_values_unsigned() {
539        let data = [0x7F];
540        let mut r = SliceReader::new(&data);
541        assert_eq!(r.read_signed32_be(1), Ok(127));
542    }
543
544    #[test]
545    fn int32_be_full_width_matches_fixed_read() {
546        let data = [0xFF, 0xFF, 0xFF, 0xFE];
547        let mut a = SliceReader::new(&data);
548        let mut b = SliceReader::new(&data);
549        assert_eq!(a.read_signed32_be(4), b.read_i32_be());
550        assert_eq!(a.read_signed32_be(4), Err(ParseError::eof(4, 4, 0)));
551    }
552
553    #[test]
554    fn int32_be_zero_len_is_not_negative() {
555        let data = [0xFF];
556        let mut r = SliceReader::new(&data);
557        // no octet read means no sign bit to extend
558        assert_eq!(r.read_signed32_be(0), Ok(0));
559        assert_eq!(r.offset(), 0);
560    }
561}