Skip to main content

gpg_inspector_lib/
stream.rs

1//! Binary stream reader for parsing GPG packet data.
2//!
3//! The [`ByteStream`] struct provides position-tracked reading of binary data
4//! with support for slicing (creating sub-streams) and OpenPGP-specific
5//! data formats like multi-precision integers and variable-length values.
6
7use std::sync::Arc;
8
9use crate::error::{Error, Result};
10
11/// A position-tracked binary stream reader.
12///
13/// `ByteStream` wraps binary data and provides sequential reading operations
14/// while tracking the current position. It supports creating slices (views)
15/// into the data for parsing nested structures without copying.
16///
17/// # Design
18///
19/// The stream uses `Arc<[u8]>` internally, allowing cheap cloning and slicing
20/// without data duplication. Position tracking is relative to the slice,
21/// with `abs_pos()` providing the absolute position in the original data.
22///
23/// # Example
24///
25/// ```
26/// use gpg_inspector_lib::ByteStream;
27///
28/// let data = vec![0x01, 0x02, 0x03, 0x04];
29/// let mut stream = ByteStream::new(data);
30///
31/// assert_eq!(stream.octet().unwrap(), 0x01);
32/// assert_eq!(stream.pos(), 1);
33/// assert_eq!(stream.remaining(), 3);
34/// ```
35#[derive(Clone)]
36pub struct ByteStream {
37    bytes: Arc<[u8]>,
38    start: usize,
39    end: usize,
40    pos: usize,
41}
42
43impl ByteStream {
44    /// Creates a new stream from a vector of bytes.
45    ///
46    /// The stream will own the data and start reading from position 0.
47    pub fn new(bytes: Vec<u8>) -> Self {
48        let end = bytes.len();
49        Self {
50            bytes: bytes.into(),
51            start: 0,
52            end,
53            pos: 0,
54        }
55    }
56
57    /// Creates a new stream from an `Arc<[u8]>`.
58    ///
59    /// Useful when sharing the same data across multiple streams or
60    /// when the data is already in an `Arc`.
61    pub fn from_arc(bytes: Arc<[u8]>) -> Self {
62        let end = bytes.len();
63        Self {
64            bytes,
65            start: 0,
66            end,
67            pos: 0,
68        }
69    }
70
71    /// Creates a slice (view) into this stream's data.
72    ///
73    /// The slice shares the underlying data but has its own position tracker
74    /// starting at 0. The `start` and `end` parameters are relative to the
75    /// current stream's bounds.
76    ///
77    /// This is useful for parsing nested packet structures where you want
78    /// to limit reads to a specific byte range.
79    pub fn slice(&self, start: usize, end: usize) -> Self {
80        let abs_start = self.start + start;
81        let abs_end = (self.start + end).min(self.end);
82        Self {
83            bytes: Arc::clone(&self.bytes),
84            start: abs_start,
85            end: abs_end,
86            pos: 0,
87        }
88    }
89
90    /// Returns the current position within this stream (relative to start).
91    pub fn pos(&self) -> usize {
92        self.pos
93    }
94
95    /// Returns the absolute position in the original data.
96    ///
97    /// For slices, this accounts for the slice's offset from the beginning
98    /// of the original data.
99    pub fn abs_pos(&self) -> usize {
100        self.start + self.pos
101    }
102
103    /// Returns the number of bytes remaining to read.
104    pub fn remaining(&self) -> usize {
105        self.end.saturating_sub(self.start + self.pos)
106    }
107
108    /// Returns `true` if there are no more bytes to read.
109    pub fn is_empty(&self) -> bool {
110        self.remaining() == 0
111    }
112
113    /// Returns the total length of this stream (or slice).
114    pub fn len(&self) -> usize {
115        self.end - self.start
116    }
117
118    /// Reads a single byte and advances the position.
119    ///
120    /// # Errors
121    ///
122    /// Returns `Error::UnexpectedEnd` if no bytes remain.
123    pub fn octet(&mut self) -> Result<u8> {
124        if self.start + self.pos >= self.end {
125            return Err(Error::UnexpectedEnd(self.abs_pos()));
126        }
127        let byte = self.bytes[self.start + self.pos];
128        self.pos += 1;
129        Ok(byte)
130    }
131
132    /// Returns the next byte without advancing the position.
133    ///
134    /// Returns `None` if no bytes remain.
135    pub fn peek(&self) -> Option<u8> {
136        if self.start + self.pos >= self.end {
137            None
138        } else {
139            Some(self.bytes[self.start + self.pos])
140        }
141    }
142
143    /// Reads a big-endian 16-bit unsigned integer.
144    ///
145    /// # Errors
146    ///
147    /// Returns `Error::UnexpectedEnd` if fewer than 2 bytes remain.
148    pub fn uint16(&mut self) -> Result<u16> {
149        let b1 = self.octet()? as u16;
150        let b2 = self.octet()? as u16;
151        Ok((b1 << 8) | b2)
152    }
153
154    /// Reads a big-endian 32-bit unsigned integer.
155    ///
156    /// # Errors
157    ///
158    /// Returns `Error::UnexpectedEnd` if fewer than 4 bytes remain.
159    pub fn uint32(&mut self) -> Result<u32> {
160        let b1 = self.octet()? as u32;
161        let b2 = self.octet()? as u32;
162        let b3 = self.octet()? as u32;
163        let b4 = self.octet()? as u32;
164        Ok((b1 << 24) | (b2 << 16) | (b3 << 8) | b4)
165    }
166
167    /// Reads `count` bytes and returns them as a vector.
168    ///
169    /// # Errors
170    ///
171    /// Returns `Error::UnexpectedEnd` if fewer than `count` bytes remain.
172    pub fn bytes(&mut self, count: usize) -> Result<Vec<u8>> {
173        if self.remaining() < count {
174            return Err(Error::UnexpectedEnd(self.abs_pos()));
175        }
176        let start = self.start + self.pos;
177        let result = self.bytes[start..start + count].to_vec();
178        self.pos += count;
179        Ok(result)
180    }
181
182    /// Reads `count` bytes and returns them as an uppercase hex string.
183    ///
184    /// # Errors
185    ///
186    /// Returns `Error::UnexpectedEnd` if fewer than `count` bytes remain.
187    pub fn hex(&mut self, count: usize) -> Result<String> {
188        let bytes = self.bytes(count)?;
189        Ok(bytes.iter().map(|b| format!("{:02X}", b)).collect())
190    }
191
192    /// Reads `count` bytes and interprets them as UTF-8.
193    ///
194    /// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
195    ///
196    /// # Errors
197    ///
198    /// Returns `Error::UnexpectedEnd` if fewer than `count` bytes remain.
199    pub fn utf8(&mut self, count: usize) -> Result<String> {
200        let bytes = self.bytes(count)?;
201        Ok(String::from_utf8_lossy(&bytes).into_owned())
202    }
203
204    /// Reads all remaining bytes and returns them as a vector.
205    ///
206    /// After this call, `remaining()` will return 0.
207    pub fn rest(&mut self) -> Vec<u8> {
208        let start = self.start + self.pos;
209        let result = self.bytes[start..self.end].to_vec();
210        self.pos = self.end - self.start;
211        result
212    }
213
214    /// Reads all remaining bytes and returns them as an uppercase hex string.
215    pub fn rest_as_hex(&mut self) -> String {
216        let bytes = self.rest();
217        bytes.iter().map(|b| format!("{:02X}", b)).collect()
218    }
219
220    /// Reads an OpenPGP Multi-Precision Integer (MPI).
221    ///
222    /// MPIs are stored as a 16-bit big-endian bit count followed by
223    /// the integer bytes (big-endian, minimum length for the value).
224    ///
225    /// Returns the bit length and the value as a hex string.
226    ///
227    /// # Errors
228    ///
229    /// Returns `Error::UnexpectedEnd` if the data is truncated.
230    pub fn multi_precision_integer(&mut self) -> Result<(u16, String)> {
231        let bit_length = self.uint16()?;
232        let byte_length = bit_length.div_ceil(8) as usize;
233        let hex = self.hex(byte_length)?;
234        Ok((bit_length, hex))
235    }
236
237    /// Reads an OpenPGP variable-length value.
238    ///
239    /// This is the new-format packet length encoding:
240    /// - 0-191: one byte, literal value
241    /// - 192-254: two bytes, `((first - 192) << 8) + second + 192`
242    /// - 255: five bytes, 32-bit big-endian length
243    ///
244    /// # Errors
245    ///
246    /// Returns `Error::UnexpectedEnd` if the data is truncated.
247    pub fn variable_length(&mut self) -> Result<usize> {
248        let first = self.octet()? as usize;
249        if first < 192 {
250            Ok(first)
251        } else if first < 255 {
252            let second = self.octet()? as usize;
253            Ok(((first - 192) << 8) + second + 192)
254        } else {
255            Ok(self.uint32()? as usize)
256        }
257    }
258
259    /// Skips `count` bytes without returning them.
260    ///
261    /// # Errors
262    ///
263    /// Returns `Error::UnexpectedEnd` if fewer than `count` bytes remain.
264    pub fn skip(&mut self, count: usize) -> Result<()> {
265        if self.remaining() < count {
266            return Err(Error::UnexpectedEnd(self.abs_pos()));
267        }
268        self.pos += count;
269        Ok(())
270    }
271
272    /// Returns a reference to all bytes in this stream's range.
273    ///
274    /// Unlike `rest()`, this does not consume the bytes or advance the position.
275    pub fn all_bytes(&self) -> &[u8] {
276        &self.bytes[self.start..self.end]
277    }
278}