Skip to main content

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}