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 fn originate(&self, request: &Request<'_>) -> Result<(), OriginateFailure>
pub fn originate(&self, request: &Request<'_>) -> Result<(), OriginateFailure>
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.
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 a port’s network number is 0 or 0xFFFF
(RouterPort::network_number), or if two ports share one, all
before any transport starts; and if a transport fails to start, or
if a transport’s own MAC is longer than
MAX_MAC_LEN
octets. Failed startup awaits cleanup of every attempted port, including
a partially started failing port. The original startup error takes
precedence; cleanup failures are logged with port identity.
Cancelling startup initiates retained cleanup on the current runtime.
It does not imply cleanup has completed or resources can be reused.
Dropping a running router similarly initiates best-effort cleanup;
await Self::stop for a successful cleanup barrier.
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, or
a definite pre-send length refusal reported by the transport, including
BACnet/SC frame attributes and direct-peer bounds, #1716),
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 totals as of the last frame, send refusal, sweep or
table change the engine finished: the engine’s own counts are u32s
it starts again each time they are taken, and the router takes them,
and adds them to these u64 totals, before it lets the engine’s lock
go (#1767). 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 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 ports(&self) -> &[RouterPortInfo]
pub fn ports(&self) -> &[RouterPortInfo]
Immutable post-start address snapshots in configured-port order.
Copies retain no live transport resources. The snapshots remain readable after stop and describe that generation, not a new bind or live lookup.
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) -> Result<(), RouterStopError>
pub async fn stop(&mut self) -> Result<(), RouterStopError>
Stop the router and await every transport’s async cleanup.
Seals forwarding and replies, interrupts pending sends, joins router
workers and awaits every transport stop, even when another fails. Ok
is a cleanup completion barrier; Err retains typed failures and makes
no resource-release promise for the failed owners. Repeated calls replay
the same result without retrying. Cancelling this wait leaves cleanup
running; another call can await its completion.
Queued local APDUs remain drainable, but their replies are sealed. Stop does not clear admission totals or wait for local consumers, even with a full queue. See the receive-queue contract.