Skip to main content

BACnetRouter

Struct BACnetRouter 

Source
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

Source

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

Source

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

Source

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.

Source

pub fn control_snapshot(&self) -> ControlDecisionCounters

Count-only RB-09 decision totals (allow/deny/policy-deny per class).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn now(&self) -> Instant

The router’s time now: the clock its table and timers run on, Tokio time since the router started.

Source

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.

Trait Implementations§

Source§

impl Drop for BACnetRouter

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more