Skip to main content

bacnet_types/error/
encode.rs

1//! Errors an encoder or a segmenter returns without an allocated message
2//! (#1802): [`EncodeError`], which an encoder or validator returns as
3//! [`Error::Encode`](super::Error::Encode), and [`SegmentationError`], which
4//! splitting a payload or reassembling its segments returns as
5//! [`Error::Segmentation`](super::Error::Segmentation).
6//!
7//! Like [`DecodeError`] they carry static labels and their numbers, and build
8//! their text only when displayed, so a build that never shows one links no
9//! formatting code for it. An [`EncodeError`]'s label takes the placeholders
10//! the [`decode`](super::decode) module lists.
11
12use core::fmt;
13
14use super::DecodeError;
15use super::decode::write_label;
16use crate::enums::Segmentation;
17
18/// A value an encoder or validator refused to put on the wire, without an
19/// allocated message (#1802).
20///
21/// It displays as `encoding error: {context}: {label}`, leaving out
22/// `{context}: ` when the context is empty and with the label's placeholders
23/// filled from [`values`](Self::values). One that restates a decoder's
24/// refusal of octets the encoder was handed, such as a captured raw value
25/// (see [`from_decode`](Self::from_decode)), adds that refusal's offset:
26/// `encoding error: decoding error at offset {offset}: {context}: {label}`.
27///
28/// ```
29/// use bacnet_types::error::{EncodeError, Error};
30///
31/// let refused = EncodeError::new("mac-address of {} octets exceeds the {}-octet limit")
32///     .within("BACnetAddress")
33///     .with_values(19, 18);
34/// assert_eq!(
35///     Error::from(refused).to_string(),
36///     "encoding error: BACnetAddress: mac-address of 19 octets exceeds the 18-octet limit"
37/// );
38/// ```
39///
40/// It is `#[non_exhaustive]`: build one with [`new`](Self::new) and the
41/// builder methods.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub struct EncodeError {
45    /// The production or member being encoded, such as `"BACnetNameValue"`;
46    /// empty when the encoder names none.
47    pub context: &'static str,
48    /// What is wrong, such as `"exceeds {} property values"`.
49    pub label: &'static str,
50    /// The numbers the label's placeholders stand for, in order; a slot with
51    /// no placeholder is 0. They are 32 bits wide, so the error fits in
52    /// [`Error`](super::Error) without widening it: a number restated from a
53    /// decoder's refusal past `u32::MAX` reads as `u32::MAX`.
54    pub values: [u32; 2],
55    /// The offset, within the octets the encoder was handed, of the fault a
56    /// decoder found there; `None` when the refusal is the encoder's own. An
57    /// offset past `u32::MAX` reads as `u32::MAX`.
58    pub decode_offset: Option<u32>,
59}
60
61/// `value`, or `u32::MAX` when it doesn't fit.
62fn saturate(value: u64) -> u32 {
63    u32::try_from(value).unwrap_or(u32::MAX)
64}
65
66impl EncodeError {
67    /// A refusal described by `label`.
68    pub const fn new(label: &'static str) -> Self {
69        Self {
70            context: "",
71            label,
72            values: [0; 2],
73            decode_offset: None,
74        }
75    }
76
77    /// The refusal of octets an encoder was handed, which `refused` found
78    /// malformed: its context, label, numbers and offset. A list item index
79    /// the decoder named is not kept.
80    pub fn from_decode(refused: &DecodeError) -> Self {
81        Self::restating(refused).with_offset(refused.offset)
82    }
83
84    /// `refused`'s context, label and numbers, without its offset.
85    pub(super) fn restating(refused: &DecodeError) -> Self {
86        Self {
87            context: refused.context,
88            label: refused.label,
89            values: [saturate(refused.values[0]), saturate(refused.values[1])],
90            decode_offset: None,
91        }
92    }
93
94    const fn with_offset(mut self, offset: usize) -> Self {
95        self.decode_offset = Some(if offset > u32::MAX as usize {
96            u32::MAX
97        } else {
98            offset as u32
99        });
100        self
101    }
102
103    /// The same refusal, met encoding `context`.
104    #[must_use]
105    pub const fn within(mut self, context: &'static str) -> Self {
106        self.context = context;
107        self
108    }
109
110    /// The same refusal, with the number the label's one placeholder stands
111    /// for.
112    #[must_use]
113    pub const fn with_value(mut self, value: u32) -> Self {
114        self.values = [value, 0];
115        self
116    }
117
118    /// The same refusal, with the numbers the label's two placeholders stand
119    /// for.
120    #[must_use]
121    pub const fn with_values(mut self, first: u32, second: u32) -> Self {
122        self.values = [first, second];
123        self
124    }
125
126    /// The text without the `encoding error: ` prefix.
127    pub fn message(&self) -> impl fmt::Display + '_ {
128        EncodeText(self)
129    }
130}
131
132impl fmt::Display for EncodeError {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.write_str("encoding error: ")?;
135        fmt::Display::fmt(&self.message(), f)
136    }
137}
138
139impl core::error::Error for EncodeError {}
140
141/// [`EncodeError::message`].
142struct EncodeText<'a>(&'a EncodeError);
143
144impl fmt::Display for EncodeText<'_> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        let error = self.0;
147        if let Some(offset) = error.decode_offset {
148            write!(f, "decoding error at offset {offset}: ")?;
149        }
150        if !error.context.is_empty() {
151            f.write_str(error.context)?;
152            f.write_str(": ")?;
153        }
154        let [first, second] = error.values;
155        write_label(f, error.label, [first.into(), second.into()])
156    }
157}
158
159/// Why a payload could not be split into segments, or its segments put back
160/// together, or a segmented request sent (#1802).
161///
162/// It displays as `segmentation error: ` and the variant's text, built when
163/// displayed.
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165#[non_exhaustive]
166pub enum SegmentationError {
167    /// A segment size of 0: a segment still costs its PDU header, so a peer
168    /// whose maximum APDU can't hold that header can't take one.
169    ZeroSegmentSize,
170    /// The payload needs more than the 256 segments a sequence number can
171    /// count.
172    TooManySegments {
173        /// Segments the payload needs.
174        segments: usize,
175    },
176    /// A received segment larger than the receiver takes.
177    SegmentTooLarge {
178        /// The segment's length in octets.
179        size: usize,
180        /// The largest segment the receiver takes.
181        max: usize,
182    },
183    /// A reassembly asked for more than 256 segments.
184    TotalTooLarge {
185        /// The segment count asked for.
186        total: usize,
187    },
188    /// A segment due in the reassembly was never received.
189    MissingSegment {
190        /// The zero-based sequence number of the missing segment.
191        index: usize,
192        /// The segment count asked for.
193        total: usize,
194    },
195    /// A request needs more segments than the peer said it accepts.
196    PeerSegmentLimit {
197        /// Segments the request needs.
198        segments: usize,
199        /// The most the peer accepts.
200        max: u32,
201    },
202    /// A request needs segmenting and the peer advertised that it can't
203    /// receive segmented requests.
204    PeerCannotReceive {
205        /// The segmentation support the peer advertised.
206        capability: Segmentation,
207    },
208    /// A segmented request's octets, or its count of segments, passed the
209    /// receiver's cap.
210    PayloadCapacityExceeded,
211    /// Counting a segmented request's octets overflowed.
212    PayloadAccountingOverflow,
213    /// The peer kept asking for the same window again with negative
214    /// SegmentACKs past the sender's retry limit.
215    NegativeAckRetriesExhausted,
216    /// An endpoint operation's acknowledgement is longer than the endpoint's
217    /// maximum APDU; endpoint operations don't reassemble segments.
218    EndpointAckTooLong,
219    /// An endpoint request too long for one APDU; the endpoint requester
220    /// doesn't segment.
221    EndpointRequestTooLong,
222}
223
224impl fmt::Display for SegmentationError {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match *self {
227            Self::ZeroSegmentSize => {
228                f.write_str("payload cannot be segmented with max segment size 0")
229            }
230            Self::TooManySegments { segments } => {
231                write!(f, "payload requires {segments} segments, maximum is 256")
232            }
233            Self::SegmentTooLarge { size, max } => {
234                write!(f, "segment size {size} exceeds maximum {max}")
235            }
236            Self::TotalTooLarge { total } => write!(
237                f,
238                "total_segments {total} exceeds maximum BACnet value (256)"
239            ),
240            Self::MissingSegment { index, total } => {
241                write!(f, "missing segment {index} of {total}")
242            }
243            Self::PeerSegmentLimit { segments, max } => write!(
244                f,
245                "request requires {segments} segments but remote accepts at most {max}"
246            ),
247            Self::PeerCannotReceive { capability } => write!(
248                f,
249                "request requires segmentation but the peer advertised {capability:?} \
250                 and cannot receive segmented requests"
251            ),
252            Self::PayloadCapacityExceeded => f.write_str("request payload capacity exceeded"),
253            Self::PayloadAccountingOverflow => f.write_str("request payload accounting overflow"),
254            Self::NegativeAckRetriesExhausted => {
255                f.write_str("too many negative SegmentAck retransmissions")
256            }
257            Self::EndpointAckTooLong => {
258                f.write_str("endpoint operation ACK exceeds configured max APDU")
259            }
260            Self::EndpointRequestTooLong => {
261                f.write_str("endpoint requester supports only unsegmented requests")
262            }
263        }
264    }
265}
266
267impl core::error::Error for SegmentationError {}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use crate::error::{DecodingKind, Error};
273
274    #[test]
275    fn encode_errors_display_their_label() {
276        assert_eq!(
277            Error::from(EncodeError::new("{} cannot be ALL").with_value(8)).to_string(),
278            "encoding error: 8 cannot be ALL"
279        );
280        let decoded = DecodeError::new(DecodingKind::InvalidTag, 3, "expected tag [{}]")
281            .within("opaque value")
282            .with_value(2);
283        let refused = EncodeError::from_decode(&decoded);
284        assert_eq!(
285            refused.to_string(),
286            "encoding error: decoding error at offset 3: opaque value: expected tag [2]"
287        );
288        // The same text the owned form printed from the decoder's display.
289        assert_eq!(refused.to_string(), format!("encoding error: {decoded}"));
290    }
291
292    #[test]
293    fn segmentation_errors_keep_their_numbers() {
294        let refused = Error::Segmentation(SegmentationError::MissingSegment { index: 2, total: 5 });
295        assert_eq!(
296            refused.to_string(),
297            "segmentation error: missing segment 2 of 5"
298        );
299        assert_eq!(
300            SegmentationError::PeerCannotReceive {
301                capability: Segmentation::TRANSMIT
302            }
303            .to_string(),
304            "request requires segmentation but the peer advertised Segmentation::TRANSMIT \
305             and cannot receive segmented requests"
306        );
307    }
308}