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::control_policy::{ControlAuthorizer, ControlGate, ControlPolicy};
16use super::engine::OPTIONS;
17use super::BACnetRouter;
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 /// Sizing: the record must hold every global broadcast the router sees
239 /// within one time to live, or the oldest is forgotten before its
240 /// looped copy returns. 512 covers 512 broadcasts a second at the
241 /// default; a longer time to live needs proportionally fewer a second.
242 pub fn duplicates(mut self, policy: Duplicates) -> Self {
243 self.engine.duplicates = Some(policy);
244 self
245 }
246
247 /// The engine's options these select.
248 pub(super) fn engine_options(&self) -> Options {
249 self.engine
250 }
251
252 /// The wire-control gate these options select.
253 pub(super) fn control_gate(&self) -> ControlGate {
254 ControlGate::new(self.policy, self.authorizer.clone())
255 }
256
257 /// Whether a network-control receiver was asked for.
258 pub(super) fn wants_network_control(&self) -> bool {
259 self.network_control
260 }
261
262 /// These options, returning other receiver types.
263 fn receivers<A2, C2>(self) -> RouterOptions<A2, C2> {
264 RouterOptions {
265 policy: self.policy,
266 authorizer: self.authorizer,
267 network_control: self.network_control,
268 engine: self.engine,
269 receivers: PhantomData,
270 }
271 }
272}
273
274impl<A, C> fmt::Debug for RouterOptions<A, C> {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.debug_struct("RouterOptions")
277 .field("policy", &self.policy)
278 .field(
279 "authorizer",
280 &self.authorizer.as_ref().map(|_| "<callback>"),
281 )
282 .field("network_control", &self.network_control)
283 .field("engine", &self.engine)
284 .field("apdus", &std::any::type_name::<A>())
285 .field("controls", &std::any::type_name::<C>())
286 .finish()
287 }
288}
289
290/// A running router and the receivers its [`RouterOptions`] asked for.
291pub struct StartedRouter<
292 A = mpsc::Receiver<ReceivedApdu>,
293 C = mpsc::Receiver<ReceivedNetworkControl>,
294> {
295 /// The router.
296 pub router: BACnetRouter,
297 /// APDUs for local applications: those without a remote destination, or
298 /// for which this router is the final hop.
299 pub apdus: A,
300 /// The router's own network-control receiver, when
301 /// [`RouterOptions::network_control_receiver`] or
302 /// [`RouterOptions::network_control_receiver_with_admission`] asked for it.
303 pub network_control: Option<C>,
304}
305
306/// A local APDU receiver type [`BACnetRouter::start`] can return: the raw
307/// [`mpsc::Receiver`] or, after [`RouterOptions::track_admission`], an
308/// [`AdmissionReceiver`]. Sealed.
309pub trait LocalApduReceiver: sealed::Sealed<ReceivedApdu> {}
310
311impl LocalApduReceiver for mpsc::Receiver<ReceivedApdu> {}
312impl LocalApduReceiver for AdmissionReceiver<ReceivedApdu> {}
313
314/// A network-control receiver type [`BACnetRouter::start`] can return: the
315/// raw [`mpsc::Receiver`] from [`RouterOptions::network_control_receiver`] or
316/// the [`AdmissionReceiver`] from
317/// [`RouterOptions::network_control_receiver_with_admission`]. Sealed.
318pub trait NetworkControlReceiver: sealed::Sealed<ReceivedNetworkControl> {}
319
320impl NetworkControlReceiver for mpsc::Receiver<ReceivedNetworkControl> {}
321impl NetworkControlReceiver for AdmissionReceiver<ReceivedNetworkControl> {}
322
323pub(super) mod sealed {
324 use super::*;
325
326 /// How the start builds each receiver type from its queue of `T`.
327 pub trait Sealed<T>: Sized {
328 /// Whether the queue keeps depth accounting (and, for APDUs,
329 /// per-source accounting).
330 const TRACKED: bool;
331
332 /// Wrap the queue's receiving end.
333 fn from_queue(rx: mpsc::Receiver<T>, counters: QueueAdmissionCounters) -> Self;
334 }
335
336 impl<T> Sealed<T> for mpsc::Receiver<T> {
337 const TRACKED: bool = false;
338
339 fn from_queue(rx: mpsc::Receiver<T>, _: QueueAdmissionCounters) -> Self {
340 rx
341 }
342 }
343
344 impl Sealed<ReceivedApdu> for AdmissionReceiver<ReceivedApdu> {
345 const TRACKED: bool = true;
346
347 fn from_queue(rx: mpsc::Receiver<ReceivedApdu>, counters: QueueAdmissionCounters) -> Self {
348 AdmissionReceiver::from_apdu_parts(rx, counters)
349 }
350 }
351
352 impl Sealed<ReceivedNetworkControl> for AdmissionReceiver<ReceivedNetworkControl> {
353 const TRACKED: bool = true;
354
355 fn from_queue(
356 rx: mpsc::Receiver<ReceivedNetworkControl>,
357 counters: QueueAdmissionCounters,
358 ) -> Self {
359 AdmissionReceiver::from_parts(rx, counters)
360 }
361 }
362}
363
364#[cfg(test)]
365#[path = "options_tests.rs"]
366mod tests;