Skip to main content

bacnet_encoding/constructed/
network_port.rs

1//! Clause 21 codecs for the Network Port object's host and broadcast-table
2//! values: `BACnetHostAddress`, `BACnetHostNPort`, `BACnetBDTEntry` and
3//! `BACnetFDTEntry` (Clause 12.56).
4//!
5//! The shapes, as the Clause 21 productions lay them out:
6//!
7//! - `BACnetHostAddress` is a CHOICE of primitive alternatives: `[0]` NULL
8//!   for no host, `[1]` OCTET STRING for an IP address (4 or 16 octets) and
9//!   `[2]` CharacterString for a host name.
10//! - `BACnetHostNPort` is a SEQUENCE of the host, under `[0]`, and the port
11//!   as an Unsigned16 under `[1]`. Since the host is a CHOICE, its `[0]` is
12//!   an opening and closing pair around the chosen alternative.
13//! - `BACnetBDTEntry` is a SEQUENCE of the BBMD's `BACnetHostNPort` under an
14//!   opening and closing `[0]`, then the optional broadcast mask, a 4-octet
15//!   OCTET STRING under `[1]`.
16//! - `BACnetFDTEntry` is a SEQUENCE of three primitives: the registrant's
17//!   address under `[0]` (6 octets for B/IP, 18 for B/IPv6), then the time
18//!   to live and the time remaining, each an Unsigned16, under `[1]` and
19//!   `[2]`.
20//!
21//! A BACnetLIST of entries is the plain concatenation of its elements. None
22//! of this is the 10-octet BVLL layout Read-Broadcast-Distribution-Table-Ack
23//! and Read-Foreign-Device-Table-Ack carry (Annex J.2), which the transport
24//! crate owns.
25//!
26//! Decoding is strict: a missing, misplaced, truncated or wrong-sized member,
27//! or a port or time past an Unsigned16, is an error.
28
29#[cfg(not(feature = "std"))]
30use alloc::vec::Vec;
31
32use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
33
34use bacnet_types::constructed::{
35    BACnetBDTEntry, BACnetFDTEntry, BACnetHostAddress, BACnetHostNPort,
36};
37use bacnet_types::error::Error;
38use bytes::BytesMut;
39
40use super::tagged::{
41    contents, decode_ctx_character_string, decode_ctx_fixed, decode_ctx_unsigned,
42    decode_optional_ctx, expect_closing, expect_opening, misplaced_tag,
43};
44use super::MAX_FRAMED_ITEMS;
45use crate::primitives;
46use crate::tags::{self, TagClass};
47
48/// Encode one `BACnetHostAddress` CHOICE alternative.
49///
50/// Fails, leaving `buf` untouched, only when a host name is too long to
51/// encode.
52pub fn encode_host_address(buf: &mut BytesMut, host: &BACnetHostAddress) -> Result<(), Error> {
53    match host {
54        BACnetHostAddress::None => tags::encode_tag(buf, 0, TagClass::Context, 0),
55        BACnetHostAddress::Ip(IpAddr::V4(ip)) => {
56            primitives::encode_ctx_octet_string(buf, 1, &ip.octets())
57        }
58        BACnetHostAddress::Ip(IpAddr::V6(ip)) => {
59            primitives::encode_ctx_octet_string(buf, 1, &ip.octets())
60        }
61        BACnetHostAddress::Name(name) => {
62            let mut encoded = BytesMut::new();
63            primitives::encode_ctx_character_string(&mut encoded, 2, name)?;
64            buf.extend_from_slice(&encoded);
65        }
66    }
67    Ok(())
68}
69
70/// Decode one `BACnetHostAddress` CHOICE alternative at `offset`; returns it
71/// and the offset past it.
72///
73/// A NULL with contents, an IP address of other than 4 or 16 octets, an
74/// undecodable name, or any tag other than primitive `[0]`, `[1]` or `[2]` is
75/// a decode error.
76pub fn decode_host_address(
77    data: &[u8],
78    offset: usize,
79) -> Result<(BACnetHostAddress, usize), Error> {
80    let what = "BACnetHostAddress";
81    let (tag, start) = tags::decode_tag(data, offset)?;
82    if tag.is_context(0) {
83        if tag.length != 0 {
84            return Err(Error::decoding(
85                offset,
86                format!("{what} none [0]: NULL has {} contents octets", tag.length),
87            ));
88        }
89        return Ok((BACnetHostAddress::None, start));
90    }
91    if tag.is_context(1) {
92        let (octets, end) = contents(data, start, tag.length)?;
93        let ip = match *octets {
94            [a, b, c, d] => IpAddr::V4(Ipv4Addr::new(a, b, c, d)),
95            _ => match <[u8; 16]>::try_from(octets) {
96                Ok(v6) => IpAddr::V6(Ipv6Addr::from(v6)),
97                Err(_) => {
98                    return Err(Error::decoding(
99                        offset,
100                        format!(
101                            "{what} ip-address [1]: {} octets, expected 4 or 16",
102                            octets.len()
103                        ),
104                    ))
105                }
106            },
107        };
108        return Ok((BACnetHostAddress::Ip(ip), end));
109    }
110    if tag.is_context(2) {
111        let (name, end) = decode_ctx_character_string(data, offset, 2, what)?;
112        return Ok((BACnetHostAddress::Name(name), end));
113    }
114    Err(misplaced_tag(
115        data,
116        &tag,
117        None,
118        offset,
119        format!("{what}: expected none [0], ip-address [1] or name [2]"),
120    ))
121}
122
123/// Encode one bare `BACnetHostNPort` SEQUENCE.
124///
125/// Fails, leaving `buf` untouched, only when a host name is too long to
126/// encode.
127pub fn encode_host_n_port(buf: &mut BytesMut, value: &BACnetHostNPort) -> Result<(), Error> {
128    let mut encoded = BytesMut::new();
129    tags::encode_opening_tag(&mut encoded, 0);
130    encode_host_address(&mut encoded, &value.host)?;
131    tags::encode_closing_tag(&mut encoded, 0);
132    primitives::encode_ctx_unsigned(&mut encoded, 1, value.port.into());
133    buf.extend_from_slice(&encoded);
134    Ok(())
135}
136
137/// Decode one bare `BACnetHostNPort` SEQUENCE at `offset`; returns it and the
138/// offset past its port.
139pub fn decode_host_n_port(data: &[u8], offset: usize) -> Result<(BACnetHostNPort, usize), Error> {
140    let what = "BACnetHostNPort";
141    let pos = expect_opening(data, offset, 0, what)?;
142    let (host, pos) = decode_host_address(data, pos)?;
143    let pos = expect_closing(data, pos, 0, what)?;
144    let (port, pos) = decode_ctx_unsigned::<u16>(data, pos, 1, what)?;
145    Ok((BACnetHostNPort { host, port }, pos))
146}
147
148/// Encode one bare `BACnetBDTEntry` SEQUENCE.
149///
150/// Fails, leaving `buf` untouched, only when a host name is too long to
151/// encode.
152pub fn encode_bdt_entry(buf: &mut BytesMut, entry: &BACnetBDTEntry) -> Result<(), Error> {
153    let mut encoded = BytesMut::new();
154    tags::encode_opening_tag(&mut encoded, 0);
155    encode_host_n_port(&mut encoded, &entry.bbmd_address)?;
156    tags::encode_closing_tag(&mut encoded, 0);
157    if let Some(mask) = &entry.broadcast_mask {
158        primitives::encode_ctx_octet_string(&mut encoded, 1, mask);
159    }
160    buf.extend_from_slice(&encoded);
161    Ok(())
162}
163
164/// Decode one bare `BACnetBDTEntry` SEQUENCE at `offset`; returns it and the
165/// offset past it. A broadcast mask, when present, must be 4 octets.
166pub fn decode_bdt_entry(data: &[u8], offset: usize) -> Result<(BACnetBDTEntry, usize), Error> {
167    let what = "BACnetBDTEntry";
168    let pos = expect_opening(data, offset, 0, what)?;
169    let (bbmd_address, pos) = decode_host_n_port(data, pos)?;
170    let pos = expect_closing(data, pos, 0, what)?;
171    let (mask, pos) = decode_optional_ctx(data, pos, 1, what, |data, at, tag, what| {
172        decode_ctx_fixed(data, at, tag, 4, "broadcast mask", what)
173    })?;
174    let broadcast_mask = mask.map(|octets| [octets[0], octets[1], octets[2], octets[3]]);
175    Ok((
176        BACnetBDTEntry {
177            bbmd_address,
178            broadcast_mask,
179        },
180        pos,
181    ))
182}
183
184/// Encode one bare `BACnetFDTEntry` SEQUENCE.
185pub fn encode_fdt_entry(buf: &mut BytesMut, entry: &BACnetFDTEntry) {
186    let mut address = [0u8; 18];
187    let len = match entry.address {
188        SocketAddr::V4(v4) => {
189            address[..4].copy_from_slice(&v4.ip().octets());
190            address[4..6].copy_from_slice(&v4.port().to_be_bytes());
191            6
192        }
193        SocketAddr::V6(v6) => {
194            address[..16].copy_from_slice(&v6.ip().octets());
195            address[16..].copy_from_slice(&v6.port().to_be_bytes());
196            18
197        }
198    };
199    primitives::encode_ctx_octet_string(buf, 0, &address[..len]);
200    primitives::encode_ctx_unsigned(buf, 1, entry.time_to_live.into());
201    primitives::encode_ctx_unsigned(buf, 2, entry.remaining_time_to_live.into());
202}
203
204/// Decode one bare `BACnetFDTEntry` SEQUENCE at `offset`; returns it and the
205/// offset past it. The address must be 6 or 18 octets.
206pub fn decode_fdt_entry(data: &[u8], offset: usize) -> Result<(BACnetFDTEntry, usize), Error> {
207    let what = "BACnetFDTEntry";
208    let (tag, start) = tags::decode_tag(data, offset)?;
209    if !tag.is_context(0) {
210        return Err(misplaced_tag(
211            data,
212            &tag,
213            Some(0),
214            offset,
215            format!("{what}: expected bacnetip-address [0]"),
216        ));
217    }
218    let (octets, pos) = contents(data, start, tag.length)?;
219    let address = match octets.len() {
220        6 => SocketAddr::V4(SocketAddrV4::new(
221            Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]),
222            u16::from_be_bytes([octets[4], octets[5]]),
223        )),
224        18 => {
225            let mut ip = [0u8; 16];
226            ip.copy_from_slice(&octets[..16]);
227            SocketAddr::V6(SocketAddrV6::new(
228                Ipv6Addr::from(ip),
229                u16::from_be_bytes([octets[16], octets[17]]),
230                0,
231                0,
232            ))
233        }
234        other => {
235            return Err(Error::decoding(
236                offset,
237                format!("{what} bacnetip-address [0]: {other} octets, expected 6 or 18"),
238            ))
239        }
240    };
241    let (time_to_live, pos) = decode_ctx_unsigned::<u16>(data, pos, 1, what)?;
242    let (remaining_time_to_live, pos) = decode_ctx_unsigned::<u16>(data, pos, 2, what)?;
243    Ok((
244        BACnetFDTEntry {
245            address,
246            time_to_live,
247            remaining_time_to_live,
248        },
249        pos,
250    ))
251}
252
253/// Encode a BACnetLIST of `BACnetBDTEntry` as concatenated elements.
254///
255/// Fails, leaving `buf` untouched, when any element fails to encode.
256pub fn encode_bdt_entry_list(buf: &mut BytesMut, entries: &[BACnetBDTEntry]) -> Result<(), Error> {
257    let mut encoded = BytesMut::new();
258    for entry in entries {
259        encode_bdt_entry(&mut encoded, entry)?;
260    }
261    buf.extend_from_slice(&encoded);
262    Ok(())
263}
264
265/// Encode a BACnetLIST of `BACnetFDTEntry` as concatenated elements.
266pub fn encode_fdt_entry_list(buf: &mut BytesMut, entries: &[BACnetFDTEntry]) {
267    for entry in entries {
268        encode_fdt_entry(buf, entry);
269    }
270}
271
272/// Decode a complete BACnetLIST of `BACnetBDTEntry`. Every byte must belong
273/// to an element; an empty input is an empty list.
274pub fn decode_bdt_entry_list(data: &[u8]) -> Result<Vec<BACnetBDTEntry>, Error> {
275    decode_list(data, "BACnetBDTEntry list", decode_bdt_entry)
276}
277
278/// Decode a complete BACnetLIST of `BACnetFDTEntry`. Every byte must belong
279/// to an element; an empty input is an empty list.
280pub fn decode_fdt_entry_list(data: &[u8]) -> Result<Vec<BACnetFDTEntry>, Error> {
281    decode_list(data, "BACnetFDTEntry list", decode_fdt_entry)
282}
283
284fn decode_list<T>(
285    data: &[u8],
286    what: &str,
287    decode: impl Fn(&[u8], usize) -> Result<(T, usize), Error>,
288) -> Result<Vec<T>, Error> {
289    let mut values = Vec::new();
290    let mut offset = 0;
291    while offset < data.len() {
292        if values.len() == MAX_FRAMED_ITEMS {
293            return Err(Error::decoding(
294                offset,
295                format!("{what}: more than {MAX_FRAMED_ITEMS} entries"),
296            ));
297        }
298        let (value, end) = decode(data, offset)?;
299        values.push(value);
300        offset = end;
301    }
302    Ok(values)
303}