pub struct BACnetRouter { /* private fields */ }Expand description
BACnet router connecting multiple networks.
The router holds multiple ports, each bound to a different BACnet network. When an NPDU arrives on one port with a destination network that maps to another port, the router forwards the message. See the receive-queue contract for raw/tracked local receivers, admission limits, counters and lifecycle.
Every routing decision is bacnet-router-core’s engine (#1679), which
the router keeps behind one lock: each port’s dispatch task takes it for
one frame at a time, as the sweep and Self::table do, and nothing
holds it across an await. The engine’s timers run on Tokio time from the
router’s start. It follows Clause 6 where the router before #1679 didn’t
(R1 to R7 of #1679): egress limits, rejects that keep routes,
network numbers at startup, reachable-only and arrival-port-aware
route answers, the hop count, and the router’s own address on any port.
Implementations§
Source§impl BACnetRouter
impl BACnetRouter
Sourcepub fn network_control_ingress_sequence(&self) -> u64
pub fn network_control_ingress_sequence(&self) -> u64
Sequence assigned to the most recent control offered to the receiver
from RouterOptions::network_control_receiver
or RouterOptions::network_control_receiver_with_admission,
including one the receiver dropped. Saturates at u64::MAX.
Source§impl BACnetRouter
impl BACnetRouter
Sourcepub async fn start<T, A, C>(
ports: Vec<RouterPort<T>>,
options: RouterOptions<A, C>,
) -> Result<StartedRouter<A, C>, Error>
pub async fn start<T, A, C>( ports: Vec<RouterPort<T>>, options: RouterOptions<A, C>, ) -> Result<StartedRouter<A, C>, Error>
Create and start a router from a list of ports, set up as options
asks (#1220).
Returns the router with its local APDU receiver, for messages without
a remote destination or for which this router is the final hop, plus
the network-control receiver when the options ask for it.
RouterOptions::new is the plain router; see RouterOptions for
the tracked APDU receiver, the wire-control policy and the
network-control receiver, which combine freely.
Fails if there are no ports or more than 255 (a routing table’s Port
ID names at most 255), if two ports share a network number, if a
transport fails to start, or if a transport’s own MAC is longer than
NpduAddress::MAX_MAC_LEN
octets.
Sourcepub fn control_snapshot(&self) -> ControlDecisionCounters
pub fn control_snapshot(&self) -> ControlDecisionCounters
Count-only RB-09 decision totals (allow/deny/policy-deny per class).
Sourcepub fn counters(&self) -> Counters
pub fn counters(&self) -> Counters
The engine’s counts since start: the four drop counts below, and
what else the engine counts, such as the frames it refused as
malformed, the relays a spent hop count stopped, the rejects it sent,
the sends too long for their egress port (egress_too_long: the
limit its transport’s TransportPort::max_npdu reports, #1665),
and the rewrites it couldn’t write (write_failures: longer than its
65,556-octet scratch, or with a field no NPDU may carry). They
saturate and name no peer. The table’s own counts are
claim_counters.
They are the counts as of the last frame, sweep or table change the
engine finished, copied out before its lock was let go, so reading
them takes no lock: it is safe while a RouterTableGuard lives,
and so are the four drop counts below, which read them.
Sourcepub fn address_length_drops(&self) -> u64
pub fn address_length_drops(&self) -> u64
NPDUs refused on any port since start because an address in them was
past NpduAddress::MAX_MAC_LEN:
their DLEN or SLEN (#1141), or the link-layer source MAC the port’s
transport reported (#1198). Saturates at u64::MAX.
Such an NPDU is neither forwarded nor delivered locally, and a network
message in it changes no route. When its DLEN or SLEN is the long one
and it names a specific DNET, the router also answers the sender with
Reject-Message-To-Network reason 6, ADDRESSING_ERROR (Clause 6.4.4),
the way it rejects a DNET it cannot reach. A global broadcast, or an
NPDU without a DNET, is only dropped. So is a frame from an over-long
link-layer source MAC: that is a fault in the transport, not in the
NPDU, and the router sends nothing back to such a MAC. No built-in
transport reports one.
Sourcepub fn global_broadcast_dadr_drops(&self) -> u64
pub fn global_broadcast_dadr_drops(&self) -> u64
NPDUs dropped on any port since start because their DNET was 0xFFFF
and they also carried a DADR (#1379). Saturates at u64::MAX.
DNET 0xFFFF already names every device on every network (Clauses
6.2.2 and 6.3.2), so a DADR beside it contradicts it. The router does
not pass such an NPDU on to its other ports, deliver it locally,
act on a network message in it, or answer it with a reject: a
global broadcast never draws one. The address lengths are within
bounds, so the drop is counted here and not in
Self::address_length_drops.
Sourcepub fn broadcast_pdu_type_drops(&self) -> u64
pub fn broadcast_pdu_type_drops(&self) -> u64
NPDUs dropped on any port since start because they were addressed to
a broadcast, global (DNET 0xFFFF) or remote (a DNET with DLEN 0), and
carried an APDU other than an Unconfirmed-Request (#1491). Saturates
at u64::MAX.
Only an Unconfirmed-Request may go to a broadcast network address (Clause 6.3). Anything else belongs to one peer’s transaction, so the router neither passes it on to any network nor delivers it locally, and answers it with no reject. The PDU type comes from the first APDU octet, with no decode; network messages are not affected.
Sourcepub fn group_dadr_drops(&self) -> u64
pub fn group_dadr_drops(&self) -> u64
NPDUs dropped on any port since start because the router would have
delivered them, on a directly connected port, to a DADR that is a
group destination there, such as a B/IP multicast address, and their
APDU isn’t an Unconfirmed-Request (#1504). Saturates at u64::MAX.
Sent as one unicast, such an NPDU would reach every node in the
group without the broadcast network addresses that
Self::broadcast_pdu_type_drops filters: a confirmed request would
draw an answer from each node, and an answer would reach nodes that
asked nothing. The port’s rule is
TransportPort::group_destinations,
so a custom transport that keeps the default recognizes none. The
router delivers it nowhere and answers it with no reject. A network
message routed to such a DADR, or a Reject-Message-To-Network relayed
to one, is dropped and counted too: it must not reach every router on
that network as a broadcast. An Unconfirmed-Request for one of the
groups the port’s broadcast stands for
(TransportPort::local_broadcast_destinations) isn’t counted: it
goes out on that port as a local broadcast, and reaches this router’s
application where a DLEN 0 broadcast would (#1677). On B/IP, one for a
group at another UDP port, another B/IP network, is counted.
Sourcepub fn table(&self) -> RouterTableGuard<'_>
pub fn table(&self) -> RouterTableGuard<'_>
The routing table, locked for local management: read routes, or change them with no wire-control policy in the way.
Every port’s dispatch waits while the guard lives, so drop it soon,
and never hold it across an await. Times in it are the router’s own
(Self::now).
Don’t call it again while a guard lives on the same thread, or from
a ControlAuthorizer: the lock isn’t reentrant, so that call
never returns. The counts, Self::now and
Self::control_snapshot take no lock and are safe to call while
the guard lives.
Sourcepub fn now(&self) -> Instant
pub fn now(&self) -> Instant
The router’s time now: the clock its table and timers run on, Tokio time since the router started.
Sourcepub async fn stop(&mut self)
pub async fn stop(&mut self)
Stop the router.
Aborts and awaits dispatch, sender and aging tasks without waiting for the local application consumer, even with a full queue. Queued local APDUs remain drainable, retaining their reply senders until consumed or the receiver is dropped. Stop is not an admission drop and does not clear depth/high-water/drop totals. See the receive-queue contract.