Skip to main content

bacnet_mstp_codec/
frame.rs

1//! Frame types, headers, and encoding.
2
3use crate::cobs;
4use crate::crc::{crc8, crc16};
5use crate::{
6    BROADCAST_MAC, FrameError, FrameSink, HEADER_LENGTH, MAX_COBS_LENGTH, MAX_STANDARD_MPDU_DATA,
7    MIN_COBS_LENGTH, PREAMBLE,
8};
9
10/// Octets before the data field: the preamble and the header with its CRC.
11pub(crate) const DATA_OFFSET: usize = PREAMBLE.len() + HEADER_LENGTH;
12/// What follows a non-empty data field: the two-octet data CRC of a standard
13/// frame, or the two octets an extended frame's Length leaves out.
14const TRAILER_LENGTH: usize = 2;
15
16/// Wire length of a frame whose header declares `length`: the data and its
17/// CRC for a standard frame, both encoded fields for an extended one, whose
18/// Length counts them less two (Clause 9.3).
19pub(crate) const fn wire_length(length: usize) -> usize {
20    if length == 0 {
21        DATA_OFFSET
22    } else {
23        DATA_OFFSET + length + TRAILER_LENGTH
24    }
25}
26
27/// An open set of wire codes with named constants, the shape
28/// `bacnet-bip-codec` gives `BvlcFunction` and `bacnet-types` its
29/// enumerations: any raw value converts, and the names cover the values
30/// the standard defines.
31macro_rules! named_codes {
32    (
33        $(#[$meta:meta])*
34        pub struct $Name:ident($inner:ty);
35        $(
36            $(#[$vmeta:meta])*
37            const $VARIANT:ident = $val:expr_2021;
38        )*
39    ) => {
40        $(#[$meta])*
41        #[derive(Clone, Copy, PartialEq, Eq, Hash)]
42        pub struct $Name($inner);
43
44        impl $Name {
45            $(
46                $(#[$vmeta])*
47                #[doc = ""]
48                #[doc = concat!("Wire value `", stringify!($val), "`.")]
49                pub const $VARIANT: Self = Self($val);
50            )*
51
52            /// Every named constant, with its name, in the order declared.
53            pub const ALL_NAMED: &[(&str, Self)] = &[
54                $( (stringify!($VARIANT), Self($val)), )*
55            ];
56
57            /// Wrap a raw wire value, named or not.
58            #[inline]
59            #[must_use]
60            pub const fn from_raw(value: $inner) -> Self {
61                Self(value)
62            }
63
64            /// The raw wire value.
65            #[inline]
66            #[must_use]
67            pub const fn to_raw(self) -> $inner {
68                self.0
69            }
70
71            /// The constant's name, or `None` for a value the standard
72            /// doesn't define.
73            #[must_use]
74            pub const fn name(self) -> Option<&'static str> {
75                match self.0 {
76                    $( $val => Some(stringify!($VARIANT)), )*
77                    _ => None,
78                }
79            }
80
81            /// Whether the standard defines this value.
82            #[must_use]
83            pub const fn is_defined(self) -> bool {
84                self.name().is_some()
85            }
86        }
87
88        /// `Name::CONSTANT` for a named value, `Name(raw)` otherwise.
89        impl core::fmt::Debug for $Name {
90            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91                match self.name() {
92                    Some(name) => write!(f, "{}::{}", stringify!($Name), name),
93                    None => write!(f, "{}({})", stringify!($Name), self.0),
94                }
95            }
96        }
97
98        /// The constant's name, or the raw value in decimal.
99        impl core::fmt::Display for $Name {
100            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101                match self.name() {
102                    Some(name) => f.write_str(name),
103                    None => write!(f, "{}", self.0),
104                }
105            }
106        }
107    };
108}
109
110named_codes! {
111    /// An MS/TP frame type: the first octet of the header after the
112    /// preamble, which says what the frame is (Clause 9.3).
113    ///
114    /// The set is open, so a decoder hands on every octet as it arrived and
115    /// a dispatcher matches the constants with a `_` arm for the rest. The
116    /// constants are the ten types Clause 9.3 defines. Any other octet is
117    /// reserved (8-31), reserved and COBS-encoded (34-127), or proprietary
118    /// (128-255); [`FrameType::from_raw`] wraps it all the same, and two
119    /// frame types are equal when their octets are.
120    ///
121    /// Frame types are numbered in decimal, as Clause 9.3 numbers them: the
122    /// constants' wire values below, and `Debug` and `Display` for a type no
123    /// constant names.
124    ///
125    /// ```
126    /// use bacnet_mstp_codec::FrameType;
127    ///
128    /// assert_eq!(FrameType::from_raw(0), FrameType::TOKEN);
129    /// assert_eq!(FrameType::TOKEN.name(), Some("TOKEN"));
130    ///
131    /// // A proprietary type has no name, but keeps its octet.
132    /// let proprietary = FrameType::from_raw(128);
133    /// assert!(proprietary.is_proprietary() && !proprietary.is_defined());
134    /// assert_eq!(proprietary.to_raw(), 128);
135    /// assert_eq!(format!("{proprietary:?}"), "FrameType(128)");
136    /// assert_eq!(format!("{:?}", FrameType::TOKEN), "FrameType::TOKEN");
137    /// ```
138    pub struct FrameType(u8);
139
140    /// Token: passes the right to send to the next master (Clause 9.3.1).
141    const TOKEN = 0;
142    /// Poll For Master: looks for a master at an address (Clause 9.3.2).
143    const POLL_FOR_MASTER = 1;
144    /// Reply To Poll For Master: a master answering a poll (Clause 9.3.3).
145    const REPLY_TO_POLL_FOR_MASTER = 2;
146    /// Test_Request: asks the receiver to send its data back (Clause
147    /// 9.3.4).
148    const TEST_REQUEST = 3;
149    /// Test_Response: the answer to a Test_Request (Clause 9.3.5).
150    const TEST_RESPONSE = 4;
151    /// BACnet Data Expecting Reply: an NPDU whose sender waits for an
152    /// answer (Clause 9.3.6).
153    const BACNET_DATA_EXPECTING_REPLY = 5;
154    /// BACnet Data Not Expecting Reply: an NPDU that wants no answer (Clause
155    /// 9.3.7).
156    const BACNET_DATA_NOT_EXPECTING_REPLY = 6;
157    /// Reply Postponed: puts off the answer to a BACnet Data Expecting Reply
158    /// (Clause 9.3.8).
159    const REPLY_POSTPONED = 7;
160    /// BACnet Extended Data Expecting Reply: an NPDU too long for a
161    /// standard frame, COBS-encoded (Clause 9.3.9).
162    const BACNET_EXTENDED_DATA_EXPECTING_REPLY = 32;
163    /// BACnet Extended Data Not Expecting Reply: an NPDU too long for a
164    /// standard frame, COBS-encoded (Clause 9.3.10).
165    const BACNET_EXTENDED_DATA_NOT_EXPECTING_REPLY = 33;
166}
167
168impl FrameType {
169    /// Whether a frame of this type may carry a data field.
170    ///
171    /// False only for the four types defined without data:
172    /// [`TOKEN`](Self::TOKEN), [`POLL_FOR_MASTER`](Self::POLL_FOR_MASTER),
173    /// [`REPLY_TO_POLL_FOR_MASTER`](Self::REPLY_TO_POLL_FOR_MASTER) and
174    /// [`REPLY_POSTPONED`](Self::REPLY_POSTPONED) (Clauses 9.3.1-9.3.3, 9.3.8).
175    /// Every other type may, including the reserved types 8-31, which the
176    /// codec passes through without policing their content.
177    #[must_use]
178    pub const fn allows_data(self) -> bool {
179        !matches!(self.to_raw(), 0 | 1 | 2 | 7)
180    }
181
182    /// Whether this type is in the COBS-encoded range, 32-127 (Clause 9.3).
183    /// Its Length must fall within
184    /// [`MIN_COBS_LENGTH`]..=[`MAX_COBS_LENGTH`] (Clause 9.5.8).
185    #[must_use]
186    pub const fn is_cobs(self) -> bool {
187        let raw = self.to_raw();
188        raw >= 32 && raw <= 127
189    }
190
191    /// Whether this is one of the two extended data types, 32 and 33: the
192    /// COBS-encoded types the codec encodes and decodes, which carry BACnet
193    /// NPDUs of 502 to [`MAX_EXTENDED_MPDU_DATA`](crate::MAX_EXTENDED_MPDU_DATA)
194    /// octets (Clauses 9.3.9, 9.3.10). The rest of the COBS range, 34-127,
195    /// is reserved.
196    #[must_use]
197    pub const fn is_extended_data(self) -> bool {
198        matches!(self.to_raw(), 32 | 33)
199    }
200
201    /// Whether this type is in the vendor-proprietary range, 128-255. Such a
202    /// frame's data opens with a 2-octet vendor identifier (Clause 9.3.11).
203    #[must_use]
204    pub const fn is_proprietary(self) -> bool {
205        self.to_raw() >= 128
206    }
207}
208
209/// The addressing fields of an MS/TP frame header.
210///
211/// The length field is not stored: it follows from the data that goes with
212/// the header, [`Frame::data`], and the frame type.
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
214pub struct FrameHeader {
215    /// Frame type.
216    pub frame_type: FrameType,
217    /// Destination station address; 255 is broadcast.
218    pub destination: u8,
219    /// Source station address.
220    pub source: u8,
221}
222
223impl FrameHeader {
224    /// Construct a header.
225    #[must_use]
226    pub const fn new(frame_type: FrameType, destination: u8, source: u8) -> Self {
227        Self {
228            frame_type,
229            destination,
230            source,
231        }
232    }
233}
234
235/// Check the header fields that both directions check, in the order they
236/// check them (Clause 9.5.8): the source, then the Length against the bounds
237/// of the type's class, then whether the codec supports a COBS type. So
238/// [`FrameError::UnsupportedCobsFrame`] always comes with a good header.
239pub(crate) fn check_header_fields(
240    frame_type: FrameType,
241    source: u8,
242    length: usize,
243) -> Result<(), FrameError> {
244    if source == BROADCAST_MAC {
245        return Err(FrameError::BroadcastSource);
246    }
247    if frame_type.is_cobs() {
248        if !(MIN_COBS_LENGTH..=MAX_COBS_LENGTH).contains(&length) {
249            return Err(FrameError::InvalidCobsLength { length });
250        }
251        if !frame_type.is_extended_data() {
252            return Err(FrameError::UnsupportedCobsFrame {
253                raw_type: frame_type.to_raw(),
254            });
255        }
256    } else if length > MAX_STANDARD_MPDU_DATA {
257        return Err(FrameError::Oversize {
258            length,
259            maximum: MAX_STANDARD_MPDU_DATA,
260        });
261    }
262    Ok(())
263}
264
265/// A frame whose data borrows from the caller or from decoder storage.
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
267pub struct Frame<'a> {
268    /// Header fields.
269    pub header: FrameHeader,
270    /// The data field as the sender's client gave it: an NPDU, test data or
271    /// proprietary content. An extended frame carries it COBS-encoded on the
272    /// wire; here it is decoded.
273    pub data: &'a [u8],
274}
275
276impl<'a> Frame<'a> {
277    /// Construct a frame.
278    #[must_use]
279    pub const fn new(frame_type: FrameType, destination: u8, source: u8, data: &'a [u8]) -> Self {
280        Self {
281            header: FrameHeader::new(frame_type, destination, source),
282            data,
283        }
284    }
285
286    /// Encode this frame at the start of `output` and return its wire length.
287    ///
288    /// The frame is checked first, as described in the [crate docs](crate):
289    /// a source other than 255; for a standard type, at most
290    /// [`MAX_STANDARD_MPDU_DATA`] octets of data, none on a type that never
291    /// carries any, and a vendor identifier's two octets on a proprietary
292    /// type; for a COBS type, a Length within the COBS bounds and one of the
293    /// two extended data types. Nothing is written unless the frame passes
294    /// and fits.
295    ///
296    /// ```
297    /// use bacnet_mstp_codec::{Frame, FrameError, FrameType};
298    ///
299    /// let mut wire = [0u8; 8];
300    /// let length = Frame::new(FrameType::TOKEN, 0x10, 0x05, &[]).encode_into(&mut wire)?;
301    /// assert_eq!(wire[..length], [0x55, 0xFF, 0x00, 0x10, 0x05, 0x00, 0x00, 0x8C]);
302    ///
303    /// // A Token never carries data.
304    /// assert_eq!(
305    ///     Frame::new(FrameType::TOKEN, 0x10, 0x05, &[1]).encode_into(&mut wire),
306    ///     Err(FrameError::UnexpectedData { raw_type: 0, length: 1 }),
307    /// );
308    /// # Ok::<(), FrameError>(())
309    /// ```
310    pub fn encode_into(self, output: &mut [u8]) -> Result<usize, FrameError> {
311        let mut sink = output;
312        self.encode_to_sink(&mut sink)
313    }
314
315    /// Encode this frame into `sink` and return its wire length.
316    ///
317    /// The checks are those of [`Frame::encode_into`]. When the sink reports a
318    /// fixed capacity, a frame that does not fit fails before anything is
319    /// written. A growable sink is written directly, so if one of its writes
320    /// fails, the octets written before it stay in the sink; see
321    /// [`FrameSink`].
322    pub fn encode_to_sink<S: FrameSink + ?Sized>(self, sink: &mut S) -> Result<usize, FrameError> {
323        let length = self.checked_length()?;
324        let total = wire_length(length);
325        if let Some(available) = sink.remaining_capacity()
326            && available < total
327        {
328            return Err(FrameError::BufferExhausted {
329                needed: total,
330                available,
331            });
332        }
333        let fields = [
334            self.header.frame_type.to_raw(),
335            self.header.destination,
336            self.header.source,
337            (length >> 8) as u8,
338            length as u8,
339        ];
340        let mut head = [0u8; DATA_OFFSET];
341        head[..PREAMBLE.len()].copy_from_slice(&PREAMBLE);
342        head[PREAMBLE.len()..DATA_OFFSET - 1].copy_from_slice(&fields);
343        head[DATA_OFFSET - 1] = crc8(&fields);
344        sink.write(&head)?;
345        if self.header.frame_type.is_cobs() {
346            cobs::write_fields(self.data, sink)?;
347        } else if length != 0 {
348            sink.write(self.data)?;
349            sink.write(&crc16(self.data).to_le_bytes())?;
350        }
351        Ok(total)
352    }
353
354    /// Apply the encode checks and return the frame's Length field.
355    fn checked_length(&self) -> Result<usize, FrameError> {
356        let frame_type = self.header.frame_type;
357        let length = if frame_type.is_cobs() {
358            cobs::frame_length(self.data)
359        } else {
360            self.data.len()
361        };
362        check_header_fields(frame_type, self.header.source, length)?;
363        if length != 0 && !frame_type.allows_data() {
364            return Err(FrameError::UnexpectedData {
365                raw_type: frame_type.to_raw(),
366                length,
367            });
368        }
369        if length < 2 && frame_type.is_proprietary() {
370            return Err(FrameError::MissingVendorId {
371                raw_type: frame_type.to_raw(),
372                length,
373            });
374        }
375        Ok(length)
376    }
377}