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}