Skip to main content

structured_asn1/
error.rs

1//! What went wrong, where, and under which element.
2//!
3//! An error carries a stable code, an offset and a path of element indices,
4//! and never bytes from the document: these values travel into diagnostics
5//! that leave the device when somebody sends feedback.
6
7use crate::memory::{Memory, MemoryError, OwnedVec};
8use core::fmt;
9
10/// Why a parse stopped.
11///
12/// Malformed input is a statement about the bytes. A spent budget is a
13/// statement about this run, and the two must not be confused.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum ErrorCode {
17    /// The element claims more bytes than the enclosing element has left.
18    UnexpectedEnd,
19    /// A tag number was encoded with a leading zero byte, or does not end.
20    MalformedTag,
21    /// A tag number too large to represent.
22    TagTooLarge,
23    /// A definite length was not encoded in the fewest bytes DER allows.
24    LengthNotMinimal,
25    /// A length too large to represent as an offset on this target.
26    LengthTooLarge,
27    /// An offset that would not fit, given where this window starts.
28    OffsetTooLarge,
29    /// An indefinite length where the schema requires a definite one.
30    IndefiniteLengthNotAllowed,
31    /// An indefinite-length element that never reached its end-of-contents.
32    UnterminatedContents,
33    /// End-of-contents was encoded with a non-zero length.
34    MalformedEndOfContents,
35    /// A primitive element encoded as constructed, or the reverse.
36    WrongConstruction,
37    /// The element is not the one the schema asked for.
38    UnexpectedTag,
39    /// An integer with a redundant leading `0x00` or `0xff` byte.
40    IntegerNotMinimal,
41    /// An integer with no content bytes at all.
42    IntegerEmpty,
43    /// An object identifier that is empty, ends mid-arc, or pads an arc.
44    MalformedOid,
45    /// A bit string with no count of padding bits, a count above seven, or,
46    /// under DER, padding bits that are not zero.
47    MalformedBitString,
48    /// A boolean of the wrong length, or, under DER, a true that is not `0xff`.
49    MalformedBoolean,
50    /// A null with contents.
51    MalformedNull,
52    /// A real whose octets are not a value of the type: a reserved base or
53    /// number representation, a part written in more octets than it needs,
54    /// or a mantissa the normalization forbids.
55    MalformedReal,
56    /// The work this input asks for is more than the caller allowed.
57    ///
58    /// Distinct from every code above on purpose: nothing is wrong with the
59    /// input, and reporting it as malformed would tell somebody their document
60    /// is broken when the truth is that this device declined to finish.
61    LimitExceeded(Limit),
62    /// The allocator could not supply storage, independently of configured limits.
63    AllocatorExhausted,
64    /// A requested allocation's capacity cannot be represented on this target.
65    CapacityOverflow,
66}
67
68/// Which budget ran out.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[non_exhaustive]
71pub enum Limit {
72    /// Elements nested more deeply than allowed.
73    Depth,
74    /// More elements than allowed, across the whole parse.
75    Elements,
76    /// A single element longer than allowed.
77    ElementLength,
78    /// More input examined than allowed.
79    Work,
80    /// Aggregate live allocation capacity, including replacement peaks.
81    Memory,
82}
83
84impl fmt::Display for Limit {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str(match self {
87            Self::Depth => "nesting depth",
88            Self::Elements => "element count",
89            Self::ElementLength => "element length",
90            Self::Work => "work",
91            Self::Memory => "memory",
92        })
93    }
94}
95
96impl fmt::Display for ErrorCode {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        match self {
99            Self::UnexpectedEnd => {
100                f.write_str("element runs past the end of the one containing it")
101            }
102            Self::MalformedTag => f.write_str("malformed tag number"),
103            Self::TagTooLarge => f.write_str("tag number too large"),
104            Self::LengthNotMinimal => f.write_str("length not encoded in the fewest bytes"),
105            Self::LengthTooLarge => f.write_str("length too large"),
106            Self::OffsetTooLarge => f.write_str("offset too large for this window"),
107            Self::IndefiniteLengthNotAllowed => f.write_str("indefinite length not allowed here"),
108            Self::UnterminatedContents => f.write_str("indefinite length never terminated"),
109            Self::MalformedEndOfContents => f.write_str("end-of-contents with a length"),
110            Self::WrongConstruction => {
111                f.write_str("primitive and constructed are the wrong way round")
112            }
113            Self::UnexpectedTag => f.write_str("not the element expected here"),
114            Self::IntegerNotMinimal => f.write_str("integer has a redundant leading byte"),
115            Self::IntegerEmpty => f.write_str("integer has no content"),
116            Self::MalformedOid => f.write_str("malformed object identifier"),
117            Self::MalformedBitString => f.write_str("malformed bit string"),
118            Self::MalformedBoolean => f.write_str("malformed boolean"),
119            Self::MalformedNull => f.write_str("null with contents"),
120            Self::MalformedReal => f.write_str("malformed real"),
121            Self::LimitExceeded(limit) => write!(f, "{limit} budget spent"),
122            Self::AllocatorExhausted => f.write_str("allocator could not supply storage"),
123            Self::CapacityOverflow => f.write_str("storage capacity overflow"),
124        }
125    }
126}
127
128/// Where in the structure an error was found: the index of each element
129/// entered, outermost first.
130///
131/// Indices rather than names, because this layer does not know the schema. A
132/// caller that does can turn `[0, 2, 1]` into something a person reads. What it
133/// deliberately does not hold is any part of the document.
134#[derive(Debug, Default, PartialEq, Eq)]
135pub struct Path {
136    indices: Option<OwnedVec<u32>>,
137    unavailable: Option<MemoryError>,
138}
139
140impl Path {
141    /// The indices, outermost first.
142    #[must_use]
143    pub fn indices(&self) -> &[u32] {
144        self.indices.as_deref().unwrap_or(&[])
145    }
146
147    /// Whether this is the top level.
148    #[must_use]
149    pub fn is_root(&self) -> bool {
150        self.unavailable.is_none() && self.indices().is_empty()
151    }
152
153    /// Why the full path could not be retained; None means the path is complete.
154    #[must_use]
155    pub fn unavailable(&self) -> Option<MemoryError> {
156        self.unavailable
157    }
158
159    pub(crate) fn missing(reason: MemoryError) -> Self {
160        Self {
161            indices: None,
162            unavailable: Some(reason),
163        }
164    }
165
166    pub(crate) fn try_copy_in(&self, memory: &Memory) -> core::result::Result<Self, MemoryError> {
167        Ok(Self {
168            indices: match &self.indices {
169                Some(indices) => {
170                    let mut copy = memory.vector();
171                    copy.try_extend_from_slice(indices)?;
172                    Some(copy)
173                }
174                None => None,
175            },
176            unavailable: self.unavailable,
177        })
178    }
179
180    /// Pre-admission for copying this path and appending scalar indices.
181    /// `OwnedVec`'s allocator charges the aggregate account itself; its parser
182    /// caller separately reserves this exact local share before any allocation.
183    pub(crate) fn copy_work(&self, additional: usize) -> core::result::Result<u64, MemoryError> {
184        let length = self.indices().len() as u64;
185        let additional = additional as u64;
186        let target = length
187            .checked_add(additional)
188            .ok_or(MemoryError::WorkExceeded)?;
189        // Copy plus exact allocation: two passes of four bytes per u32.
190        let mut work = length
191            .checked_mul(8)
192            .and_then(|work| work.checked_add(additional))
193            .ok_or(MemoryError::WorkExceeded)?;
194        let mut capacity = length;
195        // Audited allocator-api2 0.4 RawVec<u32>: minimum four elements,
196        // then doubling. At most u64::BITS growth steps, independent of input
197        // traversal. Include old/new layouts before each replacement allocation.
198        while capacity < target {
199            let next = capacity
200                .checked_mul(2)
201                .ok_or(MemoryError::WorkExceeded)?
202                .max(4);
203            work = next
204                .checked_add(capacity)
205                .and_then(|bytes| bytes.checked_mul(4))
206                .and_then(|bytes| work.checked_add(bytes))
207                .ok_or(MemoryError::WorkExceeded)?;
208            capacity = next;
209        }
210        Ok(work)
211    }
212
213    pub(crate) fn push(
214        &mut self,
215        memory: &Memory,
216        index: u32,
217    ) -> core::result::Result<(), MemoryError> {
218        self.indices
219            .get_or_insert_with(|| memory.vector())
220            .try_push(index)
221    }
222}
223
224impl fmt::Display for Path {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        if let Some(reason) = self.unavailable {
227            return write!(f, "path unavailable: {reason}");
228        }
229        if self.indices().is_empty() {
230            return f.write_str("/");
231        }
232        for index in self.indices() {
233            write!(f, "/{index}")?;
234        }
235        Ok(())
236    }
237}
238
239/// A parse that stopped, and enough to say where without quoting the input.
240#[derive(Debug, PartialEq, Eq)]
241pub struct Error {
242    code: ErrorCode,
243    offset: u64,
244    path: Path,
245}
246
247impl Error {
248    pub(crate) fn new(code: ErrorCode, offset: u64, path: Path) -> Self {
249        Self { code, offset, path }
250    }
251
252    /// An allocation-free resource failure, with an explicitly unavailable path.
253    #[must_use]
254    pub fn resource(reason: MemoryError, offset: u64) -> Self {
255        Self::new(ErrorCode::from(reason), offset, Path::missing(reason))
256    }
257
258    /// Why the parse stopped.
259    #[must_use]
260    pub fn code(&self) -> ErrorCode {
261        self.code
262    }
263
264    /// The offset in the document where the trouble is, counted from the start
265    /// of the whole document rather than of the slice being read.
266    #[must_use]
267    pub fn offset(&self) -> u64 {
268        self.offset
269    }
270
271    /// Which element it was under.
272    #[must_use]
273    pub fn path(&self) -> &Path {
274        &self.path
275    }
276
277    /// Whether this run declined to finish rather than the input being wrong.
278    #[must_use]
279    pub fn is_limit(&self) -> bool {
280        // X.690 ยง8.1.2.4.2 does not bound the tag number; our absolute header
281        // ceiling declines processing rather than declaring the input invalid.
282        // https://www.itu.int/rec/T-REC-X.690-202102-I/en
283        matches!(
284            self.code,
285            ErrorCode::LimitExceeded(_) | ErrorCode::TagTooLarge
286        )
287    }
288
289    /// Whether resource exhaustion, rather than malformed input, stopped this run.
290    #[must_use]
291    pub fn is_resource(&self) -> bool {
292        self.is_limit()
293            || matches!(
294                self.code,
295                ErrorCode::AllocatorExhausted | ErrorCode::CapacityOverflow
296            )
297    }
298}
299
300impl From<MemoryError> for ErrorCode {
301    fn from(reason: MemoryError) -> Self {
302        match reason {
303            MemoryError::LimitExceeded => Self::LimitExceeded(Limit::Memory),
304            MemoryError::WorkExceeded => Self::LimitExceeded(Limit::Work),
305            MemoryError::OutOfMemory => Self::AllocatorExhausted,
306            MemoryError::CapacityOverflow => Self::CapacityOverflow,
307        }
308    }
309}
310
311impl fmt::Display for Error {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        write!(f, "{} at {} ({})", self.code, self.offset, self.path)
314    }
315}
316
317#[cfg(feature = "std")]
318impl std::error::Error for Error {}
319
320/// The result of reading one thing.
321pub type Result<T> = core::result::Result<T, Error>;
322
323#[cfg(test)]
324mod tests;