bacnet_bip_codec/function.rs
1//! The BVLC function and result codes, and the payload layout each function
2//! has.
3
4use crate::{BDT_ENTRY_LENGTH, BIP_ADDRESS_LENGTH, FDT_ENTRY_LENGTH};
5
6/// An open set of wire codes with named constants, the shape `bacnet-types`
7/// gives its enumerations: any raw value converts, and the names cover the
8/// values the standard defines.
9macro_rules! named_codes {
10 (
11 $(#[$meta:meta])*
12 pub struct $Name:ident($inner:ty);
13 $(
14 $(#[$vmeta:meta])*
15 const $VARIANT:ident = $val:expr;
16 )*
17 ) => {
18 $(#[$meta])*
19 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
20 pub struct $Name($inner);
21
22 impl $Name {
23 $(
24 $(#[$vmeta])*
25 #[doc = ""]
26 #[doc = concat!("Wire value `", stringify!($val), "`.")]
27 pub const $VARIANT: Self = Self($val);
28 )*
29
30 /// Every named constant, with its name, in the order declared.
31 pub const ALL_NAMED: &[(&str, Self)] = &[
32 $( (stringify!($VARIANT), Self($val)), )*
33 ];
34
35 /// Wrap a raw wire value, named or not.
36 #[inline]
37 #[must_use]
38 pub const fn from_raw(value: $inner) -> Self {
39 Self(value)
40 }
41
42 /// The raw wire value.
43 #[inline]
44 #[must_use]
45 pub const fn to_raw(self) -> $inner {
46 self.0
47 }
48
49 /// The constant's name, or `None` for a value the standard
50 /// doesn't define.
51 #[must_use]
52 pub const fn name(self) -> Option<&'static str> {
53 match self.0 {
54 $( $val => Some(stringify!($VARIANT)), )*
55 _ => None,
56 }
57 }
58
59 /// Whether the standard defines this value.
60 #[must_use]
61 pub const fn is_defined(self) -> bool {
62 self.name().is_some()
63 }
64 }
65
66 /// `Name::CONSTANT` for a named value, `Name(raw)` otherwise.
67 impl core::fmt::Debug for $Name {
68 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69 match self.name() {
70 Some(name) => write!(f, "{}::{}", stringify!($Name), name),
71 None => write!(f, "{}({})", stringify!($Name), self.0),
72 }
73 }
74 }
75
76 /// The constant's name, or the raw value in decimal.
77 impl core::fmt::Display for $Name {
78 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79 match self.name() {
80 Some(name) => f.write_str(name),
81 None => write!(f, "{}", self.0),
82 }
83 }
84 }
85 };
86}
87
88pub(crate) use named_codes;
89
90named_codes! {
91 /// A BVLC function code: the second octet of every B/IP BVLL message,
92 /// which says what the message is (Annex J.2).
93 ///
94 /// The set is open, so a decoder can report a code it doesn't know. The
95 /// constants are the twelve functions Annex J.2.1-J.2.12 define. Code
96 /// 0x0C is not among them: the clause that defined it (J.2.13) was
97 /// deleted, so this edition treats it like any other unknown code.
98 pub struct BvlcFunction(u8);
99
100 /// Acknowledges a BBMD management request (J.2.1).
101 const BVLC_RESULT = 0x00;
102 /// Writes a BBMD's broadcast distribution table (J.2.2).
103 const WRITE_BROADCAST_DISTRIBUTION_TABLE = 0x01;
104 /// Asks a BBMD for its broadcast distribution table (J.2.3).
105 const READ_BROADCAST_DISTRIBUTION_TABLE = 0x02;
106 /// Returns a broadcast distribution table (J.2.4).
107 const READ_BROADCAST_DISTRIBUTION_TABLE_ACK = 0x03;
108 /// An NPDU a BBMD passes on, with its originator's address (J.2.5).
109 const FORWARDED_NPDU = 0x04;
110 /// Asks a BBMD to take the sender as a foreign device (J.2.6).
111 const REGISTER_FOREIGN_DEVICE = 0x05;
112 /// Asks a BBMD for its foreign device table (J.2.7).
113 const READ_FOREIGN_DEVICE_TABLE = 0x06;
114 /// Returns a foreign device table (J.2.8).
115 const READ_FOREIGN_DEVICE_TABLE_ACK = 0x07;
116 /// Removes one entry from a BBMD's foreign device table (J.2.9).
117 const DELETE_FOREIGN_DEVICE_TABLE_ENTRY = 0x08;
118 /// A foreign device's broadcast, for its BBMD to distribute (J.2.10).
119 const DISTRIBUTE_BROADCAST_TO_NETWORK = 0x09;
120 /// A directed NPDU (J.2.11).
121 const ORIGINAL_UNICAST_NPDU = 0x0A;
122 /// A local broadcast NPDU (J.2.12).
123 const ORIGINAL_BROADCAST_NPDU = 0x0B;
124}
125
126named_codes! {
127 /// The result code a BVLC-Result carries (Annex J.2.1.1): success, or a
128 /// NAK naming the request that failed.
129 pub struct BvlcResultCode(u16);
130
131 /// The request succeeded.
132 const SUCCESSFUL_COMPLETION = 0x0000;
133 /// Write-Broadcast-Distribution-Table failed or is not supported.
134 const WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0010;
135 /// Read-Broadcast-Distribution-Table failed or is not supported.
136 const READ_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0020;
137 /// Register-Foreign-Device failed or is not supported.
138 const REGISTER_FOREIGN_DEVICE_NAK = 0x0030;
139 /// Read-Foreign-Device-Table failed or is not supported.
140 const READ_FOREIGN_DEVICE_TABLE_NAK = 0x0040;
141 /// Delete-Foreign-Device-Table-Entry failed or is not supported.
142 const DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK = 0x0050;
143 /// Distribute-Broadcast-To-Network failed or is not supported.
144 const DISTRIBUTE_BROADCAST_TO_NETWORK_NAK = 0x0060;
145}
146
147/// How long the payload of a BVLL message may be: the octets after the
148/// 4-octet header, which depend on the function alone (Annex J.2, U.2).
149///
150/// [`BvlcFunction::payload_shape`] gives it for B/IP and
151/// [`Bvlc6Function::payload_shape`](crate::ipv6::Bvlc6Function::payload_shape)
152/// for B/IPv6.
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum PayloadShape {
155 /// Exactly this many octets.
156 Fixed(usize),
157 /// A table of whole entries of this many octets each. An empty table is
158 /// allowed. Only B/IP messages carry tables.
159 Table {
160 /// Octets per entry.
161 entry_length: usize,
162 },
163 /// This many octets of fixed fields, then an NPDU of any length. The
164 /// codec does not look inside the NPDU; the network layer does.
165 Npdu {
166 /// Octets before the NPDU.
167 prefix: usize,
168 },
169}
170
171impl PayloadShape {
172 /// Whether a payload of `length` octets fits this shape.
173 #[must_use]
174 pub const fn admits(self, length: usize) -> bool {
175 match self {
176 Self::Fixed(fixed) => length == fixed,
177 Self::Table { entry_length } => length.is_multiple_of(entry_length),
178 Self::Npdu { prefix } => length >= prefix,
179 }
180 }
181}
182
183impl BvlcFunction {
184 /// The payload layout of this function, from its format clause in
185 /// Annex J.2, or `None` for a code Annex J doesn't define.
186 ///
187 /// - BVLC-Result and Register-Foreign-Device: two octets, a result code or
188 /// a time-to-live (J.2.1.1, J.2.6.1).
189 /// - The two table reads: nothing (J.2.3.1, J.2.7.1).
190 /// - Delete-Foreign-Device-Table-Entry: the entry's address, six octets
191 /// (J.2.9.1).
192 /// - Write-Broadcast-Distribution-Table and both table ACKs: whole
193 /// 10-octet entries (J.2.2.1, J.2.4.1, J.2.8.1).
194 /// - Forwarded-NPDU: the originator's 6-octet address, then the NPDU
195 /// (J.2.5.1).
196 /// - Distribute-Broadcast-To-Network and both Original messages: just the
197 /// NPDU (J.2.10.1-J.2.12.1).
198 ///
199 /// ```
200 /// use bacnet_bip_codec::{BvlcFunction, PayloadShape};
201 ///
202 /// assert_eq!(
203 /// BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY.payload_shape(),
204 /// Some(PayloadShape::Fixed(6)),
205 /// );
206 /// assert_eq!(BvlcFunction::from_raw(0x0C).payload_shape(), None);
207 /// ```
208 #[must_use]
209 pub const fn payload_shape(self) -> Option<PayloadShape> {
210 Some(match self {
211 Self::BVLC_RESULT | Self::REGISTER_FOREIGN_DEVICE => PayloadShape::Fixed(2),
212 Self::READ_BROADCAST_DISTRIBUTION_TABLE | Self::READ_FOREIGN_DEVICE_TABLE => {
213 PayloadShape::Fixed(0)
214 }
215 Self::DELETE_FOREIGN_DEVICE_TABLE_ENTRY => PayloadShape::Fixed(BIP_ADDRESS_LENGTH),
216 Self::WRITE_BROADCAST_DISTRIBUTION_TABLE
217 | Self::READ_BROADCAST_DISTRIBUTION_TABLE_ACK => PayloadShape::Table {
218 entry_length: BDT_ENTRY_LENGTH,
219 },
220 Self::READ_FOREIGN_DEVICE_TABLE_ACK => PayloadShape::Table {
221 entry_length: FDT_ENTRY_LENGTH,
222 },
223 Self::FORWARDED_NPDU => PayloadShape::Npdu {
224 prefix: BIP_ADDRESS_LENGTH,
225 },
226 Self::DISTRIBUTE_BROADCAST_TO_NETWORK
227 | Self::ORIGINAL_UNICAST_NPDU
228 | Self::ORIGINAL_BROADCAST_NPDU => PayloadShape::Npdu { prefix: 0 },
229 _ => return None,
230 })
231 }
232
233 /// The NAK that answers this function when it fails or isn't supported,
234 /// for the six requests J.2.1 says only a BBMD carries out (the BDT
235 /// write and read, the FDT read, registration, entry deletion and
236 /// broadcast distribution). `None` for every other code.
237 ///
238 /// A device that is not a BBMD answers a unicast request of these kinds
239 /// with this code (J.2.1), and every device answers
240 /// Write-Broadcast-Distribution-Table with it (J.4.4.2).
241 #[must_use]
242 pub const fn nak(self) -> Option<BvlcResultCode> {
243 Some(match self {
244 Self::WRITE_BROADCAST_DISTRIBUTION_TABLE => {
245 BvlcResultCode::WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK
246 }
247 Self::READ_BROADCAST_DISTRIBUTION_TABLE => {
248 BvlcResultCode::READ_BROADCAST_DISTRIBUTION_TABLE_NAK
249 }
250 Self::REGISTER_FOREIGN_DEVICE => BvlcResultCode::REGISTER_FOREIGN_DEVICE_NAK,
251 Self::READ_FOREIGN_DEVICE_TABLE => BvlcResultCode::READ_FOREIGN_DEVICE_TABLE_NAK,
252 Self::DELETE_FOREIGN_DEVICE_TABLE_ENTRY => {
253 BvlcResultCode::DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK
254 }
255 Self::DISTRIBUTE_BROADCAST_TO_NETWORK => {
256 BvlcResultCode::DISTRIBUTE_BROADCAST_TO_NETWORK_NAK
257 }
258 _ => return None,
259 })
260 }
261}