bacnet_network/router/options.rs
1//! What [`BACnetRouter::start`] sets up, and what it hands back (#1220).
2//!
3//! A router can be started with a tracked local APDU receiver, a wire-control
4//! policy and authorizer, and its own network-control receiver, raw or tracked
5//! (#1242), in any combination. [`RouterOptions`] picks them and
6//! [`StartedRouter`] carries the receivers that were asked for.
7
8use std::fmt;
9use std::marker::PhantomData;
10
11use tokio::sync::mpsc;
12
13use bacnet_router_core::router::{Congestion, Duplicates, Options};
14
15use super::BACnetRouter;
16use super::control_policy::{ControlAuthorizer, ControlGate, ControlPolicy};
17use super::engine::OPTIONS;
18use crate::layer::{
19 AdmissionReceiver, QueueAdmissionCounters, ReceivedApdu, ReceivedNetworkControl,
20};
21
22/// How [`BACnetRouter::start`] sets up a router.
23///
24/// [`Self::new`] gives the plain router: a raw local APDU receiver, the
25/// permissive wire-control policy with no authorizer, and no network-control
26/// receiver. Each builder method turns on one option, and they combine freely.
27///
28/// `A` is the local APDU receiver type the start returns, a raw
29/// [`mpsc::Receiver`] unless [`Self::track_admission`] switches it to an
30/// [`AdmissionReceiver`]. `C` is the network-control receiver type in the same
31/// way: raw, unless [`Self::network_control_receiver_with_admission`] asks for
32/// the tracked one.
33///
34/// ```no_run
35/// # use bacnet_network::router::{BACnetRouter, RouterOptions, RouterPort, StartedRouter};
36/// # use bacnet_network::router::control_policy::ControlPolicy;
37/// # use bacnet_transport::loopback::LoopbackTransport;
38/// # async fn run(ports: Vec<RouterPort<LoopbackTransport>>) -> Result<(), bacnet_types::error::Error> {
39/// let options = RouterOptions::new()
40/// .track_admission()
41/// .control_policy(ControlPolicy::Hardened)
42/// .network_control_receiver();
43/// let StartedRouter { router, apdus, network_control } =
44/// BACnetRouter::start(ports, options).await?;
45/// let counters = apdus.counters();
46/// # Ok(())
47/// # }
48/// ```
49pub struct RouterOptions<
50 A = mpsc::Receiver<ReceivedApdu>,
51 C = mpsc::Receiver<ReceivedNetworkControl>,
52> {
53 policy: ControlPolicy,
54 authorizer: Option<ControlAuthorizer>,
55 network_control: bool,
56 engine: Options,
57 receivers: PhantomData<fn() -> (A, C)>,
58}
59
60impl RouterOptions {
61 /// The plain router, with every option off.
62 ///
63 /// Its local APDU receiver is a raw [`mpsc::Receiver`]. All ports share
64 /// one 256-item local queue. Full/Closed admission drops the arriving APDU
65 /// with its payload, metadata and reply sender without sending reply bytes
66 /// or a wire rejection, never evicting an older item. Admission never
67 /// waits for the consumer or stops forwarding/inline control handling.
68 /// This raw receiver has no per-source quota, admission snapshot or
69 /// depth/high-water tracking. Full/Closed drops are counted internally,
70 /// with Closed > Full precedence.
71 ///
72 /// Closing retains queued items; dropping discards them. Both leave
73 /// routing active, and each later local admission attempt counts as
74 /// Closed. [`BACnetRouter::stop`] also leaves queued items drainable. Use
75 /// [`Self::track_admission`] for snapshots and per-source fairness; see the
76 /// [receive-queue contract](crate::layer#receive-queue-admission) for the
77 /// raw/tracked matrix and ownership/lifecycle details.
78 pub fn new() -> Self {
79 Self {
80 policy: ControlPolicy::default(),
81 authorizer: None,
82 network_control: false,
83 engine: OPTIONS,
84 receivers: PhantomData,
85 }
86 }
87}
88
89impl Default for RouterOptions {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl<A, C> RouterOptions<A, C> {
96 /// Return the local APDU receiver as an [`AdmissionReceiver`], which
97 /// exposes queue-admission snapshots and enforces a per-source quota.
98 ///
99 /// Routing and the router's lifecycle are unchanged. All ports share one
100 /// 256-item queue and one accounting state; [`AdmissionReceiver::counters`]
101 /// returns cloneable count-only handles for depth/high-water and
102 /// admission-drop totals, readable after receiver or router drop. Failed
103 /// admission drops the arriving APDU, never an older one, and releases its
104 /// payload, metadata and reply sender without reply bytes or wire
105 /// rejections. Forwarding and inline control handling remain independent.
106 /// Closing the receiver retains queued items for draining and counts each
107 /// subsequent local arrival as a closed drop while forwarding continues.
108 /// [`BACnetRouter::stop`] also leaves queued items drainable. Dropping the
109 /// receiver discards queued items and releases their reply channels
110 /// without sending reply bytes; discarding queued items is not an
111 /// admission drop.
112 ///
113 /// Each ([ingress port network number](ReceivedApdu::ingress_network),
114 /// complete [`source_mac`](ReceivedApdu::source_mac) byte value) may hold
115 /// at most 16 queued local APDUs, never keyed by routed NPDU SNET/SADR.
116 /// Identical MAC bytes on different ports have separate quotas.
117 /// Dequeuing releases one slot, even if the consumer retains the APDU.
118 /// **Closed > fairness > Full** selects one drop reason; over-quota
119 /// arrivals increment
120 /// [`fairness_drops`](crate::layer::QueueAdmissionSnapshot::fairness_drops)
121 /// even if the queue is also globally full, unless admission is closed.
122 /// The raw receiver from [`RouterOptions::new`] does not enforce a quota.
123 /// See the [receive-queue contract](crate::layer#receive-queue-admission)
124 /// for exact keys, the raw/tracked matrix and complete ownership/lifecycle
125 /// rules.
126 pub fn track_admission(self) -> RouterOptions<AdmissionReceiver<ReceivedApdu>, C> {
127 self.receivers()
128 }
129
130 /// RB-09: the wire-control policy for state-changing routing controls.
131 ///
132 /// [`ControlPolicy::Permissive`], the default, admits them.
133 /// [`ControlPolicy::Hardened`] denies a protected control unless an
134 /// authorizer approves it, as a silent drop. Read the totals with
135 /// [`BACnetRouter::control_snapshot`].
136 pub fn control_policy(mut self, policy: ControlPolicy) -> Self {
137 self.policy = policy;
138 self
139 }
140
141 /// RB-09: a callback that decides every protected wire control, whatever
142 /// the [policy](Self::control_policy). See [`ControlAuthorizer`] for what
143 /// it may and may not do.
144 pub fn control_authorizer(mut self, authorizer: ControlAuthorizer) -> Self {
145 self.authorizer = Some(authorizer);
146 self
147 }
148
149 /// Also return the router's own network-control receiver (#1175), in
150 /// [`StartedRouter::network_control`].
151 ///
152 /// The receiver gets each Reject-Message-To-Network addressed to the
153 /// router itself: one with no DNET, or whose DADR is the router's MAC on
154 /// the port attached to that DNET. Such a reject still updates the routing
155 /// table and is never relayed. Without this receiver, the table update is
156 /// all that happens. Every other network message is still handled inline.
157 ///
158 /// Records match what a non-router
159 /// [`NetworkLayer::enable_network_control_receiver`](crate::layer::NetworkLayer::enable_network_control_receiver)
160 /// delivers, numbered from [`BACnetRouter::network_control_ingress_sequence`].
161 /// The queue holds 256 controls, apart from the APDU queue. Admission never
162 /// waits: a full or closed receiver drops the arriving control, and
163 /// routing carries on. This receiver is a raw [`mpsc::Receiver`], which
164 /// counts its drops internally only; use
165 /// [`Self::network_control_receiver_with_admission`] to read them. The two
166 /// are alternatives, and the later call decides the receiver type.
167 pub fn network_control_receiver(
168 mut self,
169 ) -> RouterOptions<A, mpsc::Receiver<ReceivedNetworkControl>> {
170 self.network_control = true;
171 self.receivers()
172 }
173
174 /// [`Self::network_control_receiver`], with the receiver as an
175 /// [`AdmissionReceiver`] that reports on its queue (#1242), as
176 /// [`NetworkLayer::enable_network_control_receiver_with_admission`](crate::layer::NetworkLayer::enable_network_control_receiver_with_admission)
177 /// does for a non-router.
178 ///
179 /// [`AdmissionReceiver::counters`] reads the queue's depth, its high-water
180 /// mark and its full and closed drop totals. Controls have no per-source
181 /// quota, so the fairness total stays zero. The records, the 256-control
182 /// queue and the drop-arriving admission are those of the raw receiver;
183 /// see the [receive-queue contract](crate::layer#receive-queue-admission)
184 /// for what closing, dropping and stopping do to the counts.
185 pub fn network_control_receiver_with_admission(
186 mut self,
187 ) -> RouterOptions<A, AdmissionReceiver<ReceivedNetworkControl>> {
188 self.network_control = true;
189 self.receivers()
190 }
191
192 /// The router's own Router-Busy for a clogged port (#1697): off unless
193 /// this sets a policy.
194 ///
195 /// Each port queues at most 256 sends. With a policy set, a port whose
196 /// queue stays full for [`Congestion::clog_after`] counts as clogged:
197 /// the router broadcasts Router-Busy-To-Network on its other ports for
198 /// the networks it reaches through that one, answers each send the full
199 /// queue then refuses, when it has an origin to answer, with
200 /// Reject-Message-To-Network reason 2, and broadcasts
201 /// Router-Available-To-Network once the queue falls under the policy's
202 /// low-water mark (Clauses 6.6.3.6, 6.6.3.7, 6.6.4).
203 /// [`Congestion::DEFAULT`] clogs after 2 s and drains under half.
204 /// The counts are in [`Counters`](super::Counters).
205 pub fn congestion(mut self, policy: Congestion) -> Self {
206 self.engine.congestion = Some(policy);
207 self
208 }
209
210 /// Apply a received Reject-Message-To-Network to a learned route only
211 /// when it came from that route's next router (#1698).
212 ///
213 /// Off by default: Clause 6.6.3.5 has a router update the route a
214 /// reject names whoever sent it, so a reject relayed by any router
215 /// changes it. On, one from another router is still passed on toward
216 /// its destination, but leaves the route as it was, and is counted in
217 /// [`Counters::not_next_hop`](super::Counters::not_next_hop).
218 /// Router-Busy and Router-Available are always scoped to the routes
219 /// through their sender.
220 pub fn next_hop_rejects(mut self) -> Self {
221 self.engine.next_hop_rejects = true;
222 self
223 }
224
225 /// Drop a global broadcast that repeats one seen within
226 /// [`Duplicates::ttl`] (#1698): off unless this sets it.
227 ///
228 /// In a topology with a loop, routers can pass one global broadcast
229 /// around it until its hop count runs out. With this on, the router
230 /// remembers the last 512 global broadcasts by origin (SNET and SADR,
231 /// or the arrival network and link source) and a hash of the APDU,
232 /// and drops a repeat seen within the time to live, 1 s for
233 /// [`Duplicates::DEFAULT`]: not relayed, not delivered, counted in
234 /// [`Counters::duplicate_drops`](super::Counters::duplicate_drops).
235 /// The standard defines no such suppression, and a sender's own
236 /// identical retry within the time to live is dropped too.
237 ///
238 /// The same record covers the network messages the router passes on
239 /// with no origin, I-Am-Router, Router-Busy and Router-Available
240 /// (#1810), which carry no hop count, so around a loop nothing else
241 /// stops them. A repeat within the time to live is neither applied to
242 /// the table nor passed on. An I-Am-Router is kept by its data alone,
243 /// so two routers announcing the same networks within one time to live
244 /// look like one, and the second is dropped until it announces again.
245 /// A Router-Busy or Router-Available is kept by its sender (arrival
246 /// network and link source) and data, so another router's identical
247 /// message is still applied, and accepting one forgets its opposite
248 /// from the same sender, so Busy, Available, Busy within one time to
249 /// live applies all three; at most four such resets per sender within
250 /// one time to live, so a pair looping round a ring still ends. A
251 /// Who-Is-Router the router passes on for a network it doesn't know
252 /// (#2020) is kept, like a global broadcast, by the origin its SNET and
253 /// SADR name, and its data.
254 ///
255 /// Without this option too, a global broadcast or Who-Is-Router whose
256 /// SNET and SADR name the router itself is dropped, as its own come
257 /// back round a loop, and counted in
258 /// [`Counters::own_source_drops`](super::Counters::own_source_drops)
259 /// (#2021).
260 ///
261 /// Entries are keyed by origin, so in a looped site this router's own
262 /// global broadcast sent from each port's own address (a bare copy per
263 /// port) is a different origin per port to the other routers, and may
264 /// reach their applications once per port. Send it from one port's
265 /// address there
266 /// ([`SourceSelection::Port`](super::SourceSelection::Port)), so every
267 /// copy names one origin.
268 ///
269 /// Sizing: the record must hold every global broadcast the router sees
270 /// within one time to live, or the oldest is forgotten before its
271 /// looped copy returns. 512 covers 512 broadcasts a second at the
272 /// default; a longer time to live needs proportionally fewer a second.
273 /// The relayed Who-Is-Router, I-Am-Router, Router-Busy and
274 /// Router-Available entries share those 512 slots with the global
275 /// broadcasts, the flow-control ones one per sender and message.
276 pub fn duplicates(mut self, policy: Duplicates) -> Self {
277 self.engine.duplicates = Some(policy);
278 self
279 }
280
281 /// The engine's options these select.
282 pub(super) fn engine_options(&self) -> Options {
283 self.engine
284 }
285
286 /// The wire-control gate these options select.
287 pub(super) fn control_gate(&self) -> ControlGate {
288 ControlGate::new(self.policy, self.authorizer.clone())
289 }
290
291 /// Whether a network-control receiver was asked for.
292 pub(super) fn wants_network_control(&self) -> bool {
293 self.network_control
294 }
295
296 /// These options, returning other receiver types.
297 fn receivers<A2, C2>(self) -> RouterOptions<A2, C2> {
298 RouterOptions {
299 policy: self.policy,
300 authorizer: self.authorizer,
301 network_control: self.network_control,
302 engine: self.engine,
303 receivers: PhantomData,
304 }
305 }
306}
307
308impl<A, C> fmt::Debug for RouterOptions<A, C> {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 f.debug_struct("RouterOptions")
311 .field("policy", &self.policy)
312 .field(
313 "authorizer",
314 &self.authorizer.as_ref().map(|_| "<callback>"),
315 )
316 .field("network_control", &self.network_control)
317 .field("engine", &self.engine)
318 .field("apdus", &std::any::type_name::<A>())
319 .field("controls", &std::any::type_name::<C>())
320 .finish()
321 }
322}
323
324/// A running router and the receivers its [`RouterOptions`] asked for.
325pub struct StartedRouter<
326 A = mpsc::Receiver<ReceivedApdu>,
327 C = mpsc::Receiver<ReceivedNetworkControl>,
328> {
329 /// The router.
330 pub router: BACnetRouter,
331 /// APDUs for local applications: those without a remote destination, or
332 /// for which this router is the final hop.
333 pub apdus: A,
334 /// The router's own network-control receiver, when
335 /// [`RouterOptions::network_control_receiver`] or
336 /// [`RouterOptions::network_control_receiver_with_admission`] asked for it.
337 pub network_control: Option<C>,
338}
339
340/// A local APDU receiver type [`BACnetRouter::start`] can return: the raw
341/// [`mpsc::Receiver`] or, after [`RouterOptions::track_admission`], an
342/// [`AdmissionReceiver`]. Sealed.
343pub trait LocalApduReceiver: sealed::Sealed<ReceivedApdu> + sealed::ApduQueue {}
344
345impl LocalApduReceiver for mpsc::Receiver<ReceivedApdu> {}
346impl LocalApduReceiver for AdmissionReceiver<ReceivedApdu> {}
347
348/// A network-control receiver type [`BACnetRouter::start`] can return: the
349/// raw [`mpsc::Receiver`] from [`RouterOptions::network_control_receiver`] or
350/// the [`AdmissionReceiver`] from
351/// [`RouterOptions::network_control_receiver_with_admission`]. Sealed.
352pub trait NetworkControlReceiver: sealed::Sealed<ReceivedNetworkControl> {}
353
354impl NetworkControlReceiver for mpsc::Receiver<ReceivedNetworkControl> {}
355impl NetworkControlReceiver for AdmissionReceiver<ReceivedNetworkControl> {}
356
357pub(super) mod sealed {
358 use super::*;
359
360 /// How the start builds each receiver type from its queue of `T`.
361 pub trait Sealed<T>: Sized {
362 /// Whether the queue keeps depth accounting (and, for APDUs,
363 /// per-source accounting).
364 const TRACKED: bool;
365
366 /// Wrap the queue's receiving end.
367 fn from_queue(rx: mpsc::Receiver<T>, counters: QueueAdmissionCounters) -> Self;
368 }
369
370 impl<T> Sealed<T> for mpsc::Receiver<T> {
371 const TRACKED: bool = false;
372
373 fn from_queue(rx: mpsc::Receiver<T>, _: QueueAdmissionCounters) -> Self {
374 rx
375 }
376 }
377
378 impl Sealed<ReceivedApdu> for AdmissionReceiver<ReceivedApdu> {
379 const TRACKED: bool = true;
380
381 fn from_queue(rx: mpsc::Receiver<ReceivedApdu>, counters: QueueAdmissionCounters) -> Self {
382 AdmissionReceiver::from_apdu_parts(rx, counters)
383 }
384 }
385
386 /// Either local APDU receiver, as one type, for the router's
387 /// [application port](crate::router::RouterApplicationPort).
388 pub enum ApduSource {
389 /// The raw queue.
390 Raw(mpsc::Receiver<ReceivedApdu>),
391 /// The tracked queue, whose accounting it keeps.
392 Tracked(AdmissionReceiver<ReceivedApdu>),
393 }
394
395 impl ApduSource {
396 /// The next APDU, or `None` once the queue is closed and drained.
397 pub(crate) async fn recv(&mut self) -> Option<ReceivedApdu> {
398 match self {
399 Self::Raw(rx) => rx.recv().await,
400 Self::Tracked(rx) => rx.recv().await,
401 }
402 }
403 }
404
405 /// How a local APDU receiver becomes an [`ApduSource`].
406 pub trait ApduQueue {
407 /// This receiver as an [`ApduSource`].
408 fn into_source(self) -> ApduSource;
409 }
410
411 impl ApduQueue for mpsc::Receiver<ReceivedApdu> {
412 fn into_source(self) -> ApduSource {
413 ApduSource::Raw(self)
414 }
415 }
416
417 impl ApduQueue for AdmissionReceiver<ReceivedApdu> {
418 fn into_source(self) -> ApduSource {
419 ApduSource::Tracked(self)
420 }
421 }
422
423 impl Sealed<ReceivedNetworkControl> for AdmissionReceiver<ReceivedNetworkControl> {
424 const TRACKED: bool = true;
425
426 fn from_queue(
427 rx: mpsc::Receiver<ReceivedNetworkControl>,
428 counters: QueueAdmissionCounters,
429 ) -> Self {
430 AdmissionReceiver::from_parts(rx, counters)
431 }
432 }
433}
434
435#[cfg(test)]
436#[path = "options_tests.rs"]
437mod tests;