Skip to main content

rustbac_core/
npdu.rs

1use crate::encoding::{reader::Reader, writer::Writer};
2use crate::{DecodeError, EncodeError};
3
4/// BACnet network layer protocol version (always `0x01`).
5pub const NPDU_VERSION: u8 = 0x01;
6
7/// A network-layer address consisting of a network number and a MAC address.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct NpduAddress {
10    /// The DNET/SNET network number.
11    pub network: u16,
12    /// MAC address bytes (up to 6).
13    pub mac: [u8; 6],
14    /// Number of valid bytes in `mac`.
15    pub mac_len: u8,
16}
17
18/// BACnet Network Protocol Data Unit (NPDU) header.
19///
20/// Handles encoding and decoding of the NPDU including optional source/
21/// destination addresses, hop count, and network-layer message fields.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct Npdu {
24    pub control: u8,
25    pub destination: Option<NpduAddress>,
26    pub source: Option<NpduAddress>,
27    pub hop_count: Option<u8>,
28    pub message_type: Option<u8>,
29    pub vendor_id: Option<u16>,
30}
31
32impl Npdu {
33    pub const fn new(control: u8) -> Self {
34        Self {
35            control,
36            destination: None,
37            source: None,
38            hop_count: None,
39            message_type: None,
40            vendor_id: None,
41        }
42    }
43
44    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
45        // Derive control bits from optional fields so the header is always
46        // consistent, regardless of what the caller set in `self.control`.
47        let mut control = self.control;
48        if self.destination.is_some() {
49            control |= 0x20;
50        } else {
51            control &= !0x20;
52        }
53        if self.source.is_some() {
54            control |= 0x08;
55        } else {
56            control &= !0x08;
57        }
58        if self.message_type.is_some() {
59            control |= 0x80;
60        } else {
61            control &= !0x80;
62        }
63
64        w.write_u8(NPDU_VERSION)?;
65        w.write_u8(control)?;
66
67        if let Some(dest) = self.destination {
68            encode_addr(w, dest)?;
69        }
70        if let Some(src) = self.source {
71            encode_addr(w, src)?;
72        }
73        if self.destination.is_some() {
74            w.write_u8(self.hop_count.unwrap_or(255))?;
75        }
76        if let Some(mt) = self.message_type {
77            w.write_u8(mt)?;
78            if mt >= 0x80 {
79                w.write_be_u16(self.vendor_id.unwrap_or(0))?;
80            }
81        }
82        Ok(())
83    }
84
85    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
86        let version = r.read_u8()?;
87        if version != NPDU_VERSION {
88            return Err(DecodeError::InvalidValue);
89        }
90
91        let control = r.read_u8()?;
92        let has_dest = (control & 0x20) != 0;
93        let has_src = (control & 0x08) != 0;
94        let is_network_msg = (control & 0x80) != 0;
95
96        let destination = if has_dest {
97            Some(decode_addr(r)?)
98        } else {
99            None
100        };
101        let source = if has_src { Some(decode_addr(r)?) } else { None };
102        let hop_count = if has_dest { Some(r.read_u8()?) } else { None };
103
104        let (message_type, vendor_id) = if is_network_msg {
105            let mt = r.read_u8()?;
106            let vid = if mt >= 0x80 {
107                Some(r.read_be_u16()?)
108            } else {
109                None
110            };
111            (Some(mt), vid)
112        } else {
113            (None, None)
114        };
115
116        Ok(Self {
117            control,
118            destination,
119            source,
120            hop_count,
121            message_type,
122            vendor_id,
123        })
124    }
125}
126
127fn encode_addr(w: &mut Writer<'_>, addr: NpduAddress) -> Result<(), EncodeError> {
128    if addr.mac_len as usize > addr.mac.len() {
129        return Err(EncodeError::InvalidLength);
130    }
131    w.write_be_u16(addr.network)?;
132    w.write_u8(addr.mac_len)?;
133    w.write_all(&addr.mac[..addr.mac_len as usize])
134}
135
136fn decode_addr(r: &mut Reader<'_>) -> Result<NpduAddress, DecodeError> {
137    let network = r.read_be_u16()?;
138    let mac_len = r.read_u8()?;
139    if mac_len as usize > 6 {
140        return Err(DecodeError::InvalidLength);
141    }
142    let mut mac = [0u8; 6];
143    let src = r.read_exact(mac_len as usize)?;
144    mac[..mac_len as usize].copy_from_slice(src);
145    Ok(NpduAddress {
146        network,
147        mac,
148        mac_len,
149    })
150}
151
152#[cfg(test)]
153mod tests {
154    use super::{Npdu, NpduAddress};
155    use crate::encoding::{reader::Reader, writer::Writer};
156
157    #[test]
158    fn npdu_roundtrip() {
159        let mut p = Npdu::new(0x20);
160        p.destination = Some(NpduAddress {
161            network: 1,
162            mac: [192, 168, 1, 2, 0xBA, 0xC0],
163            mac_len: 6,
164        });
165        p.hop_count = Some(255);
166
167        let mut buf = [0u8; 32];
168        let mut w = Writer::new(&mut buf);
169        p.encode(&mut w).unwrap();
170
171        let mut r = Reader::new(w.as_written());
172        let dec = Npdu::decode(&mut r).unwrap();
173        assert_eq!(dec.control, p.control);
174        assert_eq!(dec.destination.unwrap().network, 1);
175    }
176
177    #[test]
178    fn network_message_vendor_id_only_for_vendor_types() {
179        let mut p = Npdu::new(0x80);
180        p.message_type = Some(0x80);
181        p.vendor_id = Some(260);
182
183        let mut buf = [0u8; 16];
184        let mut w = Writer::new(&mut buf);
185        p.encode(&mut w).unwrap();
186
187        let mut r = Reader::new(w.as_written());
188        let dec = Npdu::decode(&mut r).unwrap();
189        assert_eq!(dec.message_type, Some(0x80));
190        assert_eq!(dec.vendor_id, Some(260));
191    }
192}