cursor_binary_parser/
binary_cursor.rs

1//! A binary parsing utility that provides a cursor-like interface for reading binary data.
2//! 
3//! This module provides a `BinaryCursor` type that wraps a `std::io::Cursor<T>` where T implements `AsRef<[u8]>`
4//! and adds functionality for parsing various types of binary data, as well as managing a location stack
5//! for temporary position changes.
6//! 
7//! This project is heavily inspired by nom, but with the intention of not consuming the input data.
8//! 
9//! # Examples
10//! 
11//! ```rust
12//! use cursor_binary_parser::binary_cursor::{BinaryCursor, BinaryCursorJump};
13//! 
14//! // Can be used with Vec<u8>
15//! let data = vec![0x42, 0x24, 0x00, 0x01];
16//! let mut cursor = BinaryCursor::new(&data);
17//! 
18//! // Parse a u8
19//! let value = cursor.parse_u8().unwrap();
20//! assert_eq!(value, 0x42);
21//! 
22//! // Can also be used with &[u8]
23//! let slice: &[u8] = &[0x42, 0x24, 0x00, 0x01];
24//! let mut cursor = BinaryCursor::new(slice);
25//! 
26//! // Use BinaryCursorJump for temporary position changes
27//! {
28//!     let mut jump = BinaryCursorJump::new(&mut cursor);
29//!     jump.jump(2).unwrap();
30//!     let value = jump.cursor.parse_u16_le().unwrap();
31//!     assert_eq!(value, 0x0100);
32//! }
33//! // Position is automatically restored after jump
34//! ```
35
36use std::io::{Cursor, Read};
37use thiserror::Error;
38
39// region: Error implementation
40/// Error type for binary cursor operations
41#[derive(Debug, Error)]
42pub enum BinaryCursorError {
43    /// Error that occurs during parsing operations
44    #[error("Parse error: {0}")]
45    ParseError(#[from] std::io::Error),
46}
47
48impl BinaryCursorError {
49    /// Creates a new `BinaryCursorError` from an `io::Error`
50    pub fn from_io_error(error: std::io::Error) -> Self {
51        Self::ParseError(error)
52    }
53}
54// endregion: Error implementation
55
56// region: Cursor implementation
57/// A cursor-like interface for parsing binary data
58/// 
59/// This type provides methods for parsing various types of binary data and managing
60/// a location stack for temporary position changes. It works with any type T that
61/// implements `AsRef<[u8]>`, such as `Vec<u8>`, `&[u8]`, or other byte containers.
62#[derive(Debug)]
63pub struct BinaryCursor<T: AsRef<[u8]>> {
64    /// The underlying cursor containing the binary data
65    pub data: Cursor<T>,
66    /// Stack of saved positions for temporary jumps
67    location_stack: Vec<u32>,
68}
69
70impl<T> BinaryCursor<T>
71where
72    T: AsRef<[u8]>,
73{
74    /// Creates a new `BinaryCursor` from a slice of bytes
75    pub fn new(data: T) -> Self {
76        Self {
77            data: Cursor::new(data),
78            location_stack: vec![],
79        }
80    }
81
82    /// Saves the current position to the location stack
83    pub fn push_location(&mut self) {
84        let pos = self.data.position() as u32;
85        self.location_stack.push(pos);
86    }
87
88    /// Removes and returns the most recently saved position from the location stack
89    pub fn pop_location(&mut self) -> Option<u32> {
90        self.location_stack.pop()
91    }
92
93    /// Restores the most recently saved position from the location stack
94    /// 
95    /// Returns `true` if a position was restored, `false` if the stack was empty
96    pub fn restore_location(&mut self) -> bool {
97        if let Some(pos) = self.location_stack.pop() {
98            self.data.set_position(pos as u64);
99            true
100        } else {
101            false
102        }
103    }
104
105    /// Parses a single u8 from the current position
106    pub fn parse_u8(&mut self) -> Result<u8, BinaryCursorError> {
107        let mut buf = [0u8; 1];
108        self.data.read_exact(&mut buf)?;
109        Ok(buf[0])
110    }
111
112    /// Parses a u16 in little-endian format from the current position
113    pub fn parse_u16_le(&mut self) -> Result<u16, BinaryCursorError> {
114        let mut buf = [0u8; 2];
115        self.data.read_exact(&mut buf)?;
116        Ok(u16::from_le_bytes(buf))
117    }
118
119    /// Parses a u32 in little-endian format from the current position
120    pub fn parse_u32_le(&mut self) -> Result<u32, BinaryCursorError> {
121        let mut buf = [0u8; 4];
122        self.data.read_exact(&mut buf)?;
123        Ok(u32::from_le_bytes(buf))
124    }
125
126    /// Parses an f32 in little-endian format from the current position
127    pub fn parse_f32_le(&mut self) -> Result<f32, BinaryCursorError> {
128        let mut buf = [0u8; 4];
129        self.data.read_exact(&mut buf)?;
130        Ok(f32::from_le_bytes(buf))
131    }
132
133    /// Parses a specified number of bytes from the current position
134    pub fn parse_bytes(&mut self, count: usize) -> Result<Vec<u8>, BinaryCursorError> {
135        let mut buf = vec![0u8; count];
136        self.data.read_exact(&mut buf)?;
137        Ok(buf)
138    }
139
140    /// Returns the current position in the data stream
141    pub fn position(&self) -> u64 {
142        self.data.position()
143    }
144
145    /// Sets the current position in the data stream
146    pub fn set_position(&mut self, pos: u64) {
147        self.data.set_position(pos);
148    }
149
150    /// Parses multiple items using the provided parser function
151    /// 
152    /// This is similar to nom's `count` combinator, but works with the `BinaryCursor` interface.
153    /// 
154    /// # Examples
155    /// 
156    /// ```rust
157    /// use cursor_binary_parser::binary_cursor::BinaryCursor;
158    /// 
159    /// let data = vec![0x01, 0x02, 0x03, 0x04];
160    /// let mut cursor = BinaryCursor::new(data);
161    /// 
162    /// let values = cursor.count(|c| c.parse_u8(), 4).unwrap();
163    /// assert_eq!(values, vec![0x01, 0x02, 0x03, 0x04]);
164    /// ```
165    pub fn count<U, F>(&mut self, mut parser: F, count: usize) -> Result<Vec<U>, BinaryCursorError>
166    where
167        F: FnMut(&mut Self) -> Result<U, BinaryCursorError>,
168    {
169        let mut items = Vec::with_capacity(count);
170        for _ in 0..count {
171            items.push(parser(self)?);
172        }
173        Ok(items)
174    }
175}
176// endregion: Cursor implementation
177
178// region: CursorJump implementation
179/// A helper type for temporary position changes
180/// 
181/// This type provides a way to temporarily change the position of a `BinaryCursor`
182/// and automatically restore it when the `BinaryCursorJump` is dropped.
183/// Works with any type T that implements `AsRef<[u8]>`.
184pub struct BinaryCursorJump<'a, T: AsRef<[u8]>> {
185    /// Reference to the cursor being manipulated
186    pub cursor: &'a mut BinaryCursor<T>,
187}
188
189impl<'a, T> BinaryCursorJump<'a, T> 
190where
191    T: AsRef<[u8]>,
192{
193    /// Creates a new `BinaryCursorJump` for the given cursor
194    pub fn new(cursor: &'a mut BinaryCursor<T>) -> Self {
195        Self { cursor }
196    }
197
198    /// Temporarily jumps to the specified position
199    /// 
200    /// The position will be automatically restored when the `BinaryCursorJump` is dropped.
201    pub fn jump(&mut self, location: u64) -> Result<(), BinaryCursorError> {
202        self.cursor.push_location();
203        self.cursor.set_position(location);
204        Ok(())
205    }
206}
207
208impl<'a, T> Drop for BinaryCursorJump<'a, T>
209where
210    T: AsRef<[u8]>,
211{
212    fn drop(&mut self) {
213        self.cursor.restore_location();
214    }
215}
216// endregion: CursorJump implementation
217
218// region: Tests
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_parse_u8() {
225        let data = vec![0x42, 0x43, 0x44];
226        let mut cursor = BinaryCursor::new(data);
227        assert_eq!(cursor.parse_u8().unwrap(), 0x42);
228        assert_eq!(cursor.position(), 1);
229    }
230
231    #[test]
232    fn test_parse_u16_le() {
233        let data = vec![0x42, 0x24, 0x43, 0x25];
234        let mut cursor = BinaryCursor::new(data);
235        assert_eq!(cursor.parse_u16_le().unwrap(), 0x2442);
236        assert_eq!(cursor.position(), 2);
237    }
238
239    #[test]
240    fn test_parse_u32_le() {
241        let data = vec![0x42, 0x24, 0x00, 0x01, 0x43, 0x25, 0x01, 0x02];
242        let mut cursor = BinaryCursor::new(data);
243        assert_eq!(cursor.parse_u32_le().unwrap(), 0x01002442);
244        assert_eq!(cursor.position(), 4);
245    }
246
247    #[test]
248    fn test_parse_f32_le() {
249        let data = vec![0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x40];
250        let mut cursor = BinaryCursor::new(data);
251        assert_eq!(cursor.parse_f32_le().unwrap(), 1.0);
252        assert_eq!(cursor.position(), 4);
253    }
254
255    #[test]
256    fn test_parse_bytes() {
257        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
258        let mut cursor = BinaryCursor::new(data);
259        assert_eq!(cursor.parse_bytes(4).unwrap(), vec![0x01, 0x02, 0x03, 0x04]);
260        assert_eq!(cursor.position(), 4);
261    }
262
263    #[test]
264    fn test_location_stack() {
265        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
266        let mut cursor = BinaryCursor::new(data);
267
268        cursor.push_location();
269        cursor.set_position(4);
270        assert_eq!(cursor.position(), 4);
271
272        assert!(cursor.restore_location());
273        assert_eq!(cursor.position(), 0);
274    }
275
276    #[test]
277    fn test_binary_cursor_jump() {
278        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
279        let mut cursor = BinaryCursor::new(data);
280
281        {
282            let mut jump = BinaryCursorJump::new(&mut cursor);
283            jump.jump(4).unwrap();
284            assert_eq!(jump.cursor.position(), 4);
285        }
286
287        assert_eq!(cursor.position(), 0u64);
288    }
289
290    #[test]
291    fn test_sequential_parsing() {
292        let data = vec![0x42, 0x24, 0x00, 0x01, 0x43, 0x25, 0x01, 0x02];
293        let mut cursor = BinaryCursor::new(data);
294
295        assert_eq!(cursor.parse_u8().unwrap(), 0x42);
296        assert_eq!(cursor.position(), 1);
297
298        assert_eq!(cursor.parse_u16_le().unwrap(), 0x0024);
299        assert_eq!(cursor.position(), 3);
300
301        assert_eq!(cursor.parse_u32_le().unwrap(), 0x01254301);
302        assert_eq!(cursor.position(), 7);
303
304        assert!(cursor.parse_u8().is_ok());
305        assert_eq!(cursor.position(), 8);
306    }
307
308    #[test]
309    fn test_count() {
310        let data = vec![0x01, 0x02, 0x03, 0x04];
311        let mut cursor = BinaryCursor::new(data);
312        let result = cursor.count(|c| c.parse_u8(), 4).unwrap();
313        assert_eq!(result, vec![0x01, 0x02, 0x03, 0x04]);
314        assert_eq!(cursor.position(), 4);
315    }
316
317    #[test]
318    fn test_pop_location() {
319        let data = vec![0x01, 0x02, 0x03, 0x04];
320        let mut cursor = BinaryCursor::new(data);
321        assert_eq!(cursor.pop_location(), None);
322
323        cursor.push_location();
324        cursor.set_position(2);
325        assert_eq!(cursor.pop_location(), Some(0));
326        assert_eq!(cursor.position(), 2);
327    }
328
329    #[test]
330    fn test_error_handling() {
331        let data = vec![0x42];
332        let mut cursor = BinaryCursor::new(data);
333
334        assert!(cursor.parse_u16_le().is_err());
335        assert!(cursor.parse_u32_le().is_err());
336        assert!(cursor.parse_f32_le().is_err());
337        assert!(cursor.parse_bytes(2).is_err());
338    }
339
340    #[test]
341    fn test_restore_location() {
342        let data = vec![0x01, 0x02, 0x03, 0x04];
343        let mut cursor = BinaryCursor::new(data);
344
345        cursor.push_location();
346        cursor.set_position(2);
347        assert_eq!(cursor.position(), 2);
348
349        assert!(cursor.restore_location());
350        assert_eq!(cursor.position(), 0);
351
352        assert!(!cursor.restore_location());
353    }
354
355    #[test]
356    fn test_error_conversion() {
357        use std::io::{Error, ErrorKind};
358        let io_error = Error::new(ErrorKind::UnexpectedEof, "test error");
359        let cursor_error = BinaryCursorError::from_io_error(io_error);
360        match cursor_error {
361            BinaryCursorError::ParseError(_) => (),
362        }
363    }
364}
365// endregion: Tests