Skip to main content

boon/io/
reader.rs

1use crate::error::{Error, Result};
2
3/// Byte-aligned, forward-scanning cursor over a `&[u8]` buffer.
4///
5/// `ByteReader` is used to walk the **outer** demo command stream — the
6/// sequence of `(cmd, tick, size, body)` tuples that make up a `.dem` file
7/// after the 16-byte file header. All multi-byte integers are little-endian,
8/// and variable-length integers use protobuf-style LEB128 encoding.
9///
10/// For **bit-level** access inside decompressed packet payloads, see
11/// [`BitReader`](crate::io::BitReader).
12pub struct ByteReader<'a> {
13    data: &'a [u8],
14    position: usize,
15}
16
17impl<'a> ByteReader<'a> {
18    /// Create a new reader starting at the beginning of `data`.
19    pub fn new(data: &'a [u8]) -> Self {
20        Self { data, position: 0 }
21    }
22
23    /// Current byte offset from the start of the buffer.
24    #[inline]
25    pub fn position(&self) -> usize {
26        self.position
27    }
28
29    /// Number of bytes between the cursor and the end of the buffer.
30    #[inline]
31    pub fn remaining(&self) -> usize {
32        self.data.len().saturating_sub(self.position)
33    }
34
35    /// Returns `true` when the cursor is at or past the end of the buffer.
36    #[inline]
37    pub fn is_empty(&self) -> bool {
38        self.position >= self.data.len()
39    }
40
41    /// Move the cursor to an absolute byte `position`.
42    ///
43    /// Returns [`Error::Overflow`] if `position` exceeds the buffer length.
44    pub fn seek(&mut self, position: usize) -> Result<()> {
45        if position > self.data.len() {
46            return Err(Error::Overflow {
47                needed: position,
48                available: self.data.len(),
49            });
50        }
51        self.position = position;
52        Ok(())
53    }
54
55    fn check_remaining(&self, n: usize) -> Result<()> {
56        if self.position + n > self.data.len() {
57            return Err(Error::Overflow {
58                needed: n,
59                available: self.remaining(),
60            });
61        }
62        Ok(())
63    }
64
65    /// Read a single byte and advance the cursor.
66    pub fn read_u8(&mut self) -> Result<u8> {
67        self.check_remaining(1)?;
68        let val = self.data[self.position];
69        self.position += 1;
70        Ok(val)
71    }
72
73    /// Read a little-endian `u16` and advance the cursor by 2 bytes.
74    pub fn read_u16(&mut self) -> Result<u16> {
75        self.check_remaining(2)?;
76        let val = u16::from_le_bytes([self.data[self.position], self.data[self.position + 1]]);
77        self.position += 2;
78        Ok(val)
79    }
80
81    /// Read a little-endian `u32` and advance the cursor by 4 bytes.
82    pub fn read_u32(&mut self) -> Result<u32> {
83        self.check_remaining(4)?;
84        let val = u32::from_le_bytes([
85            self.data[self.position],
86            self.data[self.position + 1],
87            self.data[self.position + 2],
88            self.data[self.position + 3],
89        ]);
90        self.position += 4;
91        Ok(val)
92    }
93
94    /// Read a little-endian `i32` and advance the cursor by 4 bytes.
95    pub fn read_i32(&mut self) -> Result<i32> {
96        self.check_remaining(4)?;
97        let val = i32::from_le_bytes([
98            self.data[self.position],
99            self.data[self.position + 1],
100            self.data[self.position + 2],
101            self.data[self.position + 3],
102        ]);
103        self.position += 4;
104        Ok(val)
105    }
106
107    /// Borrow `n` bytes starting at the cursor and advance past them.
108    pub fn read_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
109        self.check_remaining(n)?;
110        let slice = &self.data[self.position..self.position + n];
111        self.position += n;
112        Ok(slice)
113    }
114
115    /// Read a protobuf-style unsigned varint (up to 32 bits / 5 bytes).
116    pub fn read_uvarint32(&mut self) -> Result<u32> {
117        let mut result: u32 = 0;
118        for i in 0..5 {
119            let byte = self.read_u8()? as u32;
120            result |= (byte & 0x7F) << (7 * i);
121            if byte & 0x80 == 0 {
122                return Ok(result);
123            }
124        }
125        Ok(result)
126    }
127
128    /// Read a protobuf-style unsigned varint (up to 64 bits / 10 bytes).
129    pub fn read_uvarint64(&mut self) -> Result<u64> {
130        let mut result: u64 = 0;
131        for i in 0..10 {
132            let byte = self.read_u8()? as u64;
133            result |= (byte & 0x7F) << (7 * i);
134            if byte & 0x80 == 0 {
135                return Ok(result);
136            }
137        }
138        Ok(result)
139    }
140
141    /// Advance the cursor by `n` bytes without reading.
142    pub fn skip(&mut self, n: usize) -> Result<()> {
143        self.check_remaining(n)?;
144        self.position += n;
145        Ok(())
146    }
147
148    /// Returns the full underlying data slice (independent of cursor position).
149    pub fn data(&self) -> &'a [u8] {
150        self.data
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_read_u32() {
160        let data = 0x12345678u32.to_le_bytes();
161        let mut r = ByteReader::new(&data);
162        assert_eq!(r.read_u32().unwrap(), 0x12345678);
163    }
164
165    #[test]
166    fn test_read_varint() {
167        let data = [0xAC, 0x02];
168        let mut r = ByteReader::new(&data);
169        assert_eq!(r.read_uvarint32().unwrap(), 300);
170    }
171
172    #[test]
173    fn test_read_bytes() {
174        let data = [1, 2, 3, 4, 5];
175        let mut r = ByteReader::new(&data);
176        let bytes = r.read_bytes(3).unwrap();
177        assert_eq!(bytes, &[1, 2, 3]);
178        assert_eq!(r.remaining(), 2);
179    }
180
181    #[test]
182    fn test_overflow() {
183        let data = [1];
184        let mut r = ByteReader::new(&data);
185        r.read_u8().unwrap();
186        assert!(r.read_u8().is_err());
187    }
188}