ingot_types/
error.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use core::{
6    convert::Infallible,
7    error::Error,
8    ffi::CStr,
9    fmt::{Debug, Display},
10};
11
12use crate::Read;
13
14/// Convenience type for fallible operations done while parsing headers.
15pub type ParseResult<T> = Result<T, ParseError>;
16
17/// Convenience type for fallible operations done while parsing full packets.
18pub type PacketParseResult<T> = Result<T, PacketParseError>;
19
20/// An error encountered while parsing an individual header.
21#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
22pub enum ParseError {
23    /// Encountered a header which was not allowed by a `choice`.
24    Unwanted,
25    /// No hint was provided from a previous header, but the current
26    /// header requires a hint to choose
27    NeedsHint,
28    /// There are insufficient bytes in the buffer to read the intended
29    /// header.
30    TooSmall,
31    /// A header could not be parsed as it was split across several buffers.
32    StraddledHeader,
33    /// There are no remaining chunks in the [`Read`].
34    ///
35    /// [`Read`]: crate::Read
36    NoRemainingChunks,
37    /// A parser control attempted to accept an input packet as complete,
38    /// however the remaining layers are non-optional.
39    CannotAccept,
40    /// The packet was explicitly rejected by a parser control block.
41    Reject,
42    /// A field in the header had an illegal value for the target datatype.
43    IllegalValue,
44}
45
46impl ParseError {
47    /// Return the name of the error variant as a [`CStr`].
48    #[inline]
49    pub fn as_cstr(&self) -> &'static CStr {
50        match self {
51            ParseError::Unwanted => c"Unwanted",
52            ParseError::NeedsHint => c"NeedsHint",
53            ParseError::TooSmall => c"TooSmall",
54            ParseError::StraddledHeader => c"StraddledHeader",
55            ParseError::NoRemainingChunks => c"NoRemainingChunks",
56            ParseError::CannotAccept => c"CannotAccept",
57            ParseError::Reject => c"Reject",
58            ParseError::IllegalValue => c"IllegalValue",
59        }
60    }
61
62    /// Converts `TooSmall` to `StraddledHeader` for generated `parse_read`
63    /// impls.
64    #[doc(hidden)]
65    pub fn convert_read_parse(self, reader: &mut impl Read) -> Self {
66        match self {
67            Self::TooSmall if reader.next_chunk().is_ok() => {
68                Self::StraddledHeader
69            }
70            a => a,
71        }
72    }
73}
74
75impl From<Infallible> for ParseError {
76    #[inline]
77    fn from(_: Infallible) -> Self {
78        // There appears to be no perf improvement via
79        // unreachable_unchecked! here.
80        unreachable!()
81    }
82}
83
84impl Display for ParseError {
85    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
86        match self {
87            ParseError::Unwanted => {
88                write!(f, "encountered header not permitted by the parser")
89            }
90            ParseError::NeedsHint => write!(
91                f,
92                "header/choice requires a hint to parse and none was provided"
93            ),
94            ParseError::TooSmall => {
95                write!(f, "insufficient bytes in buffer to read current header")
96            }
97            ParseError::StraddledHeader => {
98                write!(f, "header is split across more than one buffer")
99            }
100            ParseError::NoRemainingChunks => write!(
101                f,
102                "packet contains no more chunks for parsing outstanding headers"
103            ),
104            ParseError::CannotAccept => write!(
105                f,
106                "tried to accept packet with unfilled mandatory headers"
107            ),
108            ParseError::Reject => write!(f, "packet was explicitly rejected"),
109            ParseError::IllegalValue => {
110                write!(f, "encountered field value not permitted by the parser")
111            }
112        }
113    }
114}
115
116impl Error for ParseError {}
117
118/// An error encountered while parsing a complete packet.
119#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
120pub struct PacketParseError {
121    label: &'static CRStr,
122    inner: ParseError,
123}
124
125impl PacketParseError {
126    #[inline]
127    /// Tag a [`ParseError`] with a location.
128    pub const fn new(err: ParseError, label: &'static CRStr) -> Self {
129        Self { label, inner: err }
130    }
131
132    #[inline]
133    /// Return the underlying error.
134    pub fn error(&self) -> &ParseError {
135        &self.inner
136    }
137
138    #[inline]
139    /// Return the name of the header where parsing failed.
140    pub fn header(&self) -> &CRStr {
141        self.label
142    }
143}
144
145impl From<Infallible> for PacketParseError {
146    #[inline]
147    fn from(_: Infallible) -> Self {
148        // There appears to be no perf improvement via
149        // unreachable_unchecked! here.
150        unreachable!()
151    }
152}
153
154impl From<PacketParseError> for ParseError {
155    #[inline]
156    fn from(value: PacketParseError) -> Self {
157        value.inner
158    }
159}
160
161impl Display for PacketParseError {
162    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
163        write!(f, "error while parsing {}: {}", self.label, self.inner)
164    }
165}
166
167impl Error for PacketParseError {
168    fn source(&self) -> Option<&(dyn Error + 'static)> {
169        Some(&self.inner)
170    }
171}
172
173/// A static string which is jointly usable as a [`CStr`] and [`str`].
174#[derive(Clone, Copy, Eq, PartialEq, Hash)]
175pub struct CRStr(&'static str, &'static CStr);
176
177/// Error encountered while constructing a [`CRStr`] (the string was not
178/// null-terminated, or was invalid UTF-8).
179#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
180pub struct CRStrError;
181
182impl Display for CRStrError {
183    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
184        write!(f, "input string was not null-terminated")
185    }
186}
187
188impl Error for CRStrError {}
189
190impl CRStr {
191    #[inline]
192    /// Validate that an input UTF-8 string is null-terminated, and
193    /// attempt to construct references to each kind of string.
194    ///
195    /// To use, an input string must be null-terminated:
196    ///
197    /// ```rust
198    /// # use ingot_types::CRStr;
199    /// # use core::ffi::CStr;
200    /// let my_crstr = CRStr::new("some text\0").unwrap();
201    ///
202    /// let str: &str = my_crstr.as_ref();
203    /// let cstr: &CStr = my_crstr.as_ref();
204    /// assert_eq!(str, "some text");
205    /// assert_eq!(cstr, c"some text");
206    ///
207    /// let will_fail = CRStr::new("not null-terminated");
208    /// assert!(will_fail.is_err());
209    /// ```
210    pub const fn new(data: &'static str) -> Result<Self, CRStrError> {
211        if let Ok(cs) = CStr::from_bytes_with_nul(data.as_bytes()) {
212            if let Some((_nul, actual_str)) = data.as_bytes().split_last() {
213                Ok(Self(
214                    // SAFETY: We have been given a valid &str, and we know
215                    // its last character *must* be \0 due to the success of
216                    // from_bytes_with_nul. Additionally, \0 cannot be an interior
217                    // byte of a UTF8 multibyte character (which are `0x10xx_xxxx`).
218                    unsafe { core::str::from_utf8_unchecked(actual_str) },
219                    cs,
220                ))
221            } else {
222                Err(CRStrError)
223            }
224        } else {
225            Err(CRStrError)
226        }
227    }
228
229    #[inline]
230    /// [`CRStr::new`] which panics on an invalid input.
231    /// Intended for `static`/`const` definitions.
232    ///
233    /// ```rust
234    /// # use ingot_types::CRStr;
235    /// # use core::ffi::CStr;
236    /// static MY_STR: CRStr = CRStr::new_unchecked("some text\0");
237    ///
238    /// let str: &str = MY_STR.as_ref();
239    /// let cstr: &CStr = MY_STR.as_ref();
240    /// assert_eq!(str, "some text");
241    /// assert_eq!(cstr, c"some text");
242    /// ```
243    pub const fn new_unchecked(data: &'static str) -> Self {
244        match Self::new(data) {
245            Ok(v) => v,
246            Err(_) => panic!(),
247        }
248    }
249
250    #[inline]
251    /// Use this string as a [`str`].
252    pub fn as_str(&self) -> &'static str {
253        self.0
254    }
255
256    #[inline]
257    /// Use this string as a [`CStr`].
258    pub fn as_cstr(&self) -> &'static CStr {
259        self.1
260    }
261}
262
263impl AsRef<str> for CRStr {
264    #[inline]
265    fn as_ref(&self) -> &'static str {
266        self.as_str()
267    }
268}
269
270impl AsRef<CStr> for CRStr {
271    #[inline]
272    fn as_ref(&self) -> &'static CStr {
273        self.as_cstr()
274    }
275}
276
277impl Error for CRStr {}
278
279impl Debug for CRStr {
280    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
281        write!(f, "{}", self.0)
282    }
283}
284
285impl Display for CRStr {
286    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
287        write!(f, "{}", self.0)
288    }
289}