Skip to main content

sim_codec_classfile/
bytes.rs

1//! Bounded, located byte lanes used by the classfile grammar.
2
3use core::fmt;
4
5/// A precise byte-lane failure category.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum ByteErrorKind {
8    /// The input ended before the requested value was complete.
9    Truncated,
10    /// Offset or length arithmetic overflowed.
11    LengthOverflow,
12    /// A declared or produced value exceeded its allocation budget.
13    BudgetExceeded,
14    /// Modified UTF-8 used a longer representation than necessary.
15    OverlongModifiedUtf8,
16    /// Modified UTF-8 contained a literal zero byte.
17    IllegalZero,
18    /// Modified UTF-8 contained an invalid byte sequence.
19    InvalidModifiedUtf8,
20    /// Modified UTF-8 contained an unpaired UTF-16 surrogate.
21    MalformedSurrogate,
22}
23
24/// A byte-lane error located at an absolute input or output offset.
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct ByteError {
27    /// Stable machine-matchable failure category.
28    pub kind: ByteErrorKind,
29    /// Absolute byte offset at which the failure was detected.
30    pub offset: usize,
31    /// Human-readable context.
32    pub message: String,
33}
34
35impl ByteError {
36    pub(crate) fn new(kind: ByteErrorKind, offset: usize, message: impl Into<String>) -> Self {
37        Self {
38            kind,
39            offset,
40            message: message.into(),
41        }
42    }
43}
44
45impl fmt::Display for ByteError {
46    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(formatter, "{} at byte {}", self.message, self.offset)
48    }
49}
50
51impl std::error::Error for ByteError {}
52
53/// A zero-copy big-endian reader confined to one declared byte region.
54#[derive(Clone, Debug)]
55pub struct ByteReader<'a> {
56    bytes: &'a [u8],
57    position: usize,
58    origin: usize,
59    allocation_budget: usize,
60}
61
62impl<'a> ByteReader<'a> {
63    /// Construct a reader whose owned outputs may contain at most `allocation_budget` bytes/items.
64    pub fn new(bytes: &'a [u8], allocation_budget: usize) -> Self {
65        Self {
66            bytes,
67            position: 0,
68            origin: 0,
69            allocation_budget,
70        }
71    }
72
73    fn with_origin(bytes: &'a [u8], allocation_budget: usize, origin: usize) -> Self {
74        Self {
75            bytes,
76            position: 0,
77            origin,
78            allocation_budget,
79        }
80    }
81
82    /// Current absolute byte offset.
83    pub fn offset(&self) -> usize {
84        self.origin + self.position
85    }
86
87    /// Bytes remaining in this reader's declared region.
88    pub fn remaining(&self) -> usize {
89        self.bytes.len() - self.position
90    }
91
92    /// Allocation budget inherited by values decoded from this region.
93    pub fn allocation_budget(&self) -> usize {
94        self.allocation_budget
95    }
96
97    /// Reject an allocation before reserving it if its declared size exceeds the budget.
98    pub fn preflight_allocation(&self, amount: usize) -> Result<(), ByteError> {
99        if amount > self.allocation_budget {
100            return Err(ByteError::new(
101                ByteErrorKind::BudgetExceeded,
102                self.offset(),
103                format!(
104                    "declared allocation {amount} exceeds budget {}",
105                    self.allocation_budget
106                ),
107            ));
108        }
109        Ok(())
110    }
111
112    /// Read one byte.
113    pub fn read_u1(&mut self) -> Result<u8, ByteError> {
114        Ok(self.take(1)?[0])
115    }
116
117    /// Read an unsigned big-endian two-byte integer.
118    pub fn read_u2(&mut self) -> Result<u16, ByteError> {
119        let value: [u8; 2] = self.take(2)?.try_into().expect("exact length");
120        Ok(u16::from_be_bytes(value))
121    }
122
123    /// Read an unsigned big-endian four-byte integer.
124    pub fn read_u4(&mut self) -> Result<u32, ByteError> {
125        let value: [u8; 4] = self.take(4)?.try_into().expect("exact length");
126        Ok(u32::from_be_bytes(value))
127    }
128
129    /// Borrow exactly `length` bytes.
130    pub fn take(&mut self, length: usize) -> Result<&'a [u8], ByteError> {
131        let start = self.position;
132        let end = start.checked_add(length).ok_or_else(|| {
133            ByteError::new(
134                ByteErrorKind::LengthOverflow,
135                self.offset(),
136                "byte length overflow",
137            )
138        })?;
139        let result = self.bytes.get(start..end).ok_or_else(|| {
140            ByteError::new(
141                ByteErrorKind::Truncated,
142                self.origin + self.bytes.len(),
143                format!("needed {length} bytes, only {} remain", self.remaining()),
144            )
145        })?;
146        self.position = end;
147        Ok(result)
148    }
149
150    /// Create a child reader bounded to exactly `length` bytes and advance the parent.
151    pub fn sub_reader(&mut self, length: usize) -> Result<ByteReader<'a>, ByteError> {
152        let origin = self.offset();
153        let bytes = self.take(length)?;
154        Ok(Self::with_origin(bytes, self.allocation_budget, origin))
155    }
156}
157
158/// A checked big-endian writer with a hard output budget.
159#[derive(Clone, Debug)]
160pub struct ByteWriter {
161    bytes: Vec<u8>,
162    budget: usize,
163}
164
165impl ByteWriter {
166    /// Construct an empty writer that can produce at most `budget` bytes.
167    pub fn new(budget: usize) -> Self {
168        Self {
169            bytes: Vec::new(),
170            budget,
171        }
172    }
173
174    fn reserve_for(&mut self, additional: usize) -> Result<(), ByteError> {
175        let target = self.bytes.len().checked_add(additional).ok_or_else(|| {
176            ByteError::new(
177                ByteErrorKind::LengthOverflow,
178                self.bytes.len(),
179                "output length overflow",
180            )
181        })?;
182        if target > self.budget {
183            return Err(ByteError::new(
184                ByteErrorKind::BudgetExceeded,
185                self.bytes.len(),
186                format!("output length {target} exceeds budget {}", self.budget),
187            ));
188        }
189        self.bytes.try_reserve_exact(additional).map_err(|error| {
190            ByteError::new(
191                ByteErrorKind::BudgetExceeded,
192                self.bytes.len(),
193                format!("output allocation failed: {error}"),
194            )
195        })
196    }
197
198    /// Write one byte.
199    pub fn write_u1(&mut self, value: u8) -> Result<(), ByteError> {
200        self.write_bytes(&[value])
201    }
202    /// Write an unsigned big-endian two-byte integer.
203    pub fn write_u2(&mut self, value: u16) -> Result<(), ByteError> {
204        self.write_bytes(&value.to_be_bytes())
205    }
206    /// Write an unsigned big-endian four-byte integer.
207    pub fn write_u4(&mut self, value: u32) -> Result<(), ByteError> {
208        self.write_bytes(&value.to_be_bytes())
209    }
210    /// Append exact bytes after checking the output budget.
211    pub fn write_bytes(&mut self, value: &[u8]) -> Result<(), ByteError> {
212        self.reserve_for(value.len())?;
213        self.bytes.extend_from_slice(value);
214        Ok(())
215    }
216    /// Borrow all bytes written so far.
217    pub fn as_slice(&self) -> &[u8] {
218        &self.bytes
219    }
220    /// Finish and return the written bytes.
221    pub fn into_bytes(self) -> Vec<u8> {
222        self.bytes
223    }
224}