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;