qubit_codec_binary/codec/leb128_decode_error.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use core::{
9 fmt::{
10 self,
11 Display,
12 Formatter,
13 },
14 num::NonZeroUsize,
15};
16
17use crate::Leb128DecodeErrorKind;
18
19/// Error reported while decoding a LEB128 integer from a byte buffer.
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub struct Leb128DecodeError {
22 kind: Leb128DecodeErrorKind,
23 start_index: usize,
24 error_index: usize,
25 consumed: Option<NonZeroUsize>,
26 required: Option<NonZeroUsize>,
27 available: Option<usize>,
28}
29
30impl Leb128DecodeError {
31 /// Creates an incomplete-input decoding error.
32 ///
33 /// # Parameters
34 ///
35 /// - `start_index`: Byte index where the incomplete value starts.
36 /// - `required`: Non-zero lower bound for total bytes that must be readable
37 /// from `start_index` before decoding can make progress.
38 /// - `available`: Bytes currently available from `start_index`.
39 ///
40 /// # Returns
41 ///
42 /// Returns an error carrying incomplete-input context.
43 ///
44 /// # Panics
45 ///
46 /// Panics when `required <= available`, or when the one-past-available
47 /// error boundary overflows `usize`.
48 pub const fn incomplete(
49 start_index: usize,
50 required: NonZeroUsize,
51 available: usize,
52 ) -> Self {
53 assert!(
54 required.get() > available,
55 "incomplete LEB128 required bytes must exceed available bytes",
56 );
57 Self {
58 kind: Leb128DecodeErrorKind::Incomplete,
59 start_index,
60 error_index: add_offset(start_index, available),
61 consumed: None,
62 required: Some(required),
63 available: Some(available),
64 }
65 }
66
67 /// Creates a malformed-input decoding error.
68 ///
69 /// # Parameters
70 ///
71 /// - `start_index`: Byte index where the malformed value starts.
72 /// - `error_index`: Byte index at which the malformed input was detected.
73 /// - `consumed`: Non-zero bytes the caller may consume to make progress.
74 ///
75 /// # Returns
76 ///
77 /// Returns an error carrying malformed-input context.
78 ///
79 /// # Panics
80 ///
81 /// Panics when `error_index` is outside the consumed span.
82 pub const fn malformed(
83 start_index: usize,
84 error_index: usize,
85 consumed: NonZeroUsize,
86 ) -> Self {
87 assert_error_index_in_consumed_span(start_index, error_index, consumed);
88 Self {
89 kind: Leb128DecodeErrorKind::Malformed,
90 start_index,
91 error_index,
92 consumed: Some(consumed),
93 required: None,
94 available: None,
95 }
96 }
97
98 /// Creates a non-canonical-input decoding error.
99 ///
100 /// # Parameters
101 ///
102 /// - `start_index`: Byte index where the non-canonical value starts.
103 /// - `consumed`: Non-zero bytes the caller may consume to make progress.
104 ///
105 /// # Returns
106 ///
107 /// Returns an error carrying non-canonical-input context.
108 ///
109 /// # Panics
110 ///
111 /// Panics when the last consumed byte index overflows `usize`.
112 pub const fn noncanonical(
113 start_index: usize,
114 consumed: NonZeroUsize,
115 ) -> Self {
116 Self {
117 kind: Leb128DecodeErrorKind::NonCanonical,
118 start_index,
119 error_index: last_consumed_index(start_index, consumed),
120 consumed: Some(consumed),
121 required: None,
122 available: None,
123 }
124 }
125
126 /// Returns the decoding error kind.
127 #[must_use]
128 pub const fn kind(self) -> Leb128DecodeErrorKind {
129 self.kind
130 }
131
132 /// Returns the absolute byte index where the attempted value starts.
133 #[must_use]
134 pub const fn start_index(self) -> usize {
135 self.start_index
136 }
137
138 /// Returns the absolute byte index where the error was detected.
139 ///
140 /// # Returns
141 ///
142 /// Returns the byte index that made the error observable. For incomplete
143 /// input this is the one-past-available boundary where the next byte would
144 /// be required, so it may be equal to the input length visible to the
145 /// caller.
146 #[must_use]
147 pub const fn error_index(self) -> usize {
148 self.error_index
149 }
150
151 /// Returns bytes that may be consumed after an invalid-input error.
152 ///
153 /// # Returns
154 ///
155 /// Returns `Some(consumed)` for invalid input, or `None` for incomplete
156 /// input.
157 #[must_use]
158 pub const fn consumed(self) -> Option<NonZeroUsize> {
159 self.consumed
160 }
161
162 /// Returns whether this error reports an incomplete input prefix.
163 #[must_use]
164 pub const fn is_incomplete(self) -> bool {
165 matches!(self.kind, Leb128DecodeErrorKind::Incomplete)
166 }
167
168 /// Returns whether this error reports malformed input.
169 #[must_use]
170 pub const fn is_malformed(self) -> bool {
171 matches!(self.kind, Leb128DecodeErrorKind::Malformed)
172 }
173
174 /// Returns whether this error reports a non-canonical representation.
175 #[must_use]
176 pub const fn is_noncanonical(self) -> bool {
177 matches!(self.kind, Leb128DecodeErrorKind::NonCanonical)
178 }
179
180 /// Returns a lower bound for bytes required to continue incomplete
181 /// decoding.
182 ///
183 /// # Returns
184 ///
185 /// Returns `Some(required)` for incomplete input, or `None` otherwise. The
186 /// returned value is a non-zero lower bound for the total readable bytes
187 /// required from [`Self::start_index`] before decoding can make progress;
188 /// it does not guarantee that the value will be complete at exactly that
189 /// length.
190 #[must_use]
191 pub const fn required(self) -> Option<NonZeroUsize> {
192 self.required
193 }
194
195 /// Returns additional bytes required to continue incomplete decoding.
196 ///
197 /// # Returns
198 ///
199 /// Returns `Some(additional)` for incomplete input, or `None` otherwise.
200 /// The value is `required - available` and is guaranteed to be non-zero.
201 #[must_use]
202 pub const fn additional(self) -> Option<NonZeroUsize> {
203 match (self.required, self.available) {
204 (Some(required), Some(available)) => {
205 let additional = required.get() - available;
206 // SAFETY: `incomplete` enforces `required > available`.
207 Some(unsafe { NonZeroUsize::new_unchecked(additional) })
208 }
209 _ => None,
210 }
211 }
212
213 /// Returns bytes available for an incomplete value.
214 ///
215 /// # Returns
216 ///
217 /// Returns `Some(available)` for incomplete input, or `None` otherwise.
218 #[must_use]
219 pub const fn available(self) -> Option<usize> {
220 self.available
221 }
222}
223
224impl Display for Leb128DecodeError {
225 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
226 match self.kind {
227 Leb128DecodeErrorKind::Incomplete => {
228 let required = self
229 .required
230 .expect("incomplete LEB128 errors always store a required byte bound");
231 let available = self
232 .available
233 .expect("incomplete LEB128 errors always store an available byte count");
234 write!(
235 formatter,
236 "{} at byte {}: need at least {} bytes, only {} available (next byte boundary {})",
237 self.kind,
238 self.start_index,
239 required,
240 available,
241 self.error_index,
242 )
243 }
244 Leb128DecodeErrorKind::Malformed
245 | Leb128DecodeErrorKind::NonCanonical => {
246 let consumed = self.consumed.expect(
247 "invalid LEB128 errors always store a consumed byte count",
248 );
249 write!(
250 formatter,
251 "{} at byte {}: detected at byte {} after consuming {} bytes",
252 self.kind, self.start_index, self.error_index, consumed,
253 )
254 }
255 }
256 }
257}
258
259impl std::error::Error for Leb128DecodeError {}
260
261/// Adds an absolute byte offset to an index.
262///
263/// # Parameters
264///
265/// - `index`: Base byte index.
266/// - `offset`: Byte offset to add.
267///
268/// # Returns
269///
270/// Returns `index + offset`.
271///
272/// # Panics
273///
274/// Panics when the sum overflows `usize`.
275const fn add_offset(index: usize, offset: usize) -> usize {
276 match index.checked_add(offset) {
277 Some(value) => value,
278 None => panic!("LEB128 byte index overflow"),
279 }
280}
281
282/// Returns the absolute index of the last consumed byte.
283///
284/// # Parameters
285///
286/// - `start_index`: Byte index where the value starts.
287/// - `consumed`: Non-zero number of consumed bytes.
288///
289/// # Returns
290///
291/// Returns `start_index + consumed - 1`.
292///
293/// # Panics
294///
295/// Panics when the index overflows `usize`.
296const fn last_consumed_index(
297 start_index: usize,
298 consumed: NonZeroUsize,
299) -> usize {
300 add_offset(start_index, consumed.get() - 1)
301}
302
303/// Validates that an error index lies inside a consumed byte span.
304///
305/// # Parameters
306///
307/// - `start_index`: Byte index where the value starts.
308/// - `error_index`: Byte index where the error was detected.
309/// - `consumed`: Non-zero number of consumed bytes.
310///
311/// # Panics
312///
313/// Panics when `error_index` is before `start_index` or after the last consumed
314/// byte.
315const fn assert_error_index_in_consumed_span(
316 start_index: usize,
317 error_index: usize,
318 consumed: NonZeroUsize,
319) {
320 let last_index = last_consumed_index(start_index, consumed);
321 assert!(
322 error_index >= start_index,
323 "LEB128 error index must not precede value start",
324 );
325 assert!(
326 error_index <= last_index,
327 "LEB128 error index must lie inside consumed input",
328 );
329}