Skip to main content

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;