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        w.write_u8(NPDU_VERSION)?;
46        w.write_u8(self.control)?;
47
48        if let Some(dest) = self.destination {
49            encode_addr(w, dest)?;
50        }
51        if let Some(src) = self.source {
52            encode_addr(w, src)?;
53        }
54        if self.destination.is_some() {
55            w.write_u8(self.hop_count.unwrap_or(255))?;
56        }
57        if (self.control & 0x80) != 0 {
58            w.write_u8(self.message_type.unwrap_or(0))?;
59            if matches!(self.message_type, Some(0x80..=0xFF)) {
60                w.write_be_u16(self.vendor_id.unwrap_or(0))?;
61            }
62        }
63        Ok(())
64    }
65
66    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
67        let version = r.read_u8()?;
68        if version != NPDU_VERSION {
69            return Err(DecodeError::InvalidValue);
70        }
71
72        let control = r.read_u8()?;
73        let has_dest = (control & 0x20) != 0;
74        let has_src = (control & 0x08) != 0;
75        let is_network_msg = (control & 0x80) != 0;
76
77        let destination = if has_dest {
78            Some(decode_addr(r)?)
79        } else {
80            None
81        };
82        let source = if has_src { Some(decode_addr(r)?) } else { None };
83        let hop_count = if has_dest { Some(r.read_u8()?) } else { None };
84
85        let (message_type, vendor_id) = if is_network_msg {
86            let mt = r.read_u8()?;
87            let vid = if mt >= 0x80 {
88                Some(r.read_be_u16()?)
89            } else {
90                None
91            };
92            (Some(mt), vid)
93        } else {
94            (None, None)
95        };
96
97        Ok(Self {
98            control,
99            destination,
100            source,
101            hop_count,
102            message_type,
103            vendor_id,
104        })
105    }
106}
107
108fn encode_addr(w: &mut Writer<'_>, addr: NpduAddress) -> Result<(), EncodeError> {
109    if addr.mac_len as usize > addr.mac.len() {
110        return Err(EncodeError::InvalidLength);
111    }
112    w.write_be_u16(addr.network)?;
113    w.write_u8(addr.mac_len)?;
114    w.write_all(&addr.mac[..addr.mac_len as usize])
115}
116
117fn decode_addr(r: &mut Reader<'_>) -> Result<NpduAddress, DecodeError> {
118    let network = r.read_be_u16()?;
119    let mac_len = r.read_u8()?;
120    if mac_len as usize > 6 {
121        return Err(DecodeError::InvalidLength);
122    }
123    let mut mac = [0u8; 6];
124    let src = r.read_exact(mac_len as usize)?;
125    mac[..mac_len as usize].copy_from_slice(src);
126    Ok(NpduAddress {
127        network,
128        mac,
129        mac_len,
130    })
131}
132
133#[cfg(test)]
134mod tests {
135    use super::{Npdu, NpduAddress};
136    use crate::encoding::{reader::Reader, writer::Writer};
137
138    #[test]
139    fn npdu_roundtrip() {
140        let mut p = Npdu::new(0x20);
141        p.destination = Some(NpduAddress {
142            network: 1,
143            mac: [192, 168, 1, 2, 0xBA, 0xC0],
144            mac_len: 6,
145        });
146        p.hop_count = Some(255);
147
148        let mut buf = [0u8; 32];
149        let mut w = Writer::new(&mut buf);
150        p.encode(&mut w).unwrap();
151
152        let mut r = Reader::new(w.as_written());
153        let dec = Npdu::decode(&mut r).unwrap();
154        assert_eq!(dec.control, p.control);
155        assert_eq!(dec.destination.unwrap().network, 1);
156    }
157
158    #[test]
159    fn network_message_vendor_id_only_for_vendor_types() {
160        let mut p = Npdu::new(0x80);
161        p.message_type = Some(0x80);
162        p.vendor_id = Some(260);
163
164        let mut buf = [0u8; 16];
165        let mut w = Writer::new(&mut buf);
166        p.encode(&mut w).unwrap();
167
168        let mut r = Reader::new(w.as_written());
169        let dec = Npdu::decode(&mut r).unwrap();
170        assert_eq!(dec.message_type, Some(0x80));
171        assert_eq!(dec.vendor_id, Some(260));
172    }
173}