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::{DecodeError, DecodingKind, 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, Fixed,
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(DecodeError::new(
85                DecodingKind::InvalidEncoding,
86                offset,
87                "none [0]: NULL has {} contents octets",
88            )
89            .within(what)
90            .with_value(tag.length.into())
91            .into());
92        }
93        return Ok((BACnetHostAddress::None, start));
94    }
95    if tag.is_context(1) {
96        let (octets, end) = contents(data, start, tag.length)?;
97        let ip = match *octets {
98            [a, b, c, d] => IpAddr::V4(Ipv4Addr::new(a, b, c, d)),
99            _ => match <[u8; 16]>::try_from(octets) {
100                Ok(v6) => IpAddr::V6(Ipv6Addr::from(v6)),
101                Err(_) => {
102                    return Err(DecodeError::new(
103                        DecodingKind::InvalidEncoding,
104                        offset,
105                        "ip-address [1]: {} octets, expected 4 or 16",
106                    )
107                    .within(what)
108                    .with_value(octets.len() as u64)
109                    .into())
110                }
111            },
112        };
113        return Ok((BACnetHostAddress::Ip(ip), end));
114    }
115    if tag.is_context(2) {
116        let (name, end) = decode_ctx_character_string(data, offset, 2, what)?;
117        return Ok((BACnetHostAddress::Name(name), end));
118    }
119    Err(misplaced(
120        data,
121        &tag,
122        None,
123        offset,
124        "expected none [0], ip-address [1] or name [2]",
125    )
126    .within(what)
127    .into())
128}
129
130/// Encode one bare `BACnetHostNPort` SEQUENCE.
131///
132/// Fails, leaving `buf` untouched, only when a host name is too long to
133/// encode.
134pub fn encode_host_n_port(buf: &mut BytesMut, value: &BACnetHostNPort) -> Result<(), Error> {
135    let mut encoded = BytesMut::new();
136    tags::encode_opening_tag(&mut encoded, 0);
137    encode_host_address(&mut encoded, &value.host)?;
138    tags::encode_closing_tag(&mut encoded, 0);
139    primitives::encode_ctx_unsigned(&mut encoded, 1, value.port.into());
140    buf.extend_from_slice(&encoded);
141    Ok(())
142}
143
144/// Decode one bare `BACnetHostNPort` SEQUENCE at `offset`; returns it and the
145/// offset past its port.
146pub fn decode_host_n_port(data: &[u8], offset: usize) -> Result<(BACnetHostNPort, usize), Error> {
147    let what = "BACnetHostNPort";
148    let pos = expect_opening(data, offset, 0, what)?;
149    let (host, pos) = decode_host_address(data, pos)?;
150    let pos = expect_closing(data, pos, 0, what)?;
151    let (port, pos) = decode_ctx_unsigned::<u16>(data, pos, 1, what)?;
152    Ok((BACnetHostNPort { host, port }, pos))
153}
154
155/// Encode one bare `BACnetBDTEntry` SEQUENCE.
156///
157/// Fails, leaving `buf` untouched, only when a host name is too long to
158/// encode.
159pub fn encode_bdt_entry(buf: &mut BytesMut, entry: &BACnetBDTEntry) -> Result<(), Error> {
160    let mut encoded = BytesMut::new();
161    tags::encode_opening_tag(&mut encoded, 0);
162    encode_host_n_port(&mut encoded, &entry.bbmd_address)?;
163    tags::encode_closing_tag(&mut encoded, 0);
164    if let Some(mask) = &entry.broadcast_mask {
165        primitives::encode_ctx_octet_string(&mut encoded, 1, mask);
166    }
167    buf.extend_from_slice(&encoded);
168    Ok(())
169}
170
171/// Decode one bare `BACnetBDTEntry` SEQUENCE at `offset`; returns it and the
172/// offset past it. A broadcast mask, when present, must be 4 octets.
173pub fn decode_bdt_entry(data: &[u8], offset: usize) -> Result<(BACnetBDTEntry, usize), Error> {
174    let what = "BACnetBDTEntry";
175    let pos = expect_opening(data, offset, 0, what)?;
176    let (bbmd_address, pos) = decode_host_n_port(data, pos)?;
177    let pos = expect_closing(data, pos, 0, what)?;
178    const MASK: Fixed = Fixed {
179        octets: 4,
180        misplaced: "expected context tag [{}] broadcast mask",
181        wrong_length: "[{}] broadcast mask has {} contents octets, expected 4",
182    };
183    let (mask, pos) = decode_optional_ctx(data, pos, 1, what, |data, at, tag, what| {
184        decode_ctx_fixed(data, at, tag, MASK, what)
185    })?;
186    let broadcast_mask = mask.map(|octets| [octets[0], octets[1], octets[2], octets[3]]);
187    Ok((
188        BACnetBDTEntry {
189            bbmd_address,
190            broadcast_mask,
191        },
192        pos,
193    ))
194}
195
196/// Encode one bare `BACnetFDTEntry` SEQUENCE.
197pub fn encode_fdt_entry(buf: &mut BytesMut, entry: &BACnetFDTEntry) {
198    let mut address = [0u8; 18];
199    let len = match entry.address {
200        SocketAddr::V4(v4) => {
201            address[..4].copy_from_slice(&v4.ip().octets());
202            address[4..6].copy_from_slice(&v4.port().to_be_bytes());
203            6
204        }
205        SocketAddr::V6(v6) => {
206            address[..16].copy_from_slice(&v6.ip().octets());
207            address[16..].copy_from_slice(&v6.port().to_be_bytes());
208            18
209        }
210    };
211    primitives::encode_ctx_octet_string(buf, 0, &address[..len]);
212    primitives::encode_ctx_unsigned(buf, 1, entry.time_to_live.into());
213    primitives::encode_ctx_unsigned(buf, 2, entry.remaining_time_to_live.into());
214}
215
216/// Decode one bare `BACnetFDTEntry` SEQUENCE at `offset`; returns it and the
217/// offset past it. The address must be 6 or 18 octets.
218pub fn decode_fdt_entry(data: &[u8], offset: usize) -> Result<(BACnetFDTEntry, usize), Error> {
219    let what = "BACnetFDTEntry";
220    let (tag, start) = tags::decode_tag(data, offset)?;
221    if !tag.is_context(0) {
222        return Err(
223            misplaced(data, &tag, Some(0), offset, "expected bacnetip-address [0]")
224                .within(what)
225                .into(),
226        );
227    }
228    let (octets, pos) = contents(data, start, tag.length)?;
229    let address = match octets.len() {
230        6 => SocketAddr::V4(SocketAddrV4::new(
231            Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]),
232            u16::from_be_bytes([octets[4], octets[5]]),
233        )),
234        18 => {
235            let mut ip = [0u8; 16];
236            ip.copy_from_slice(&octets[..16]);
237            SocketAddr::V6(SocketAddrV6::new(
238                Ipv6Addr::from(ip),
239                u16::from_be_bytes([octets[16], octets[17]]),
240                0,
241                0,
242            ))
243        }
244        other => {
245            return Err(DecodeError::new(
246                DecodingKind::InvalidEncoding,
247                offset,
248                "bacnetip-address [0]: {} octets, expected 6 or 18",
249            )
250            .within(what)
251            .with_value(other as u64)
252            .into())
253        }
254    };
255    let (time_to_live, pos) = decode_ctx_unsigned::<u16>(data, pos, 1, what)?;
256    let (remaining_time_to_live, pos) = decode_ctx_unsigned::<u16>(data, pos, 2, what)?;
257    Ok((
258        BACnetFDTEntry {
259            address,
260            time_to_live,
261            remaining_time_to_live,
262        },
263        pos,
264    ))
265}
266
267/// Encode a BACnetLIST of `BACnetBDTEntry` as concatenated elements.
268///
269/// Fails, leaving `buf` untouched, when any element fails to encode.
270pub fn encode_bdt_entry_list(buf: &mut BytesMut, entries: &[BACnetBDTEntry]) -> Result<(), Error> {
271    let mut encoded = BytesMut::new();
272    for entry in entries {
273        encode_bdt_entry(&mut encoded, entry)?;
274    }
275    buf.extend_from_slice(&encoded);
276    Ok(())
277}
278
279/// Encode a BACnetLIST of `BACnetFDTEntry` as concatenated elements.
280pub fn encode_fdt_entry_list(buf: &mut BytesMut, entries: &[BACnetFDTEntry]) {
281    for entry in entries {
282        encode_fdt_entry(buf, entry);
283    }
284}
285
286/// Decode a complete BACnetLIST of `BACnetBDTEntry`. Every byte must belong
287/// to an element; an empty input is an empty list.
288pub fn decode_bdt_entry_list(data: &[u8]) -> Result<Vec<BACnetBDTEntry>, Error> {
289    decode_list(data, "BACnetBDTEntry list", decode_bdt_entry)
290}
291
292/// Decode a complete BACnetLIST of `BACnetFDTEntry`. Every byte must belong
293/// to an element; an empty input is an empty list.
294pub fn decode_fdt_entry_list(data: &[u8]) -> Result<Vec<BACnetFDTEntry>, Error> {
295    decode_list(data, "BACnetFDTEntry list", decode_fdt_entry)
296}
297
298fn decode_list<T>(
299    data: &[u8],
300    what: &'static str,
301    decode: impl Fn(&[u8], usize) -> Result<(T, usize), Error>,
302) -> Result<Vec<T>, Error> {
303    let mut values = Vec::new();
304    let mut offset = 0;
305    while offset < data.len() {
306        if values.len() == MAX_FRAMED_ITEMS {
307            return Err(DecodeError::new(
308                DecodingKind::InvalidEncoding,
309                offset,
310                "more than {} entries",
311            )
312            .within(what)
313            .with_value(MAX_FRAMED_ITEMS as u64)
314            .into());
315        }
316        let (value, end) = decode(data, offset)?;
317        values.push(value);
318        offset = end;
319    }
320    Ok(values)
321}