Skip to main content

bacnet_network/router/
application.rs

1//! The router's application entity as a [`TransportPort`], so a stack built
2//! for one link, such as `bacnet-client`'s `BACnetClient` with its
3//! transaction state machine, runs on the router itself (#1808); or as two,
4//! so a client and a server share it (#2069).
5//!
6//! # The seam
7//!
8//! A router's application is reachable at the router's address on each of
9//! its networks (Clause 6.1), but it has no link of its own. The port gives
10//! it one, with no wire behind it:
11//!
12//! - **Sends** go to [`BACnetRouter::originate`]. The stack above writes an
13//!   NPDU as it would for any remote device: DNET and DADR, or a broadcast to
14//!   one network or to all of them. The port reads the destination, the
15//!   APDU, the priority and the expecting-reply bit back out of it, and the
16//!   router's table picks the route and the next hop. The link MAC the
17//!   stack sends to plays no part, so `router_mac` arguments are ignored. An
18//!   NPDU with no DNET, which would be a local send on a link the
19//!   application lacks, and network messages, which are the router's own
20//!   business, are refused.
21//! - **Answers** to the confirmed requests the stack received go back
22//!   through each request's saved reply plan instead
23//!   ([`application_replies`](super::application_replies)): from the
24//!   endpoint that was asked, along the path the request came by.
25//! - **Receives** come from the router's local APDU receiver, through the
26//!   [feed](super::application_feed): every APDU the router delivers to its
27//!   application is handed up as an NPDU whose SNET and SADR say where it
28//!   came from. An APDU a router relayed keeps its SNET and SADR. One from a
29//!   device on a directly attached network gets that port's network as its
30//!   SNET and the device's link MAC as its SADR, which is the station the
31//!   request was addressed to, so its reply completes the transaction keyed
32//!   to the routed destination. The link source MAC is passed up unchanged.
33//! - **Limits** follow the router's ports: the smallest NPDU limits among
34//!   them as they are now, which change when a port's transport publishes a
35//!   new one (#2070).
36//!
37//! The router chooses the source address of each request
38//! ([`SourceSelection`], the egress port's own by default), and its replies
39//! reach the local receiver the way any APDU for the router does.
40
41use std::io;
42use std::sync::{Arc, Weak};
43
44use bacnet_npdu::{MAX_NPDU, Npdu, NpduLimits};
45use bacnet_router_core::router::{OriginateError, Request, SourceSelection};
46use bacnet_transport::port::{ReceivedNpdu, TransportPort};
47use bacnet_types::MacAddr;
48use bacnet_types::error::Error;
49use tokio::sync::{mpsc, watch};
50use tokio::task::JoinHandle;
51
52use super::application_feed::{Feed, outbound, slot};
53use super::application_replies::Plan;
54use super::application_transactions::Side;
55use super::dispatch::{Outputs, Shared};
56use super::originate::{OriginateFailure, originate};
57use super::{BACnetRouter, LocalApduReceiver, StartedRouter};
58
59/// Room for the network header in front of an APDU: what B/IP leaves
60/// between its 1,497-octet NPDU and 1,476-octet APDU.
61const NPCI_ROOM: u16 = (MAX_NPDU - 1476) as u16;
62
63/// The application entity of a running [`BACnetRouter`] as a
64/// [`TransportPort`] (#1808): give it to a stack that runs on a transport,
65/// such as `bacnet-client`'s `BACnetClient`, and the stack's requests go
66/// through the router's table, and its replies come back from the router.
67///
68/// Made by [`StartedRouter::into_application_port`], from the router's own
69/// local APDU receiver: the stack on the port gets every APDU the router
70/// delivers to its application. [`StartedRouter::into_application_ports`]
71/// makes two instead, for a client and a server ([`RouterApplicationPorts`]).
72/// A remote device is addressed by its network and MAC, as a routed device
73/// (with any MAC as the router's; the table picks the next hop), whether its
74/// network is directly attached or not. A device's reply carries its network
75/// and MAC as its source either way. The port has no local network, so a
76/// send with no DNET, such as a local broadcast, is refused with
77/// [`Error::Encoding`], and so is a network message.
78///
79/// The stack's answers to confirmed requests it receives, such as the
80/// acknowledgement of a confirmed COV notification, go back through the
81/// reply plan the router saved with each request, for up to 30 seconds and
82/// 64 requests at a time, as a server's replies do. A send the router
83/// refuses there fails with the transport error the plan reports.
84///
85/// A refusal from [`BACnetRouter::originate`] fails the send:
86/// [`OriginateError::TooLong`] as [`Error::SendTooLong`], every other one
87/// as [`Error::Transport`] carrying the [`OriginateFailure`], which
88/// [`OriginateFailure::from_error`] gets back. A network the table lacks
89/// fails with [`OriginateError::NoRoute`] and starts a search for it, as
90/// `originate` does; once the router has learned the route, a retry goes.
91///
92/// [`TransportPort::max_npdu`] reports the smallest NPDU limits among the
93/// router's ports as they are now, and [`TransportPort::max_npdu_changes`]
94/// publishes each change once the port has started, such as a BACnet/SC
95/// port's on hub failover (#2070). [`TransportPort::egress_apdu_limit`]
96/// follows them, so a stack that reads it for each request, as
97/// `BACnetClient` does, builds the next request for the new limit.
98///
99/// [`TransportPort::local_receive_apdu_capacity`] is different: it is fixed
100/// from the router ports' limits when the port is made, and a later fall
101/// leaves it alone (#2087). The port can't know a future failover's limit,
102/// so there is nothing smaller to declare at creation, and the trait keeps
103/// the declaration stable so the stacks above need not follow a moving one.
104/// A stack may therefore advertise more, such as a server's
105/// Max_APDU_Length_Accepted, than a shrunken path carries back. The router
106/// guards that path instead: a message routed onto it that is too long is
107/// refused with reject reason 4 (message too long) to its source, and the
108/// port's own send that is too long for it fails with
109/// [`Error::SendTooLong`].
110///
111/// [`TransportPort::stop`] closes the port's queue. Stopping the router
112/// closes its local receiver, which closes the port's queue too, and every
113/// later send fails with [`OriginateFailure::Stopped`].
114///
115/// ```no_run
116/// # use bacnet_network::router::{BACnetRouter, RouterOptions, RouterPort};
117/// # use bacnet_transport::loopback::LoopbackTransport;
118/// # async fn run(ports: Vec<RouterPort<LoopbackTransport>>) -> Result<(), bacnet_types::error::Error> {
119/// let started = BACnetRouter::start(ports, RouterOptions::new()).await?;
120/// let (router, port, _controls) = started.into_application_port();
121/// // `BACnetClient::generic_builder().transport(port).build()` gives a
122/// // client whose requests the router originates.
123/// # Ok(())
124/// # }
125/// ```
126pub struct RouterApplicationPort {
127    outputs: Weak<Outputs>,
128    shared: Arc<Shared>,
129    feed: Arc<Feed>,
130    /// `None` for the whole entity.
131    side: Option<Side>,
132    started: bool,
133    local_mac: MacAddr,
134    /// The router ports' smallest limits, as they change.
135    limits: watch::Receiver<NpduLimits>,
136    /// Those limits when the port was made, which fix its receive capacity.
137    initial: NpduLimits,
138    cap: Option<NpduLimits>,
139    /// [`Self::max_npdu`]'s changes, for [`TransportPort::max_npdu_changes`].
140    published: watch::Sender<NpduLimits>,
141    /// The task that publishes them, once started.
142    follow: Option<JoinHandle<()>>,
143    source: SourceSelection,
144}
145
146/// The router's application entity split between a client and a server
147/// (#2069), from [`StartedRouter::into_application_ports`]: give
148/// [`Self::client`] to a `BACnetClient` and [`Self::server`] to a
149/// `BACnetServer`, each with its `generic_builder`.
150///
151/// Both send as [`RouterApplicationPort`] does. What the router delivers is
152/// shared out by what it is:
153///
154/// - a Confirmed-Request goes to the server, except a
155///   ConfirmedCOVNotification, which goes to the client, and a
156///   ConfirmedEventNotification, which goes to both: the server's answer is
157///   sent and the client's dropped, unless the server's port can't take it
158///   (not started, stopped or full), when the client's answer is sent. A
159///   ConfirmedAuditNotification goes to the server alone;
160/// - an Unconfirmed-Request goes to both, so the client hears I-Am, I-Have
161///   and unconfirmed notifications, and the server Who-Is and Who-Has;
162/// - an answer (Simple-ACK, Complex-ACK, Error, Reject, and a Segment-ACK or
163///   Abort sent by the answering side) goes to the port whose stack sent the
164///   request, matched by device and invoke ID among requests open or closed
165///   within the last 10 seconds, or to the client when neither did. One the
166///   client could mistake for a request that went out under another ID is
167///   dropped;
168/// - a Segment-ACK or Abort sent by a requester goes to the server.
169///
170/// The two stacks choose invoke IDs on their own. When both have a request
171/// open to one device under the same ID, the later one goes out under
172/// another ID free for that device, and its answers come back under its own.
173/// Up to 1,024 requests are tracked, each for 60 seconds from its last
174/// segment, retry or answer; past that the least recently used goes. Each
175/// of the stacks' waits for one answer or segment, such as a client's APDU
176/// timeout, must be shorter than 60 seconds, or a late answer goes
177/// unmatched.
178///
179/// Each port's queue is small, and the router's local queue stays the one
180/// that fills and drops. A stack that stops taking what its port holds for
181/// 100 ms is taken as stalled: the other no longer waits for it, and what
182/// arrives for it is dropped while its queue is full. It holds the other up
183/// for at most 100 ms each time it frees a slot and stays full again.
184///
185/// ```no_run
186/// # use bacnet_network::router::{BACnetRouter, RouterOptions, RouterPort};
187/// # use bacnet_transport::loopback::LoopbackTransport;
188/// # async fn run(ports: Vec<RouterPort<LoopbackTransport>>) -> Result<(), bacnet_types::error::Error> {
189/// let started = BACnetRouter::start(ports, RouterOptions::new()).await?;
190/// let (router, ports, _controls) = started.into_application_ports();
191/// // `BACnetServer::generic_builder().transport(ports.server)` hosts a
192/// // device on the router; `BACnetClient::generic_builder()
193/// // .transport(ports.client)` reads others through it.
194/// # Ok(())
195/// # }
196/// ```
197#[derive(Debug)]
198#[non_exhaustive]
199pub struct RouterApplicationPorts {
200    /// The port for the client: the answers to its requests, notifications
201    /// and every Unconfirmed-Request.
202    pub client: RouterApplicationPort,
203    /// The port for the server: the requests to the entity, the answers to
204    /// its own requests and every Unconfirmed-Request.
205    pub server: RouterApplicationPort,
206}
207
208impl<A: LocalApduReceiver, C> StartedRouter<A, C> {
209    /// The router, its application entity as a [`TransportPort`] fed by
210    /// the router's own local APDU receiver, raw or tracked, and the
211    /// network-control receiver, if one was asked for (#1808). See
212    /// [`RouterApplicationPort`].
213    ///
214    /// The port reports the smallest NPDU limits of the router's ports, as
215    /// they change, and the longest of the router's MACs on them as its
216    /// own: a request routed on from any port with the router's address as
217    /// its source then fits.
218    pub fn into_application_port(self) -> (BACnetRouter, RouterApplicationPort, Option<C>) {
219        let port = self.router.application_port(self.apdus);
220        (self.router, port, self.network_control)
221    }
222
223    /// [`Self::into_application_port`], with the entity split between a
224    /// client and a server (#2069). See [`RouterApplicationPorts`], whose
225    /// invoke-ID tracking needs each stack's APDU and segment timeouts under
226    /// 60 seconds.
227    pub fn into_application_ports(self) -> (BACnetRouter, RouterApplicationPorts, Option<C>) {
228        let ports = self.router.application_ports(self.apdus);
229        (self.router, ports, self.network_control)
230    }
231}
232
233impl BACnetRouter {
234    /// [`StartedRouter::into_application_port`], with `apdus`, which must
235    /// be this router's own local receiver.
236    pub(super) fn application_port<A: LocalApduReceiver>(&self, apdus: A) -> RouterApplicationPort {
237        let feed = Arc::new(Feed::new(apdus.into_source(), false));
238        self.port_on(feed, None)
239    }
240
241    /// [`StartedRouter::into_application_ports`], with `apdus`, which must
242    /// be this router's own local receiver.
243    pub(super) fn application_ports<A: LocalApduReceiver>(
244        &self,
245        apdus: A,
246    ) -> RouterApplicationPorts {
247        let feed = Arc::new(Feed::new(apdus.into_source(), true));
248        RouterApplicationPorts {
249            client: self.port_on(Arc::clone(&feed), Some(Side::Client)),
250            server: self.port_on(feed, Some(Side::Server)),
251        }
252    }
253
254    fn port_on(&self, feed: Arc<Feed>, side: Option<Side>) -> RouterApplicationPort {
255        let local_mac = {
256            let router = self.shared.engine.lock();
257            let longest = router.ports().iter().map(|port| port.mac.as_slice()).fold(
258                &[][..],
259                |longest, mac| {
260                    if mac.len() > longest.len() {
261                        mac
262                    } else {
263                        longest
264                    }
265                },
266            );
267            MacAddr::from_slice(longest)
268        };
269        let limits = self.shared.application_limits.subscribe();
270        let initial = *limits.borrow();
271        RouterApplicationPort {
272            outputs: self.outputs.clone(),
273            shared: Arc::clone(&self.shared),
274            feed,
275            side,
276            started: false,
277            local_mac,
278            limits,
279            initial,
280            cap: None,
281            published: watch::Sender::new(initial),
282            follow: None,
283            source: SourceSelection::default(),
284        }
285    }
286}
287
288impl RouterApplicationPort {
289    /// The router's address each request names as its source, by default
290    /// [`SourceSelection::EgressPort`]: see [`Request::source`].
291    #[must_use]
292    pub fn with_source(mut self, source: SourceSelection) -> Self {
293        self.source = source;
294        self
295    }
296
297    /// Report at most `max_npdu` from [`TransportPort::max_npdu`], below
298    /// the router ports' smallest limits when those are larger. The router
299    /// still refuses a request too long for the port it leaves by.
300    #[must_use]
301    pub fn with_max_npdu(mut self, max_npdu: NpduLimits) -> Self {
302        self.cap = Some(max_npdu);
303        self.published
304            .send_replace(capped(Some(max_npdu), *self.limits.borrow()));
305        self
306    }
307
308    /// Send the NPDU a stack above wrote: an answer through its request's
309    /// saved plan, anything else originated.
310    async fn send(&self, npdu: &[u8]) -> Result<(), Error> {
311        let npdu = Npdu::decode(npdu)
312            .map_err(|error| Error::Encoding(format!("router application send: {error}")))?;
313        if npdu.message_type.is_some() {
314            return Err(Error::Encoding(
315                "the router's application port carries APDUs only; network messages are \
316                 the router's own"
317                    .into(),
318            ));
319        }
320        let Some(destination) = npdu.destination else {
321            return Err(Error::Encoding(
322                "the router's application has no network of its own: address a device by \
323                 its network, or broadcast to one network or to all"
324                    .into(),
325            ));
326        };
327        let replies = &self.feed.replies[slot(self.side)];
328        match replies.plan_for(destination, npdu.payload) {
329            Some(Plan::Send(response)) => {
330                return replies
331                    .send(&response, npdu.payload, npdu.expecting_reply, npdu.priority)
332                    .await;
333            }
334            // The server answers this one.
335            Some(Plan::Silenced) => return Ok(()),
336            None => {}
337        }
338        let apdu = match (self.side, &self.feed.transactions) {
339            (Some(side), Some(transactions)) => {
340                outbound(transactions, side, destination, npdu.payload)?
341            }
342            _ => npdu.payload.into(),
343        };
344        let mut request = Request::new(destination, &apdu);
345        request.expecting_reply = npdu.expecting_reply;
346        request.priority = npdu.priority;
347        request.source = self.source;
348        originate(&self.outputs, &self.shared, &request).map_err(failure_error)
349    }
350
351    fn end_tasks(&mut self) {
352        self.feed.detach(slot(self.side));
353        if let Some(task) = self.follow.take() {
354            task.abort();
355        }
356    }
357
358    /// How many reply plans the port keeps now.
359    #[cfg(test)]
360    pub(super) fn saved_replies(&self) -> usize {
361        self.feed.replies[slot(self.side)].len()
362    }
363}
364
365impl Drop for RouterApplicationPort {
366    fn drop(&mut self) {
367        self.end_tasks();
368    }
369}
370
371impl std::fmt::Debug for RouterApplicationPort {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        f.debug_struct("RouterApplicationPort")
374            .field("side", &self.side)
375            .field("local_mac", &self.local_mac)
376            .field("max_npdu", &self.max_npdu())
377            .field("source", &self.source)
378            .field("started", &self.started)
379            .finish_non_exhaustive()
380    }
381}
382
383impl TransportPort for RouterApplicationPort {
384    async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, Error> {
385        if std::mem::replace(&mut self.started, true) {
386            return Err(Error::Encoding(
387                "router application port already started".to_string(),
388            ));
389        }
390        // Publish each change of the router's limits, capped. Nothing is
391        // locked: a watch borrow, then a send that never waits.
392        let mut limits = self.limits.clone();
393        let published = self.published.clone();
394        let cap = self.cap;
395        self.follow = Some(tokio::spawn(async move {
396            loop {
397                let now = capped(cap, *limits.borrow_and_update());
398                published.send_if_modified(|current| std::mem::replace(current, now) != now);
399                if limits.changed().await.is_err() {
400                    return;
401                }
402            }
403        }));
404        Ok(self.feed.attach(slot(self.side)))
405    }
406
407    async fn stop(&mut self) -> Result<(), Error> {
408        self.end_tasks();
409        Ok(())
410    }
411
412    fn abort(&mut self) {
413        self.end_tasks();
414    }
415
416    async fn send_unicast(&self, npdu: &[u8], _mac: &[u8]) -> Result<(), Error> {
417        self.send(npdu).await
418    }
419
420    async fn send_broadcast(&self, npdu: &[u8]) -> Result<(), Error> {
421        self.send(npdu).await
422    }
423
424    fn local_mac(&self) -> &[u8] {
425        &self.local_mac
426    }
427
428    /// Fixed when the port is made: a declaration that limit changes leave
429    /// alone (#2087). After a router port's limit falls, the router refuses
430    /// what no longer fits that path, with reject reason 4 for a routed
431    /// message and [`Error::SendTooLong`] for this port's own send.
432    fn local_receive_apdu_capacity(&self) -> u16 {
433        apdu_room(capped(self.cap, self.initial).unicast)
434    }
435
436    fn egress_apdu_limit(&self) -> u16 {
437        apdu_room(self.max_npdu().unicast)
438    }
439
440    fn max_npdu(&self) -> NpduLimits {
441        capped(self.cap, *self.limits.borrow())
442    }
443
444    fn max_npdu_changes(&self) -> Option<watch::Receiver<NpduLimits>> {
445        Some(self.published.subscribe())
446    }
447}
448
449/// `limits`, at most `cap`.
450fn capped(cap: Option<NpduLimits>, limits: NpduLimits) -> NpduLimits {
451    match cap {
452        Some(cap) => NpduLimits {
453            unicast: limits.unicast.min(cap.unicast),
454            broadcast: limits.broadcast.min(cap.broadcast),
455        },
456        None => limits,
457    }
458}
459
460/// The APDU room in an NPDU of `max_npdu` octets, at most 1,476.
461fn apdu_room(max_npdu: u16) -> u16 {
462    max_npdu.saturating_sub(NPCI_ROOM).min(1476)
463}
464
465/// The send error for an [`originate`] refusal.
466fn failure_error(failure: OriginateFailure) -> Error {
467    let kind = match failure {
468        OriginateFailure::Refused(OriginateError::TooLong { octets, limit }) => {
469            return Error::SendTooLong {
470                length: octets,
471                limit,
472            };
473        }
474        OriginateFailure::Refused(OriginateError::NoRoute | OriginateError::Unreachable) => {
475            io::ErrorKind::NetworkUnreachable
476        }
477        OriginateFailure::Refused(OriginateError::Busy) | OriginateFailure::QueueFull => {
478            io::ErrorKind::WouldBlock
479        }
480        OriginateFailure::Stopped => io::ErrorKind::NotConnected,
481        _ => io::ErrorKind::InvalidInput,
482    };
483    Error::Transport(io::Error::new(kind, failure))
484}
485
486impl OriginateFailure {
487    /// The refusal behind `error`, when it is a send of a
488    /// [`RouterApplicationPort`] that [`BACnetRouter::originate`] refused,
489    /// other than for length ([`Error::SendTooLong`]).
490    #[must_use]
491    pub fn from_error(error: &Error) -> Option<Self> {
492        match error {
493            Error::Transport(error) => error.get_ref()?.downcast_ref::<Self>().copied(),
494            _ => None,
495        }
496    }
497}
498
499#[cfg(test)]
500#[path = "application_tests.rs"]
501mod tests;