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