1use core::fmt;
14
15use crate::{GLOBAL_NETWORK, MAX_MAC_LEN, NetworkMessageType, NetworkPriority, VERSION};
16
17pub(crate) const NETWORK_MESSAGE: u8 = 0x80;
19pub(crate) const DESTINATION: u8 = 0x20;
21pub(crate) const SOURCE: u8 = 0x08;
23pub(crate) const EXPECTING_REPLY: u8 = 0x04;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Address<'a> {
29 pub network: u16,
31 pub mac: &'a [u8],
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct Npdu<'a> {
45 pub expecting_reply: bool,
47 pub priority: NetworkPriority,
49 pub destination: Option<Address<'a>>,
51 pub source: Option<Address<'a>>,
53 pub hop_count: u8,
56 pub message_type: Option<NetworkMessageType>,
58 pub vendor_id: Option<u16>,
61 pub payload: &'a [u8],
63 pub payload_offset: usize,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum AddressField {
71 Destination,
73 Source,
75}
76
77impl AddressField {
78 #[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
88impl 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum DecodeError<'a> {
105 AddressTooLong {
108 field: AddressField,
110 length: u8,
112 dnet: Option<u16>,
114 source: Option<Address<'a>>,
118 },
119 Malformed(Malformed),
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128#[non_exhaustive]
129pub enum Malformed {
130 Truncated,
132 Version,
134 ReservedNetwork,
136 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 #[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 #[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 #[must_use]
202 pub const fn is_network_message(&self) -> bool {
203 self.message_type.is_some()
204 }
205
206 #[must_use]
208 pub fn is_global(&self) -> bool {
209 self.destination
210 .is_some_and(|destination| destination.network == GLOBAL_NETWORK)
211 }
212
213 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
307fn 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
315fn 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
322fn 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}