bacnet_network/router/originate.rs
1//! The router's own application requests, sent through its engine (#1699).
2
3use std::fmt;
4use std::sync::Weak;
5
6use bacnet_router_core::router::{OriginateError, Request};
7
8use super::BACnetRouter;
9use super::actions::drain;
10use super::dispatch::{Outputs, Shared};
11
12/// Why [`BACnetRouter::originate`] failed: it queued nothing, but for a
13/// global broadcast's [`QueueFull`](Self::QueueFull).
14///
15/// It is `#[non_exhaustive]`, so a new reason isn't a breaking change: a
16/// `match` needs a wildcard arm.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum OriginateFailure {
20 /// The engine refused the request, for this reason.
21 Refused(OriginateError),
22 /// The egress port's queue was full, or closing; the refusal is
23 /// counted in [`Counters::refused`](super::Counters::refused).
24 ///
25 /// For a global broadcast, which goes on every port's queue, this
26 /// means at least one port refused its copy: the other ports' copies
27 /// were queued and are sent. Retrying the whole request sends those
28 /// ports a second copy, so don't retry a global broadcast blindly.
29 QueueFull,
30 /// The router has stopped.
31 Stopped,
32}
33
34impl fmt::Display for OriginateFailure {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Refused(error) => error.fmt(f),
38 Self::QueueFull => f.write_str("the egress port's queue refused the request"),
39 Self::Stopped => f.write_str("the router has stopped"),
40 }
41 }
42}
43
44impl std::error::Error for OriginateFailure {
45 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46 match self {
47 Self::Refused(error) => Some(error),
48 _ => None,
49 }
50 }
51}
52
53impl From<OriginateError> for OriginateFailure {
54 fn from(error: OriginateError) -> Self {
55 Self::Refused(error)
56 }
57}
58
59impl BACnetRouter {
60 /// Send a request from the router's own application to another
61 /// network, routed by its table like any other traffic (#1699).
62 ///
63 /// The engine picks the route and writes the NPDU: toward the next
64 /// router with the DNET, DADR and a full hop count, or onto a directly
65 /// attached network to the DADR, with the source address
66 /// [`Request::source`] selects. By default that is the router's
67 /// address on the egress port, so a reply comes back to that port and
68 /// reaches the router's local APDU receiver as any APDU for the router
69 /// does. The NPDU goes on the egress port's queue at the request's
70 /// network priority, as any routed send does, with no data attributes.
71 /// `Ok` means it was queued, not sent.
72 ///
73 /// Synchronous: it takes the engine's lock for the call and its drain,
74 /// like a port's dispatch of one frame, and never waits. A network the
75 /// table lacks is refused with [`OriginateError::NoRoute`], and a
76 /// Who-Is-Router for it goes out on every port, once per cooldown;
77 /// retry once it is learned. After [`Self::stop`] every call fails with
78 /// [`OriginateFailure::Stopped`].
79 ///
80 /// A global broadcast (DNET 0xFFFF with an empty DADR, such as a
81 /// Who-Is for every network; #1809) goes on every port's queue as that
82 /// link's broadcast, still global with a full hop count so the routers
83 /// there pass it on. With [`SourceSelection::EgressPort`] each copy is
84 /// from its own port's address; with [`SourceSelection::Port`] the
85 /// other ports' copies name that port's address as SNET and SADR. A
86 /// queue that refuses its copy fails the call with
87 /// [`OriginateFailure::QueueFull`], but the copies the other ports'
88 /// queues took are still sent: the call is not all or nothing, and a
89 /// retry repeats the broadcast on those ports.
90 ///
91 /// [`SourceSelection::EgressPort`]: bacnet_router_core::router::SourceSelection::EgressPort
92 /// [`SourceSelection::Port`]: bacnet_router_core::router::SourceSelection::Port
93 pub fn originate(&self, request: &Request<'_>) -> Result<(), OriginateFailure> {
94 originate(&self.outputs, &self.shared, request)
95 }
96}
97
98/// [`BACnetRouter::originate`] on the router's queues and engine: the
99/// router's own call, and its [application port's](super::RouterApplicationPort),
100/// which holds the same two handles.
101pub(super) fn originate(
102 outputs: &Weak<Outputs>,
103 shared: &Shared,
104 request: &Request<'_>,
105) -> Result<(), OriginateFailure> {
106 // Only the dispatch tasks own the queues: once `stop` has ended them,
107 // nothing is left to send on.
108 let outputs = outputs.upgrade().ok_or(OriginateFailure::Stopped)?;
109 let now = shared.engine.now();
110 let mut router = shared.engine.lock();
111 let result = router.originate(request, now);
112 // A search for a missing route is polled too. Only the request's own
113 // refusal fails it: a Router-Busy or search refused in the same drain
114 // doesn't.
115 let drained = drain(&mut router, &[], &outputs.send_txs, now);
116 drop(router);
117 result?;
118 if drained.originate_refused {
119 Err(OriginateFailure::QueueFull)
120 } else {
121 Ok(())
122 }
123}