Skip to main content

bacnet_types/error/
decode.rs

1//! Errors without an allocated message (#1646): [`DecodeError`], which a
2//! decoder returns as [`Error::Decoding`](super::Error::Decoding), and
3//! [`RangeError`], which a checked constructor returns as
4//! [`Error::Range`](super::Error::Range).
5//!
6//! Each holds `&'static str` labels and up to two numbers, and builds its
7//! text only when it is displayed. A firmware image that never displays one
8//! links no formatting code for it, and every build, the hosted ones
9//! included, reads the same text: the label with each placeholder in it
10//! replaced by the next number:
11//!
12//! - `{}`: the number in decimal;
13//! - `{:#x}`: the number in hexadecimal, lower case, without padding;
14//! - `{:#04X}`: an octet in hexadecimal, upper case and zero-padded to two
15//!   digits, such as `0x0B` (#1803); a wider number prints all its digits;
16//! - `{t}`: the name of the application datatype the number is the tag of
17//!   (Clause 20.2.1.4), such as `Unsigned` for 2. The names live in the
18//!   display code, so one label serves every type.
19
20use core::fmt;
21
22use crate::enums::RejectReason;
23
24/// Which fault a decoder found in the data it refused, so a responder can
25/// name it (#1446). [`DecodingKind::reject_reason`] gives the Clause 18.9
26/// Reject reason each kind draws when the data was a confirmed request.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum DecodingKind {
29    /// Contents whose encoding isn't valid for their datatype: a wrong
30    /// length, an Unsigned of no octets or with padding a canonical field
31    /// refuses, an unknown character set, a BIT STRING's unused-bit count
32    /// past 7, and the like. The kind [`Error::decoding`](super::Error::decoding)
33    /// makes.
34    InvalidEncoding,
35    /// A value too large for its field's width, or outside the range the
36    /// production or service gives it.
37    OutOfRange,
38    /// More than the decoder takes: a count of items past its limit, or a tag
39    /// length past its sanity bound.
40    Overflow,
41    /// A tag that doesn't fit where it stands: another number, class or form
42    /// than the member there needs, a closing tag that closes no open frame,
43    /// a tag header that is itself malformed, or an opening tag nested
44    /// deeper than the decoder walks.
45    InvalidTag,
46    /// The data ends, or the enclosing frame closes, where a required member
47    /// or a frame's closing tag is due: a member is missing.
48    Missing,
49    /// Octets are left after the last member of a value that must fill its
50    /// input: arguments beyond those the production has.
51    Trailing,
52    /// A well-formed encoding the decoder doesn't handle: a CharacterString
53    /// in a character set it can't convert (IBM/Microsoft DBCS, JIS X 0208
54    /// or UCS-4), or a segmented APDU, or one of a PDU type, that the
55    /// allocation-free APDU header doesn't read.
56    Unsupported,
57}
58
59impl DecodingKind {
60    /// The Clause 18.9 Reject reason that names this fault in a confirmed
61    /// request:
62    ///
63    /// | Kind | Reject reason |
64    /// |---|---|
65    /// | [`InvalidEncoding`](Self::InvalidEncoding) | INVALID_DATA_ENCODING |
66    /// | [`OutOfRange`](Self::OutOfRange) | PARAMETER_OUT_OF_RANGE |
67    /// | [`Overflow`](Self::Overflow) | BUFFER_OVERFLOW |
68    /// | [`InvalidTag`](Self::InvalidTag) | INVALID_TAG |
69    /// | [`Missing`](Self::Missing) | MISSING_REQUIRED_PARAMETER |
70    /// | [`Trailing`](Self::Trailing) | TOO_MANY_ARGUMENTS |
71    /// | [`Unsupported`](Self::Unsupported) | OTHER |
72    pub fn reject_reason(self) -> RejectReason {
73        match self {
74            Self::InvalidEncoding => RejectReason::INVALID_DATA_ENCODING,
75            Self::OutOfRange => RejectReason::PARAMETER_OUT_OF_RANGE,
76            Self::Overflow => RejectReason::BUFFER_OVERFLOW,
77            Self::InvalidTag => RejectReason::INVALID_TAG,
78            Self::Missing => RejectReason::MISSING_REQUIRED_PARAMETER,
79            Self::Trailing => RejectReason::TOO_MANY_ARGUMENTS,
80            Self::Unsupported => RejectReason::OTHER,
81        }
82    }
83}
84
85/// A decoder's refusal, without an allocated message (#1646).
86///
87/// It displays as `decoding error at offset {offset}: {context}: {label}`,
88/// leaving out `{context}: ` when the context is empty, and with each `{}`
89/// in the label replaced by the next of [`values`](Self::values) in decimal,
90/// each `{:#x}` in hexadecimal, or each `{:#04X}` as an upper-case,
91/// zero-padded octet such as `0x0B`. An [`index`](Self::index) follows the
92/// context as `[{index}]`.
93///
94/// ```
95/// use bacnet_types::error::{DecodeError, DecodingKind, Error};
96///
97/// let refused = DecodeError::new(DecodingKind::Trailing, 9, "{} trailing byte(s)")
98///     .within("IAm")
99///     .with_value(2);
100/// assert_eq!(refused.to_string(), "decoding error at offset 9: IAm: 2 trailing byte(s)");
101/// assert_eq!(Error::from(refused).reject_reason(), Some(DecodingKind::Trailing.reject_reason()));
102/// ```
103///
104/// It is `#[non_exhaustive]`: build one with [`new`](Self::new) and the
105/// builder methods.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107#[non_exhaustive]
108pub struct DecodeError {
109    /// Byte offset where the decoder found the fault.
110    pub offset: usize,
111    /// Which kind of fault it is.
112    pub kind: DecodingKind,
113    /// The production or member being decoded, such as `"WhoIs low limit"`;
114    /// empty when the decoder names none.
115    pub context: &'static str,
116    /// What is wrong, such as `"expected context tag [{}]"`.
117    pub label: &'static str,
118    /// The numbers the label's placeholders stand for, in order; a slot with
119    /// no placeholder is 0.
120    pub values: [u64; 2],
121    /// The zero-based position of the list item the fault is in, such as a
122    /// file record, when the decoder names one (#1776). It sits in the
123    /// struct's padding, so the error is no larger for it.
124    pub index: Option<u16>,
125}
126
127impl DecodeError {
128    /// A fault of `kind` at `offset`, described by `label`.
129    pub const fn new(kind: DecodingKind, offset: usize, label: &'static str) -> Self {
130        Self {
131            offset,
132            kind,
133            context: "",
134            label,
135            values: [0; 2],
136            index: None,
137        }
138    }
139
140    /// The same fault, met decoding `context`.
141    #[must_use]
142    pub const fn within(mut self, context: &'static str) -> Self {
143        self.context = context;
144        self
145    }
146
147    /// The same fault, in the list item at `index`.
148    #[must_use]
149    pub const fn at_item(mut self, index: u16) -> Self {
150        self.index = Some(index);
151        self
152    }
153
154    /// The same fault, with the number the label's one placeholder stands
155    /// for.
156    #[must_use]
157    pub const fn with_value(mut self, value: u64) -> Self {
158        self.values = [value, 0];
159        self
160    }
161
162    /// The same fault, with the numbers the label's two placeholders stand
163    /// for.
164    #[must_use]
165    pub const fn with_values(mut self, first: u64, second: u64) -> Self {
166        self.values = [first, second];
167        self
168    }
169
170    /// The context and label with their numbers, without the
171    /// `decoding error at offset {offset}: ` prefix.
172    pub fn message(&self) -> impl fmt::Display + '_ {
173        DecodeText(self)
174    }
175}
176
177impl fmt::Display for DecodeError {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        write!(f, "decoding error at offset {}: ", self.offset)?;
180        fmt::Display::fmt(&self.message(), f)
181    }
182}
183
184impl core::error::Error for DecodeError {}
185
186/// [`DecodeError::message`].
187struct DecodeText<'a>(&'a DecodeError);
188
189impl fmt::Display for DecodeText<'_> {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        let error = self.0;
192        if !error.context.is_empty() || error.index.is_some() {
193            f.write_str(error.context)?;
194            if let Some(index) = error.index {
195                write!(f, "[{index}]")?;
196            }
197            f.write_str(": ")?;
198        }
199        write_label(f, error.label, error.values)
200    }
201}
202
203/// A value a checked constructor refused for lying outside the range its
204/// type takes, such as an object instance past 4,194,303 (#1646).
205///
206/// Like [`DecodeError`] it carries a static label and up to two numbers,
207/// formatted when displayed. It displays as `value out of range: {label}`.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
209pub struct RangeError {
210    /// What is out of range, such as `"instance number {} exceeds max 4194303"`.
211    pub label: &'static str,
212    /// The numbers the label's placeholders stand for, in order; a slot with
213    /// no placeholder is 0.
214    pub values: [u64; 2],
215}
216
217impl RangeError {
218    /// A refusal described by `label`.
219    pub const fn new(label: &'static str) -> Self {
220        Self {
221            label,
222            values: [0; 2],
223        }
224    }
225
226    /// The same refusal, with the number the label's one placeholder stands
227    /// for.
228    #[must_use]
229    pub const fn with_value(mut self, value: u64) -> Self {
230        self.values = [value, 0];
231        self
232    }
233
234    /// The same refusal, with the numbers the label's two placeholders stand
235    /// for.
236    #[must_use]
237    pub const fn with_values(mut self, first: u64, second: u64) -> Self {
238        self.values = [first, second];
239        self
240    }
241
242    /// The label with its numbers, without the `value out of range: ` prefix.
243    pub fn message(&self) -> impl fmt::Display + '_ {
244        Message(self)
245    }
246}
247
248impl fmt::Display for RangeError {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.write_str("value out of range: ")?;
251        write_label(f, self.label, self.values)
252    }
253}
254
255impl core::error::Error for RangeError {}
256
257/// [`RangeError::message`].
258struct Message<'a>(&'a RangeError);
259
260impl fmt::Display for Message<'_> {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        write_label(f, self.0.label, self.0.values)
263    }
264}
265
266/// The name of the application datatype tag `number` stands for (Clause
267/// 20.2.1.4), as the decoders' labels call it.
268fn datatype_name(number: u64) -> &'static str {
269    const NAMES: [&str; 13] = [
270        "NULL",
271        "BOOLEAN",
272        "Unsigned",
273        "INTEGER",
274        "REAL",
275        "Double",
276        "OCTET STRING",
277        "CharacterString",
278        "BIT STRING",
279        "ENUMERATED",
280        "Date",
281        "Time",
282        "BACnetObjectIdentifier",
283    ];
284    usize::try_from(number)
285        .ok()
286        .and_then(|number| NAMES.get(number))
287        .copied()
288        .unwrap_or("value of a reserved type")
289}
290
291/// Write `label` with its placeholders filled from `values` in order (see
292/// the module docs). A placeholder past the second is written as it stands.
293pub(super) fn write_label(
294    f: &mut fmt::Formatter<'_>,
295    label: &str,
296    values: [u64; 2],
297) -> fmt::Result {
298    let mut rest = label;
299    let mut next = values.iter();
300    while let Some(at) = rest.find('{') {
301        let (before, from) = rest.split_at(at);
302        f.write_str(before)?;
303        let placeholder = ["{}", "{:#x}", "{:#04X}", "{t}"]
304            .into_iter()
305            .find(|placeholder| from.starts_with(placeholder));
306        match placeholder.zip(next.clone().next()) {
307            Some((placeholder, &value)) => {
308                next.next();
309                match placeholder {
310                    "{:#x}" => write!(f, "{value:#x}")?,
311                    "{:#04X}" => write!(f, "{value:#04X}")?,
312                    "{t}" => f.write_str(datatype_name(value))?,
313                    _ => write!(f, "{value}")?,
314                }
315                rest = &from[placeholder.len()..];
316            }
317            None => {
318                let placeholder = placeholder.unwrap_or("{");
319                f.write_str(placeholder)?;
320                rest = &from[placeholder.len()..];
321            }
322        }
323    }
324    f.write_str(rest)
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    #[test]
332    fn labels_fill_their_placeholders_in_order() {
333        let display = |label, values: [u64; 2]| {
334            let mut refused = DecodeError::new(DecodingKind::InvalidTag, 3, label);
335            refused.values = values;
336            refused.to_string()
337        };
338        assert_eq!(
339            display("closing tag {} does not match opening tag {}", [4, 2]),
340            "decoding error at offset 3: closing tag 4 does not match opening tag 2"
341        );
342        assert_eq!(
343            display("unknown PDU type nibble: {:#x}", [0xF, 0]),
344            "decoding error at offset 3: unknown PDU type nibble: 0xf"
345        );
346        // An octet pads to two upper-case digits; a wider value keeps them all.
347        assert_eq!(
348            display("type {:#04X}, function {:#04X}", [0x0B, 0x1FF]),
349            "decoding error at offset 3: type 0x0B, function 0x1FF"
350        );
351        assert_eq!(
352            display("no placeholder", [7, 8]),
353            "decoding error at offset 3: no placeholder"
354        );
355        assert_eq!(
356            display("expected application-tagged {t}, not {t}", [2, 12]),
357            "decoding error at offset 3: expected application-tagged Unsigned, \
358             not BACnetObjectIdentifier"
359        );
360        assert_eq!(
361            display("{t} and {t}", [0, 13]),
362            "decoding error at offset 3: NULL and value of a reserved type"
363        );
364        // Braces that aren't a placeholder, and a third placeholder, as written.
365        assert_eq!(
366            display("{a} {} {} {}", [1, 2]),
367            "decoding error at offset 3: {a} 1 2 {}"
368        );
369        assert_eq!(
370            DecodeError::new(DecodingKind::OutOfRange, 0, "[{}] value {} exceeds u32")
371                .within("WhoIs low limit")
372                .with_values(0, 1 << 32)
373                .to_string(),
374            "decoding error at offset 0: WhoIs low limit: [0] value 4294967296 exceeds u32"
375        );
376        assert_eq!(
377            DecodeError::new(DecodingKind::InvalidTag, 7, "expected {t}")
378                .within("record data")
379                .at_item(1)
380                .with_value(6)
381                .to_string(),
382            "decoding error at offset 7: record data[1]: expected OCTET STRING"
383        );
384    }
385
386    #[test]
387    fn range_errors_display_their_label() {
388        let refused =
389            RangeError::new("low limit {} is above its high limit {}").with_values(20, 10);
390        assert_eq!(
391            refused.to_string(),
392            "value out of range: low limit 20 is above its high limit 10"
393        );
394        assert_eq!(
395            refused.message().to_string(),
396            "low limit 20 is above its high limit 10"
397        );
398    }
399
400    /// The errors stay small enough to return by value on a firmware stack.
401    #[test]
402    fn errors_stay_small() {
403        use core::mem::size_of;
404        // 64 octets on a 64-bit host, the error type's tag in DecodeError's
405        // niche; less on a 32-bit target, whose pointers are half the size.
406        assert!(size_of::<DecodeError>() <= 64);
407        assert!(size_of::<super::super::Error>() <= 64);
408        assert!(size_of::<RangeError>() <= 32);
409        assert!(size_of::<super::super::EncodeError>() <= 56);
410        assert!(size_of::<super::super::SegmentationError>() <= 24);
411    }
412}
413
414// On a 32-bit target the error is 48 octets: the other variants fit beside
415// DecodeError's niche, so the enum needs no tag of its own. A variant that
416// outgrows that space adds one, which the firmware decoders pay for
417// (#1802), so check-no-std's builds refuse it here.
418#[cfg(target_pointer_width = "32")]
419const _: () = assert!(core::mem::size_of::<super::Error>() == 48);