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}