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