bacnet_npdu/codes.rs
1//! The wire codes of network-layer messages: [`NetworkMessageType`] and
2//! [`RejectMessageReason`].
3
4/// An open set of wire codes with named constants, the shape the codecs
5/// give `BvlcFunction` and `FrameType`: any raw value converts, and the
6/// names cover the values the standard defines. The codecs keep their own
7/// copies, since no crate here depends on another for it; CI checks that
8/// the copies match (`scripts/ci/check-named-codes.py`).
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_2021;
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
88named_codes! {
89 /// A network-layer message type: the octet after an NPDU's addresses
90 /// that says which message it carries (Clause 6.2.4).
91 ///
92 /// The set is open, so [`Npdu::decode`](crate::Npdu::decode) hands
93 /// on every octet as it arrived, and a router matches the constants with
94 /// a `_` arm for the rest. The constants name the types a router reads
95 /// and sends, grouped by what a router does with them and by name within
96 /// a group, and [`ALL_NAMED`](Self::ALL_NAMED) lists them that way. Of
97 /// the other octets, 0x80 and up are vendor proprietary
98 /// ([`is_proprietary`](Self::is_proprietary)), 0x0A to 0x11 are the
99 /// network-security types the standard no longer defines
100 /// ([`is_removed_security`](Self::is_removed_security)), and the rest
101 /// are reserved. Two message types are equal when their octets are.
102 ///
103 /// ```
104 /// use bacnet_npdu::NetworkMessageType;
105 ///
106 /// assert_eq!(NetworkMessageType::from_raw(0x13), NetworkMessageType::NETWORK_NUMBER_IS);
107 /// assert_eq!(NetworkMessageType::NETWORK_NUMBER_IS.name(), Some("NETWORK_NUMBER_IS"));
108 ///
109 /// // A proprietary type has no name, but keeps its octet.
110 /// let proprietary = NetworkMessageType::from_raw(0x80);
111 /// assert!(proprietary.is_proprietary() && !proprietary.is_defined());
112 /// assert_eq!(format!("{proprietary:?}"), "NetworkMessageType(128)");
113 /// ```
114 pub struct NetworkMessageType(u8);
115
116 // Routing discovery: finding the router that reaches a network, and
117 // the refusal a router sends when it can't route a message.
118 /// A router's list of what it reaches; this router learns from it.
119 const I_AM_ROUTER_TO_NETWORK = 0x01;
120 /// A route on offer; kept only where the table has none.
121 const I_COULD_BE_ROUTER_TO_NETWORK = 0x02;
122 /// A refusal with its reason, carried back to whoever sent the message.
123 const REJECT_MESSAGE_TO_NETWORK = 0x03;
124 /// A route query, answered here or searched for beyond.
125 const WHO_IS_ROUTER_TO_NETWORK = 0x00;
126
127 // Busy and available: flow control between routers.
128 /// Lifts a hold on traffic.
129 const ROUTER_AVAILABLE_TO_NETWORK = 0x05;
130 /// Puts a hold on traffic, which lapses on its own.
131 const ROUTER_BUSY_TO_NETWORK = 0x04;
132
133 // The routing table: updates, queries and their answers.
134 /// A table update, or a query with a count of 0.
135 const INITIALIZE_ROUTING_TABLE = 0x06;
136 /// What comes back for one.
137 const INITIALIZE_ROUTING_TABLE_ACK = 0x07;
138
139 // Connections over point-to-point links, which this stack doesn't open.
140 /// Counted and otherwise ignored here.
141 const DISCONNECT_CONNECTION_TO_NETWORK = 0x09;
142 /// Logged and otherwise ignored here.
143 const ESTABLISH_CONNECTION_TO_NETWORK = 0x08;
144
145 // The network number of the local network.
146 /// The number, and whether it was configured.
147 const NETWORK_NUMBER_IS = 0x13;
148 /// The question that number answers.
149 const WHAT_IS_NETWORK_NUMBER = 0x12;
150}
151
152impl NetworkMessageType {
153 /// Whether this is a vendor-proprietary type, 0x80 and up, which an NPDU
154 /// follows with a two-octet vendor ID.
155 #[must_use]
156 pub const fn is_proprietary(self) -> bool {
157 self.to_raw() >= 0x80
158 }
159
160 /// Whether this is one of the network-security types, 0x0A to 0x11,
161 /// that the standard no longer defines. A router doesn't reject them.
162 #[must_use]
163 pub const fn is_removed_security(self) -> bool {
164 matches!(self.to_raw(), 0x0A..=0x11)
165 }
166}
167
168named_codes! {
169 /// The reason a Reject-Message-To-Network gives (Clause 6.4.4).
170 ///
171 /// The set is open, so a reject read off the wire keeps whatever octet
172 /// it carried, and a `match` on the constants needs a `_` arm. The
173 /// constants are the reasons the standard defines: 5 has been
174 /// withdrawn, and 7 and up are unassigned. Two reasons are equal when
175 /// their octets are.
176 ///
177 /// ```
178 /// use bacnet_npdu::RejectMessageReason;
179 ///
180 /// assert_eq!(RejectMessageReason::from_raw(4), RejectMessageReason::MESSAGE_TOO_LONG);
181 /// assert_eq!(format!("{}", RejectMessageReason::ROUTER_BUSY), "ROUTER_BUSY");
182 /// assert_eq!(format!("{:?}", RejectMessageReason::from_raw(5)), "RejectMessageReason(5)");
183 /// ```
184 pub struct RejectMessageReason(u8);
185
186 /// Some other error.
187 const OTHER = 0;
188 /// No route to the network, after looking for one.
189 const NOT_DIRECTLY_CONNECTED = 1;
190 /// The router is busy for that network for now.
191 const ROUTER_BUSY = 2;
192 /// A message type the router doesn't know.
193 const UNKNOWN_MESSAGE_TYPE = 3;
194 /// Too long to route to that network.
195 const MESSAGE_TOO_LONG = 4;
196 /// A DLEN or SLEN no data link uses.
197 const ADDRESSING_ERROR = 6;
198}