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 super::control_policy::{ControlAuthorizer, ControlGate, ControlPolicy};
14use super::BACnetRouter;
15use crate::layer::{
16    AdmissionReceiver, QueueAdmissionCounters, ReceivedApdu, ReceivedNetworkControl,
17};
18
19/// How [`BACnetRouter::start`] sets up a router.
20///
21/// [`Self::new`] gives the plain router: a raw local APDU receiver, the
22/// permissive wire-control policy with no authorizer, and no network-control
23/// receiver. Each builder method turns on one option, and they combine freely.
24///
25/// `A` is the local APDU receiver type the start returns, a raw
26/// [`mpsc::Receiver`] unless [`Self::track_admission`] switches it to an
27/// [`AdmissionReceiver`]. `C` is the network-control receiver type in the same
28/// way: raw, unless [`Self::network_control_receiver_with_admission`] asks for
29/// the tracked one.
30///
31/// ```no_run
32/// # use bacnet_network::router::{BACnetRouter, RouterOptions, RouterPort, StartedRouter};
33/// # use bacnet_network::router::control_policy::ControlPolicy;
34/// # use bacnet_transport::loopback::LoopbackTransport;
35/// # async fn run(ports: Vec<RouterPort<LoopbackTransport>>) -> Result<(), bacnet_types::error::Error> {
36/// let options = RouterOptions::new()
37///     .track_admission()
38///     .control_policy(ControlPolicy::Hardened)
39///     .network_control_receiver();
40/// let StartedRouter { router, apdus, network_control } =
41///     BACnetRouter::start(ports, options).await?;
42/// let counters = apdus.counters();
43/// # Ok(())
44/// # }
45/// ```
46pub struct RouterOptions<
47    A = mpsc::Receiver<ReceivedApdu>,
48    C = mpsc::Receiver<ReceivedNetworkControl>,
49> {
50    policy: ControlPolicy,
51    authorizer: Option<ControlAuthorizer>,
52    network_control: bool,
53    receivers: PhantomData<fn() -> (A, C)>,
54}
55
56impl RouterOptions {
57    /// The plain router, with every option off.
58    ///
59    /// Its local APDU receiver is a raw [`mpsc::Receiver`]. All ports share
60    /// one 256-item local queue. Full/Closed admission drops the arriving APDU
61    /// with its payload, metadata and reply sender without sending reply bytes
62    /// or a wire rejection, never evicting an older item. Admission never
63    /// waits for the consumer or stops forwarding/inline control handling.
64    /// This raw receiver has no per-source quota, admission snapshot or
65    /// depth/high-water tracking. Full/Closed drops are counted internally,
66    /// with Closed > Full precedence.
67    ///
68    /// Closing retains queued items; dropping discards them. Both leave
69    /// routing active, and each later local admission attempt counts as
70    /// Closed. [`BACnetRouter::stop`] also leaves queued items drainable. Use
71    /// [`Self::track_admission`] for snapshots and per-source fairness; see the
72    /// [receive-queue contract](crate::layer#receive-queue-admission) for the
73    /// raw/tracked matrix and ownership/lifecycle details.
74    pub fn new() -> Self {
75        Self {
76            policy: ControlPolicy::default(),
77            authorizer: None,
78            network_control: false,
79            receivers: PhantomData,
80        }
81    }
82}
83
84impl Default for RouterOptions {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90impl<A, C> RouterOptions<A, C> {
91    /// Return the local APDU receiver as an [`AdmissionReceiver`], which
92    /// exposes queue-admission snapshots and enforces a per-source quota.
93    ///
94    /// Routing and the router's lifecycle are unchanged. All ports share one
95    /// 256-item queue and one accounting state; [`AdmissionReceiver::counters`]
96    /// returns cloneable count-only handles for depth/high-water and
97    /// admission-drop totals, readable after receiver or router drop. Failed
98    /// admission drops the arriving APDU, never an older one, and releases its
99    /// payload, metadata and reply sender without reply bytes or wire
100    /// rejections. Forwarding and inline control handling remain independent.
101    /// Closing the receiver retains queued items for draining and counts each
102    /// subsequent local arrival as a closed drop while forwarding continues.
103    /// [`BACnetRouter::stop`] also leaves queued items drainable. Dropping the
104    /// receiver discards queued items and releases their reply channels
105    /// without sending reply bytes; discarding queued items is not an
106    /// admission drop.
107    ///
108    /// Each ([ingress port network number](ReceivedApdu::ingress_network),
109    /// complete [`source_mac`](ReceivedApdu::source_mac) byte value) may hold
110    /// at most 16 queued local APDUs, never keyed by routed NPDU SNET/SADR.
111    /// Identical MAC bytes on different ports have separate quotas.
112    /// Dequeuing releases one slot, even if the consumer retains the APDU.
113    /// **Closed > fairness > Full** selects one drop reason; over-quota
114    /// arrivals increment
115    /// [`fairness_drops`](crate::layer::QueueAdmissionSnapshot::fairness_drops)
116    /// even if the queue is also globally full, unless admission is closed.
117    /// The raw receiver from [`RouterOptions::new`] does not enforce a quota.
118    /// See the [receive-queue contract](crate::layer#receive-queue-admission)
119    /// for exact keys, the raw/tracked matrix and complete ownership/lifecycle
120    /// rules.
121    pub fn track_admission(self) -> RouterOptions<AdmissionReceiver<ReceivedApdu>, C> {
122        self.receivers()
123    }
124
125    /// RB-09: the wire-control policy for state-changing routing controls.
126    ///
127    /// [`ControlPolicy::Permissive`], the default, admits them.
128    /// [`ControlPolicy::Hardened`] denies a protected control unless an
129    /// authorizer approves it, as a silent drop. Read the totals with
130    /// [`BACnetRouter::control_snapshot`].
131    pub fn control_policy(mut self, policy: ControlPolicy) -> Self {
132        self.policy = policy;
133        self
134    }
135
136    /// RB-09: a callback that decides every protected wire control, whatever
137    /// the [policy](Self::control_policy). See [`ControlAuthorizer`] for what
138    /// it may and may not do.
139    pub fn control_authorizer(mut self, authorizer: ControlAuthorizer) -> Self {
140        self.authorizer = Some(authorizer);
141        self
142    }
143
144    /// Also return the router's own network-control receiver (#1175), in
145    /// [`StartedRouter::network_control`].
146    ///
147    /// The receiver gets each Reject-Message-To-Network addressed to the
148    /// router itself: one with no DNET, or whose DADR is the router's MAC on
149    /// the port attached to that DNET. Such a reject still updates the routing
150    /// table and is never relayed. Without this receiver, the table update is
151    /// all that happens. Every other network message is still handled inline.
152    ///
153    /// Records match what a non-router
154    /// [`NetworkLayer::enable_network_control_receiver`](crate::layer::NetworkLayer::enable_network_control_receiver)
155    /// delivers, numbered from [`BACnetRouter::network_control_ingress_sequence`].
156    /// The queue holds 256 controls, apart from the APDU queue. Admission never
157    /// waits: a full or closed receiver drops the arriving control, and
158    /// routing carries on. This receiver is a raw [`mpsc::Receiver`], which
159    /// counts its drops internally only; use
160    /// [`Self::network_control_receiver_with_admission`] to read them. The two
161    /// are alternatives, and the later call decides the receiver type.
162    pub fn network_control_receiver(
163        mut self,
164    ) -> RouterOptions<A, mpsc::Receiver<ReceivedNetworkControl>> {
165        self.network_control = true;
166        self.receivers()
167    }
168
169    /// [`Self::network_control_receiver`], with the receiver as an
170    /// [`AdmissionReceiver`] that reports on its queue (#1242), as
171    /// [`NetworkLayer::enable_network_control_receiver_with_admission`](crate::layer::NetworkLayer::enable_network_control_receiver_with_admission)
172    /// does for a non-router.
173    ///
174    /// [`AdmissionReceiver::counters`] reads the queue's depth, its high-water
175    /// mark and its full and closed drop totals. Controls have no per-source
176    /// quota, so the fairness total stays zero. The records, the 256-control
177    /// queue and the drop-arriving admission are those of the raw receiver;
178    /// see the [receive-queue contract](crate::layer#receive-queue-admission)
179    /// for what closing, dropping and stopping do to the counts.
180    pub fn network_control_receiver_with_admission(
181        mut self,
182    ) -> RouterOptions<A, AdmissionReceiver<ReceivedNetworkControl>> {
183        self.network_control = true;
184        self.receivers()
185    }
186
187    /// The wire-control gate these options select.
188    pub(super) fn control_gate(&self) -> ControlGate {
189        ControlGate::new(self.policy, self.authorizer.clone())
190    }
191
192    /// Whether a network-control receiver was asked for.
193    pub(super) fn wants_network_control(&self) -> bool {
194        self.network_control
195    }
196
197    /// These options, returning other receiver types.
198    fn receivers<A2, C2>(self) -> RouterOptions<A2, C2> {
199        RouterOptions {
200            policy: self.policy,
201            authorizer: self.authorizer,
202            network_control: self.network_control,
203            receivers: PhantomData,
204        }
205    }
206}
207
208impl<A, C> fmt::Debug for RouterOptions<A, C> {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        f.debug_struct("RouterOptions")
211            .field("policy", &self.policy)
212            .field(
213                "authorizer",
214                &self.authorizer.as_ref().map(|_| "<callback>"),
215            )
216            .field("network_control", &self.network_control)
217            .field("apdus", &std::any::type_name::<A>())
218            .field("controls", &std::any::type_name::<C>())
219            .finish()
220    }
221}
222
223/// A running router and the receivers its [`RouterOptions`] asked for.
224pub struct StartedRouter<
225    A = mpsc::Receiver<ReceivedApdu>,
226    C = mpsc::Receiver<ReceivedNetworkControl>,
227> {
228    /// The router.
229    pub router: BACnetRouter,
230    /// APDUs for local applications: those without a remote destination, or
231    /// for which this router is the final hop.
232    pub apdus: A,
233    /// The router's own network-control receiver, when
234    /// [`RouterOptions::network_control_receiver`] or
235    /// [`RouterOptions::network_control_receiver_with_admission`] asked for it.
236    pub network_control: Option<C>,
237}
238
239/// A local APDU receiver type [`BACnetRouter::start`] can return: the raw
240/// [`mpsc::Receiver`] or, after [`RouterOptions::track_admission`], an
241/// [`AdmissionReceiver`]. Sealed.
242pub trait LocalApduReceiver: sealed::Sealed<ReceivedApdu> {}
243
244impl LocalApduReceiver for mpsc::Receiver<ReceivedApdu> {}
245impl LocalApduReceiver for AdmissionReceiver<ReceivedApdu> {}
246
247/// A network-control receiver type [`BACnetRouter::start`] can return: the
248/// raw [`mpsc::Receiver`] from [`RouterOptions::network_control_receiver`] or
249/// the [`AdmissionReceiver`] from
250/// [`RouterOptions::network_control_receiver_with_admission`]. Sealed.
251pub trait NetworkControlReceiver: sealed::Sealed<ReceivedNetworkControl> {}
252
253impl NetworkControlReceiver for mpsc::Receiver<ReceivedNetworkControl> {}
254impl NetworkControlReceiver for AdmissionReceiver<ReceivedNetworkControl> {}
255
256pub(super) mod sealed {
257    use super::*;
258
259    /// How the start builds each receiver type from its queue of `T`.
260    pub trait Sealed<T>: Sized {
261        /// Whether the queue keeps depth accounting (and, for APDUs,
262        /// per-source accounting).
263        const TRACKED: bool;
264
265        /// Wrap the queue's receiving end.
266        fn from_queue(rx: mpsc::Receiver<T>, counters: QueueAdmissionCounters) -> Self;
267    }
268
269    impl<T> Sealed<T> for mpsc::Receiver<T> {
270        const TRACKED: bool = false;
271
272        fn from_queue(rx: mpsc::Receiver<T>, _: QueueAdmissionCounters) -> Self {
273            rx
274        }
275    }
276
277    impl Sealed<ReceivedApdu> for AdmissionReceiver<ReceivedApdu> {
278        const TRACKED: bool = true;
279
280        fn from_queue(rx: mpsc::Receiver<ReceivedApdu>, counters: QueueAdmissionCounters) -> Self {
281            AdmissionReceiver::from_apdu_parts(rx, counters)
282        }
283    }
284
285    impl Sealed<ReceivedNetworkControl> for AdmissionReceiver<ReceivedNetworkControl> {
286        const TRACKED: bool = true;
287
288        fn from_queue(
289            rx: mpsc::Receiver<ReceivedNetworkControl>,
290            counters: QueueAdmissionCounters,
291        ) -> Self {
292            AdmissionReceiver::from_parts(rx, counters)
293        }
294    }
295}
296
297#[cfg(test)]
298#[path = "options_tests.rs"]
299mod tests;