Skip to main content

blockchain_zc_parser/
cursor.rs

1//! Zero-copy cursor over a byte slice.
2//!
3//! The [`Cursor`] type advances a shared reference into the input buffer without
4//! ever copying bytes. All returned slices have the lifetime of the original
5//! input data.
6
7use crate::error::{ParseError, ParseResult};
8
9/// Stateful, zero-copy cursor over an immutable byte slice.
10///
11/// All methods advance the cursor in-place and return sub-slices that borrow
12/// from the **original** input — no allocation, no memcpy.
13#[derive(Debug, Clone)]
14pub struct Cursor<'a> {
15    data: &'a [u8],
16    pos: usize,
17}
18
19impl<'a> Cursor<'a> {
20    /// Create a new cursor starting at position 0.
21    #[inline]
22    pub const fn new(data: &'a [u8]) -> Self {
23        Self { data, pos: 0 }
24    }
25
26    /// Current byte offset from the start.
27    #[inline]
28    pub const fn position(&self) -> usize {
29        self.pos
30    }
31
32    /// Number of bytes remaining in the buffer.
33    #[inline]
34    pub const fn remaining(&self) -> usize {
35        self.data.len().saturating_sub(self.pos)
36    }
37
38    /// `true` if there are no more bytes to read.
39    #[inline]
40    pub const fn is_empty(&self) -> bool {
41        self.pos >= self.data.len()
42    }
43
44    /// Return the unread portion as a slice without advancing.
45    #[inline]
46    pub fn as_slice(&self) -> &'a [u8] {
47        debug_assert!(self.pos <= self.data.len());
48        &self.data[self.pos..]
49    }
50
51    /// Borrow exactly `n` bytes and advance the cursor.
52    ///
53    /// This is the core primitive — O(1), zero-copy.
54    #[inline]
55    pub fn read_bytes(&mut self, n: usize) -> ParseResult<&'a [u8]> {
56        let end = self.pos.checked_add(n).ok_or(ParseError::UnexpectedEof {
57            needed: n,
58            available: self.remaining(),
59        })?;
60        if end > self.data.len() {
61            return Err(ParseError::UnexpectedEof {
62                needed: n,
63                available: self.remaining(),
64            });
65        }
66        // SAFETY: bounds checked above
67        let slice = unsafe { self.data.get_unchecked(self.pos..end) };
68        self.pos = end;
69        Ok(slice)
70    }
71
72    /// Read a single byte.
73    #[inline]
74    pub fn read_u8(&mut self) -> ParseResult<u8> {
75        if self.pos >= self.data.len() {
76            return Err(ParseError::UnexpectedEof {
77                needed: 1,
78                available: 0,
79            });
80        }
81        // SAFETY: bounds checked above
82        let b = unsafe { *self.data.get_unchecked(self.pos) };
83        self.pos += 1;
84        Ok(b)
85    }
86
87    /// Read a little-endian `u16`.
88    #[inline]
89    pub fn read_u16_le(&mut self) -> ParseResult<u16> {
90        let bytes = self.read_bytes(2)?;
91        Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
92    }
93
94    /// Read a little-endian `u32`.
95    #[inline]
96    pub fn read_u32_le(&mut self) -> ParseResult<u32> {
97        let bytes = self.read_bytes(4)?;
98        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
99    }
100
101    /// Read a little-endian `i32`.
102    #[inline]
103    pub fn read_i32_le(&mut self) -> ParseResult<i32> {
104        let bytes = self.read_bytes(4)?;
105        Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
106    }
107
108    /// Read a little-endian `u64`.
109    #[inline]
110    pub fn read_u64_le(&mut self) -> ParseResult<u64> {
111        let bytes = self.read_bytes(8)?;
112        Ok(u64::from_le_bytes([
113            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
114        ]))
115    }
116
117    /// Read a little-endian `i64`.
118    #[inline]
119    pub fn read_i64_le(&mut self) -> ParseResult<i64> {
120        let bytes = self.read_bytes(8)?;
121        Ok(i64::from_le_bytes([
122            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
123        ]))
124    }
125
126    /// Parse a Bitcoin-style variable-length integer (1, 3, 5, or 9 bytes).
127    ///
128    /// Returns the decoded value as `u64`.
129    #[inline]
130    pub fn read_varint(&mut self) -> ParseResult<u64> {
131        match self.read_u8()? {
132            v @ 0x00..=0xfc => Ok(v as u64),
133            0xfd => self.read_u16_le().map(|v| v as u64),
134            0xfe => self.read_u32_le().map(|v| v as u64),
135            0xff => self.read_u64_le(),
136        }
137    }
138
139    /// Read a length-prefixed byte slice (varint + raw bytes).
140    ///
141    /// Returns a zero-copy sub-slice of the original input.
142    #[inline]
143    pub fn read_var_bytes(&mut self, max: usize) -> ParseResult<&'a [u8]> {
144        let len_u64 = self.read_varint()?;
145        let len: usize = len_u64
146            .try_into()
147            .map_err(|_| ParseError::IntegerTooLarge { value: len_u64 })?;
148
149        if len > max {
150            return Err(ParseError::OversizedData { size: len, max });
151        }
152        self.read_bytes(len)
153    }
154
155    /// Borrow a fixed-size array from the current position.
156    ///
157    /// Panics at compile-time if `N == 0`.
158    #[inline]
159    pub fn read_array<const N: usize>(&mut self) -> ParseResult<&'a [u8; N]> {
160        let bytes = self.read_bytes(N)?;
161        // SAFETY: read_bytes guarantees exactly N bytes
162        Ok(unsafe { &*(bytes.as_ptr() as *const [u8; N]) })
163    }
164
165    /// Skip `n` bytes without returning them.
166    #[inline]
167    pub fn skip(&mut self, n: usize) -> ParseResult<()> {
168        let _ = self.read_bytes(n)?;
169        Ok(())
170    }
171
172    /// Create a sub-cursor for exactly `n` bytes and advance the outer cursor.
173    ///
174    /// Useful for reading a known-size payload and ensuring it is fully consumed.
175    #[inline]
176    pub fn split(&mut self, n: usize) -> ParseResult<Cursor<'a>> {
177        let slice = self.read_bytes(n)?;
178        Ok(Cursor::new(slice))
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn read_bytes_zero_copy() {
188        let data = [1u8, 2, 3, 4, 5, 6, 7, 8];
189        let mut c = Cursor::new(&data);
190        let slice = c.read_bytes(4).unwrap();
191        // Pointer equality — no copy happened
192        assert_eq!(slice.as_ptr(), data.as_ptr());
193        assert_eq!(slice, &[1, 2, 3, 4]);
194        assert_eq!(c.remaining(), 4);
195    }
196
197    #[test]
198    fn varint_single_byte() {
199        let data = [0xfc];
200        let mut c = Cursor::new(&data);
201        assert_eq!(c.read_varint().unwrap(), 0xfc);
202    }
203
204    #[test]
205    fn varint_two_bytes() {
206        let data = [0xfd, 0x01, 0x00];
207        let mut c = Cursor::new(&data);
208        assert_eq!(c.read_varint().unwrap(), 1);
209    }
210
211    #[test]
212    fn varint_nine_bytes() {
213        let mut data = [0u8; 9];
214        data[0] = 0xff;
215        data[1..].copy_from_slice(&u64::MAX.to_le_bytes());
216        let mut c = Cursor::new(&data);
217        assert_eq!(c.read_varint().unwrap(), u64::MAX);
218    }
219
220    #[test]
221    fn eof_error() {
222        let data = [1u8, 2];
223        let mut c = Cursor::new(&data);
224        assert!(matches!(
225            c.read_bytes(3),
226            Err(ParseError::UnexpectedEof {
227                needed: 3,
228                available: 2
229            })
230        ));
231    }
232}