Skip to main content

bacnet_npdu/
view.rs

1//! The borrowed view of an NPDU (Clause 6.2): [`Npdu`] and
2//! [`Npdu::decode`].
3//!
4//! [`Npdu::decode`] reads the protocol control information in place and
5//! borrows every variable field from the frame: the DADR and SADR, and the
6//! payload behind the NPCI, with its offset so a host can slice its own
7//! buffer. A DLEN or SLEN past [`MAX_MAC_LEN`] is refused before its
8//! address is read, apart from every other fault
9//! ([`DecodeError::AddressTooLong`]), with what a router needs to address
10//! its reject. The view keeps no reserved control bits, so encoding a
11//! decoded NPDU gives the canonical octets for it.
12
13use core::fmt;
14
15use crate::{GLOBAL_NETWORK, MAX_MAC_LEN, NetworkMessageType, NetworkPriority, VERSION};
16
17/// Control octet: a network-layer message, not an APDU.
18pub(crate) const NETWORK_MESSAGE: u8 = 0x80;
19/// Control octet: DNET, DLEN, DADR and the hop count are present.
20pub(crate) const DESTINATION: u8 = 0x20;
21/// Control octet: SNET, SLEN and SADR are present.
22pub(crate) const SOURCE: u8 = 0x08;
23/// Control octet: the sender expects a reply.
24pub(crate) const EXPECTING_REPLY: u8 = 0x04;
25
26/// A network and a MAC on it: a DNET and DADR, or an SNET and SADR.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Address<'a> {
29    /// The network number.
30    pub network: u16,
31    /// The MAC, borrowed from the frame. An empty DADR is a broadcast on
32    /// that network.
33    pub mac: &'a [u8],
34}
35
36/// An NPDU's control information, with its payload, borrowed from a frame.
37///
38/// A network-layer message is one whose [`message_type`] is set; without
39/// it the payload is an APDU. `bacnet-encoding`'s owned `Npdu` holds the
40/// same fields in owned buffers and decodes and encodes through this view.
41///
42/// [`message_type`]: Self::message_type
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct Npdu<'a> {
45    /// Whether the sender expects a reply.
46    pub expecting_reply: bool,
47    /// The network priority: the control octet's two low bits.
48    pub priority: NetworkPriority,
49    /// DNET and DADR, for a remote destination or a global broadcast.
50    pub destination: Option<Address<'a>>,
51    /// SNET and SADR, which a router adds to what it relays.
52    pub source: Option<Address<'a>>,
53    /// The hop count. Only an NPDU with a destination carries one; without
54    /// it this is 255, and the encoder leaves it out.
55    pub hop_count: u8,
56    /// The network-layer message type, or `None` for an APDU.
57    pub message_type: Option<NetworkMessageType>,
58    /// The vendor ID of a proprietary message type
59    /// ([`NetworkMessageType::is_proprietary`]).
60    pub vendor_id: Option<u16>,
61    /// The APDU, or the message's data, behind the NPCI.
62    pub payload: &'a [u8],
63    /// Where [`payload`](Self::payload) starts in the frame. Ignored by the
64    /// encoder.
65    pub payload_offset: usize,
66}
67
68/// Which address a [`DecodeError::AddressTooLong`] concerns.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum AddressField {
71    /// DLEN and DADR.
72    Destination,
73    /// SLEN and SADR.
74    Source,
75}
76
77impl AddressField {
78    /// The name of this address's length octet: `DLEN` or `SLEN`.
79    #[must_use]
80    pub const fn length_octet(self) -> &'static str {
81        match self {
82            Self::Destination => "DLEN",
83            Self::Source => "SLEN",
84        }
85    }
86}
87
88/// `destination` or `source`.
89impl fmt::Display for AddressField {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.write_str(match self {
92            Self::Destination => "destination",
93            Self::Source => "source",
94        })
95    }
96}
97
98/// Why [`Npdu::decode`] refused a frame.
99///
100/// The two cases are the split a router acts on: an over-long address draws
101/// a reject, anything else is dropped. So the enum is exhaustive, and a new
102/// fault joins [`Malformed`], which is `#[non_exhaustive]`.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum DecodeError<'a> {
105    /// A DLEN or SLEN past [`MAX_MAC_LEN`]: an address no data link uses,
106    /// which draws reject reason 6 (Clause 6.4.4) if the frame names a DNET.
107    AddressTooLong {
108        /// Which address's length octet it is.
109        field: AddressField,
110        /// The length octet as received.
111        length: u8,
112        /// The DNET, when the frame has one: it precedes both lengths.
113        dnet: Option<u16>,
114        /// The SNET and SADR behind an over-long DADR, when the frame holds
115        /// a complete, valid one after the DADR octets the DLEN announces:
116        /// where the reject goes. `None` when the SADR is the long one.
117        source: Option<Address<'a>>,
118    },
119    /// Anything else.
120    Malformed(Malformed),
121}
122
123/// What made a frame malformed.
124///
125/// It is `#[non_exhaustive]`, so a new fault isn't a breaking change: a
126/// `match` needs a wildcard arm.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128#[non_exhaustive]
129pub enum Malformed {
130    /// The frame ends inside a field it announces.
131    Truncated,
132    /// The first octet is not protocol version 1.
133    Version,
134    /// A DNET of 0, or an SNET of 0 or 0xFFFF.
135    ReservedNetwork,
136    /// An SLEN of 0.
137    EmptySource,
138}
139
140impl From<Malformed> for DecodeError<'_> {
141    fn from(fault: Malformed) -> Self {
142        Self::Malformed(fault)
143    }
144}
145
146impl fmt::Display for Malformed {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        f.write_str(match self {
149            Self::Truncated => "NPDU truncated",
150            Self::Version => "unsupported BACnet protocol version",
151            Self::ReservedNetwork => "NPDU names a reserved network number",
152            Self::EmptySource => "NPDU source SLEN=0 is invalid",
153        })
154    }
155}
156
157impl core::error::Error for Malformed {}
158
159impl fmt::Display for DecodeError<'_> {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        match self {
162            Self::AddressTooLong { field, length, .. } => write!(
163                f,
164                "NPDU {}={length} exceeds the {MAX_MAC_LEN}-octet address limit",
165                field.length_octet()
166            ),
167            Self::Malformed(fault) => fault.fmt(f),
168        }
169    }
170}
171
172impl core::error::Error for DecodeError<'_> {}
173
174impl<'a> Npdu<'a> {
175    /// An APDU for the local network: no addresses, normal priority.
176    #[must_use]
177    pub const fn apdu(payload: &'a [u8]) -> Self {
178        Self {
179            expecting_reply: false,
180            priority: NetworkPriority::Normal,
181            destination: None,
182            source: None,
183            hop_count: 255,
184            message_type: None,
185            vendor_id: None,
186            payload,
187            payload_offset: 0,
188        }
189    }
190
191    /// A network-layer message of a standard type for the local network: no
192    /// addresses, normal priority, no vendor ID.
193    #[must_use]
194    pub const fn message(message_type: NetworkMessageType, payload: &'a [u8]) -> Self {
195        let mut npdu = Self::apdu(payload);
196        npdu.message_type = Some(message_type);
197        npdu
198    }
199
200    /// Whether it carries a network-layer message rather than an APDU.
201    #[must_use]
202    pub const fn is_network_message(&self) -> bool {
203        self.message_type.is_some()
204    }
205
206    /// Whether its DNET is the global broadcast network.
207    #[must_use]
208    pub fn is_global(&self) -> bool {
209        self.destination
210            .is_some_and(|destination| destination.network == GLOBAL_NETWORK)
211    }
212
213    /// Read the NPDU at the start of `frame`, borrowing from it.
214    ///
215    /// The checks run in the order the fields come, so a frame with several
216    /// faults reports the first one. Reserved control bits are read past.
217    pub fn decode(frame: &'a [u8]) -> Result<Self, DecodeError<'a>> {
218        let [version, control, ..] = *frame else {
219            return Err(Malformed::Truncated.into());
220        };
221        if version != VERSION {
222            return Err(Malformed::Version.into());
223        }
224        let has_source = control & SOURCE != 0;
225        let mut at = 2;
226
227        let mut destination = None;
228        if control & DESTINATION != 0 {
229            let (network, length) = network_and_length(frame, at)?;
230            if network == 0 {
231                return Err(Malformed::ReservedNetwork.into());
232            }
233            at += 3;
234            if usize::from(length) > MAX_MAC_LEN {
235                return Err(DecodeError::AddressTooLong {
236                    field: AddressField::Destination,
237                    length,
238                    dnet: Some(network),
239                    source: if has_source {
240                        source_behind(frame, at + usize::from(length))
241                    } else {
242                        None
243                    },
244                });
245            }
246            let mac = octets(frame, at, length)?;
247            at += mac.len();
248            destination = Some(Address { network, mac });
249        }
250
251        let mut source = None;
252        if has_source {
253            let (network, length) = network_and_length(frame, at)?;
254            if network == 0 || network == GLOBAL_NETWORK {
255                return Err(Malformed::ReservedNetwork.into());
256            }
257            at += 3;
258            if length == 0 {
259                return Err(Malformed::EmptySource.into());
260            }
261            if usize::from(length) > MAX_MAC_LEN {
262                return Err(DecodeError::AddressTooLong {
263                    field: AddressField::Source,
264                    length,
265                    dnet: destination.map(|destination| destination.network),
266                    source: None,
267                });
268            }
269            let mac = octets(frame, at, length)?;
270            at += mac.len();
271            source = Some(Address { network, mac });
272        }
273
274        let mut hop_count = 255;
275        if destination.is_some() {
276            hop_count = *frame.get(at).ok_or(Malformed::Truncated)?;
277            at += 1;
278        }
279
280        let mut message_type = None;
281        let mut vendor_id = None;
282        if control & NETWORK_MESSAGE != 0 {
283            let kind = NetworkMessageType::from_raw(*frame.get(at).ok_or(Malformed::Truncated)?);
284            at += 1;
285            if kind.is_proprietary() {
286                let vendor = octets(frame, at, 2)?;
287                vendor_id = Some(u16::from_be_bytes([vendor[0], vendor[1]]));
288                at += 2;
289            }
290            message_type = Some(kind);
291        }
292
293        Ok(Self {
294            expecting_reply: control & EXPECTING_REPLY != 0,
295            priority: NetworkPriority::from_control(control),
296            destination,
297            source,
298            hop_count,
299            message_type,
300            vendor_id,
301            payload: &frame[at..],
302            payload_offset: at,
303        })
304    }
305}
306
307/// The two-octet network number and the length octet at `at`.
308fn network_and_length(frame: &[u8], at: usize) -> Result<(u16, u8), Malformed> {
309    match octets(frame, at, 3)? {
310        &[high, low, length] => Ok((u16::from_be_bytes([high, low]), length)),
311        _ => Err(Malformed::Truncated),
312    }
313}
314
315/// The `length` octets at `at`, all of them present.
316fn octets(frame: &[u8], at: usize, length: u8) -> Result<&[u8], Malformed> {
317    frame
318        .get(at..at + usize::from(length))
319        .ok_or(Malformed::Truncated)
320}
321
322/// The SNET and SADR at `at`, behind a DADR too long to read: only one the
323/// decoder would accept, complete, so a router's reject reaches a real node.
324fn source_behind(frame: &[u8], at: usize) -> Option<Address<'_>> {
325    let (network, length) = network_and_length(frame, at).ok()?;
326    if network == 0 || network == GLOBAL_NETWORK || length == 0 {
327        return None;
328    }
329    if usize::from(length) > MAX_MAC_LEN {
330        return None;
331    }
332    let mac = octets(frame, at + 3, length).ok()?;
333    Some(Address { network, mac })
334}