bacnet_router_core/router/host.rs
1//! What the router asks of its caller during a call: the [`Host`] hooks.
2
3use crate::PortId;
4use crate::message::{Networks, RoutingEntries};
5use crate::npdu::{NetworkMessageType, RejectMessageReason};
6use crate::table::RejectEffect;
7
8/// The caller's side of a [`receive`](super::Router::receive) or
9/// [`on_deadline`](super::Router::on_deadline) call.
10///
11/// Every method has a default, so `impl Host for MyHost {}` gives a router
12/// that admits every protected message and
13/// reports nothing: what [`Permissive`] is.
14///
15/// The router calls these with its state borrowed, and a hosted router
16/// calls them under the lock it keeps the router in: an implementation
17/// must not block, and must never call into the router or take that lock.
18pub trait Host {
19 /// Whether to act on a protected network message (RB-09). The router
20 /// asks after the message's data has been checked and before anything
21 /// changes. `false` drops it silently: no table change, relay, answer
22 /// or reject.
23 fn authorize(&mut self, claim: &Claim<'_>) -> bool {
24 let _ = claim;
25 true
26 }
27
28 /// Something the hosted router logs today happened.
29 fn observe(&mut self, event: Event) {
30 let _ = event;
31 }
32}
33
34/// A [`Host`] with every default: every protected message admitted,
35/// nothing observed. Port group rules still apply. For firmware and tests.
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub struct Permissive;
38
39impl Host for Permissive {}
40
41/// The protected classes of network message, one per kind of change a
42/// message can make to the routing table.
43///
44/// It is `#[non_exhaustive]`, so a new class isn't a breaking change: a
45/// `match` needs a wildcard arm.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47#[non_exhaustive]
48pub enum ControlClass {
49 /// I-Am-Router learning.
50 IAm,
51 /// Initialize-Routing-Table-Ack learning.
52 InitAck,
53 /// Initialize-Routing-Table updates and removals (not queries).
54 InitMgmt,
55 /// Router-Busy marks.
56 Busy,
57 /// Router-Available marks.
58 Available,
59 /// Changes a received reject makes.
60 Reject,
61 /// Routes stored from I-Could-Be-Router.
62 ICouldBe,
63}
64
65/// The networks a protected message names.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum Targets<'c> {
68 /// Networks by number, as I-Am-Router, Router-Busy and
69 /// Router-Available carry them, or the one network a reject or an
70 /// I-Could-Be names.
71 Networks(Networks<'c>),
72 /// The routing entries an Initialize-Routing-Table, or an Ack to one,
73 /// carries.
74 Entries(RoutingEntries<'c>),
75}
76
77impl<'c> Targets<'c> {
78 /// The networks, in the order the message lists them.
79 pub fn iter(&self) -> impl Iterator<Item = u16> + 'c + use<'c> {
80 let (list, entries) = match *self {
81 Self::Networks(list) => (Some(list.iter()), None),
82 Self::Entries(entries) => (None, Some(entries.iter().map(|entry| entry.network))),
83 };
84 list.into_iter()
85 .flatten()
86 .chain(entries.into_iter().flatten())
87 }
88
89 /// Whether it names none.
90 #[must_use]
91 pub fn is_empty(&self) -> bool {
92 self.iter().next().is_none()
93 }
94}
95
96/// What [`Host::authorize`] is told about a protected message. It carries
97/// what the message claims, none of it verified.
98///
99/// The router builds it. It is `#[non_exhaustive]`, so a new field isn't a
100/// breaking change: a pattern needs `..`.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102#[non_exhaustive]
103pub struct Claim<'c> {
104 /// The class of change it would make.
105 pub class: ControlClass,
106 /// The port it arrived on.
107 pub port: PortId,
108 /// That port's network number.
109 pub port_network: u16,
110 /// The link-layer address of the node that sent it.
111 pub source: &'c [u8],
112 /// Its message type.
113 pub message_type: NetworkMessageType,
114 /// Its vendor ID, for a proprietary type.
115 pub vendor_id: Option<u16>,
116 /// The SNET it claims, if any.
117 pub snet: Option<u16>,
118 /// The DNET it names, if any.
119 pub dnet: Option<u16>,
120 /// The networks it names.
121 pub targets: Targets<'c>,
122 /// A Router-Busy or Router-Available that lists no network: it covers
123 /// every network the sender serves.
124 pub targets_omitted: bool,
125}
126
127/// What [`Host::observe`] hears about: what the hosted router logs today
128/// that the call's result and actions don't show.
129///
130/// It is `#[non_exhaustive]`, so a new event isn't a breaking change: a
131/// `match` needs a wildcard arm.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[non_exhaustive]
134pub enum Event {
135 /// A learning claim installed or refreshed a route.
136 Learned {
137 /// The network.
138 network: u16,
139 /// The port it is reached through.
140 port: PortId,
141 /// I-Am-Router or Initialize-Routing-Table-Ack.
142 class: ControlClass,
143 },
144 /// A route moved port often enough to count as flapping.
145 Flapping {
146 /// The network.
147 network: u16,
148 /// Moves within 60 s of the one before, this one included.
149 flaps: u8,
150 },
151 /// The route cap ended a message's learning or update, or turned an
152 /// I-Could-Be-Router offer away.
153 RouteCap {
154 /// The class of the message.
155 class: ControlClass,
156 },
157 /// A reject arrived and was applied to the table.
158 RejectReceived {
159 /// The network it names.
160 network: u16,
161 /// Its reason.
162 reason: RejectMessageReason,
163 /// What it did to the route.
164 effect: RejectEffect,
165 },
166 /// Router-Busy or Router-Available covered a network.
167 Marked {
168 /// The network.
169 network: u16,
170 /// Busy, or available.
171 busy: bool,
172 },
173 /// An Initialize-Routing-Table entry installed a route.
174 UpdateApplied {
175 /// The network.
176 network: u16,
177 /// The port it now goes through.
178 port: PortId,
179 },
180 /// An Initialize-Routing-Table entry with Port ID 0 removed a route.
181 UpdateRemoved {
182 /// The network.
183 network: u16,
184 },
185 /// An Initialize-Routing-Table entry named no port of this router, and
186 /// was skipped.
187 UnknownPortId {
188 /// The network.
189 network: u16,
190 /// The Port ID it named.
191 port_id: u8,
192 },
193 /// An I-Could-Be-Router arrived.
194 ICouldBe {
195 /// The network on offer.
196 network: u16,
197 /// The sender's rating of the connection.
198 performance_index: u8,
199 /// Whether it was stored: only where the table had no route, and
200 /// was under the route cap.
201 stored: bool,
202 },
203 /// An Establish-Connection arrived; this router opens no connections.
204 EstablishConnection {
205 /// The network.
206 network: u16,
207 /// The termination time it asked for.
208 termination_time: u8,
209 },
210 /// A Disconnect-Connection arrived; with no connections, nothing is
211 /// removed.
212 DisconnectIgnored {
213 /// The network.
214 network: u16,
215 },
216 /// A message of the removed security types arrived; it is not
217 /// rejected.
218 SecurityMessage {
219 /// Its type.
220 message_type: NetworkMessageType,
221 },
222 /// A message of a type this router doesn't handle arrived for it; it
223 /// draws reject reason 3.
224 UnknownMessage {
225 /// Its type.
226 message_type: NetworkMessageType,
227 },
228 /// A Network-Number-Is reported a number other than the port's.
229 NumberConflict {
230 /// The port it arrived on.
231 port: PortId,
232 /// The port's configured number.
233 local: u16,
234 /// The number reported.
235 reported: u16,
236 /// Whether the sender's number was configured rather than learned.
237 configured: bool,
238 },
239 /// A relay went nowhere because its hop count had run out. It comes
240 /// for every such relay, including one whose frame was also delivered
241 /// locally (a global broadcast, or a DLEN 0 broadcast on the arrival
242 /// network), which [`Received`](super::Received) doesn't show.
243 HopCountSpent {
244 /// The network the relay was for: 0xFFFF for a global broadcast.
245 network: u16,
246 },
247 /// An NPDU too long for an egress port's limit for its kind of send
248 /// (fix R1). One routed toward a network drew reject reason 4 naming
249 /// it; a fan-out leaves that port out when it is polled, where the skip
250 /// is counted. Both count in
251 /// [`Counters::egress_too_long`](super::Counters::egress_too_long).
252 TooLong {
253 /// The egress port.
254 port: PortId,
255 /// The NPDU's length, as rewritten for that port.
256 octets: usize,
257 /// The port's limit for that kind of send.
258 limit: usize,
259 /// The network the reject named; `None` for a fan-out.
260 rejected: Option<u16>,
261 },
262 /// A refused NPDU named this router as its origin, so no reject went.
263 OwnOrigin {
264 /// The network the reject would have named.
265 network: u16,
266 },
267 /// The sweep removed a learned route not confirmed for too long.
268 Purged {
269 /// The network.
270 network: u16,
271 },
272 /// The sweep dropped a hardened-mode claim that waited too long.
273 PendingExpired {
274 /// The network.
275 network: u16,
276 },
277 /// The sweep forgot a search for an unknown network.
278 DiscoveryExpired {
279 /// The network.
280 network: u16,
281 },
282}