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    pub(crate) fn push(
181        &mut self,
182        memory: &Memory,
183        index: u32,
184    ) -> core::result::Result<(), MemoryError> {
185        self.indices
186            .get_or_insert_with(|| memory.vector())
187            .try_push(index)
188    }
189}
190
191impl fmt::Display for Path {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        if let Some(reason) = self.unavailable {
194            return write!(f, "path unavailable: {reason}");
195        }
196        if self.indices().is_empty() {
197            return f.write_str("/");
198        }
199        for index in self.indices() {
200            write!(f, "/{index}")?;
201        }
202        Ok(())
203    }
204}
205
206/// A parse that stopped, and enough to say where without quoting the input.
207#[derive(Debug, PartialEq, Eq)]
208pub struct Error {
209    code: ErrorCode,
210    offset: u64,
211    path: Path,
212}
213
214impl Error {
215    pub(crate) fn new(code: ErrorCode, offset: u64, path: Path) -> Self {
216        Self { code, offset, path }
217    }
218
219    /// An allocation-free resource failure, with an explicitly unavailable path.
220    #[must_use]
221    pub fn resource(reason: MemoryError, offset: u64) -> Self {
222        Self::new(ErrorCode::from(reason), offset, Path::missing(reason))
223    }
224
225    /// Why the parse stopped.
226    #[must_use]
227    pub fn code(&self) -> ErrorCode {
228        self.code
229    }
230
231    /// The offset in the document where the trouble is, counted from the start
232    /// of the whole document rather than of the slice being read.
233    #[must_use]
234    pub fn offset(&self) -> u64 {
235        self.offset
236    }
237
238    /// Which element it was under.
239    #[must_use]
240    pub fn path(&self) -> &Path {
241        &self.path
242    }
243
244    /// Whether this run declined to finish rather than the input being wrong.
245    #[must_use]
246    pub fn is_limit(&self) -> bool {
247        // X.690 ยง8.1.2.4.2 does not bound the tag number; our absolute header
248        // ceiling declines processing rather than declaring the input invalid.
249        // https://www.itu.int/rec/T-REC-X.690-202102-I/en
250        matches!(
251            self.code,
252            ErrorCode::LimitExceeded(_) | ErrorCode::TagTooLarge
253        )
254    }
255
256    /// Whether resource exhaustion, rather than malformed input, stopped this run.
257    #[must_use]
258    pub fn is_resource(&self) -> bool {
259        self.is_limit()
260            || matches!(
261                self.code,
262                ErrorCode::AllocatorExhausted | ErrorCode::CapacityOverflow
263            )
264    }
265}
266
267impl From<MemoryError> for ErrorCode {
268    fn from(reason: MemoryError) -> Self {
269        match reason {
270            MemoryError::LimitExceeded => Self::LimitExceeded(Limit::Memory),
271            MemoryError::WorkExceeded => Self::LimitExceeded(Limit::Work),
272            MemoryError::OutOfMemory => Self::AllocatorExhausted,
273            MemoryError::CapacityOverflow => Self::CapacityOverflow,
274        }
275    }
276}
277
278impl fmt::Display for Error {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        write!(f, "{} at {} ({})", self.code, self.offset, self.path)
281    }
282}
283
284#[cfg(feature = "std")]
285impl std::error::Error for Error {}
286
287/// The result of reading one thing.
288pub type Result<T> = core::result::Result<T, Error>;
289
290#[cfg(test)]
291mod tests;