Skip to main content

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}