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}