Skip to main content

NetworkLayer

Struct NetworkLayer 

Source
pub struct NetworkLayer<T: TransportPort> { /* private fields */ }
Expand description

Non-router BACnet network layer.

Wraps a TransportPort and provides APDU-level send/receive by handling NPDU framing. This layer does not act as a router (it does not forward messages between networks), but it can send to remote devices through local routers using NPDU destination addressing. See the receive-queue contract for raw/tracked receiver choices, admission limits, drop attribution and lifecycle. An inbound frame from a link-layer source MAC longer than MAX_MAC_LEN, or whose DLEN or SLEN is past it, is dropped before admission and counted by Self::address_length_drops; one whose DNET 0xFFFF carries a DADR is dropped and counted by Self::global_broadcast_dadr_drops, and a broadcast whose APDU isn’t an Unconfirmed-Request by Self::broadcast_pdu_type_drops. These rules are bacnet-router-core’s, the router engine’s own (#1795): the layer also drops a malformed NPDU, and one whose DNET names one network, APDU or network message, which no node but a router acts on (Clauses 6.5.2.1 and 6.5.2.2). Self::counters totals every drop by reason.

Implementations§

Source§

impl<T: TransportPort> NetworkLayer<T>

Source

pub fn encode_response_for_handoff( &self, issued: IssuedApdu<'_>, route: &ResponseRoute, ) -> Result<BytesMut, Error>

Encode a prompt data-link response after checking both this network owner and the captured router/direct authority. The caller hands these bytes to its one-shot DER sender without an intervening await.

Source

pub fn seal_responses(&self)

Seal original-route response issuance before joining owned server work. Queued writes observe this irreversible seal; writes already started remain bounded but cannot be retracted. Network stop/drop also seals.

Source§

impl<T: TransportPort + 'static> NetworkLayer<T>

Source

pub async fn send_apdu_on_issuance( &self, apdu: &[u8], next_hop: &[u8], destination: Option<&NpduAddress>, expecting_reply: bool, priority: NetworkPriority, on_issuance: impl FnOnce() + Send, ) -> Result<(), Error>

Issue one local unicast operation after encoding its complete NPDU.

destination selects routed NPDU addressing; next_hop is always the local data-link destination. The synchronous callback runs exactly once after encoding succeeds, immediately before calling/polling the transport send. Constructing this future does not call it. Encoding failure never calls it; later transport failure does not undo issuance. This boundary does not establish physical emission or remote receipt. A destination on network 0 or 0xFFFF is an encoding failure: this unicast reaches one next hop, and a global broadcast goes out through NetworkLayer::broadcast_global_apdu instead (#1380). A destination with no DADR is a remote broadcast, which carries only an Unconfirmed-Request APDU (Clause 6.3), so any other APDU to it is an encoding failure too (#1479). With no destination, a next_hop that is the link’s broadcast or another group address is a local broadcast as well, but this send takes next_hop as given: unlike NetworkLayer::send_apdu, it doesn’t send an Unconfirmed-Request for one as the link’s broadcast, and nothing checks it, since a response goes back to the MAC its request came from (B/IP refuses a group address there, #1677).

Used by server transaction owners whose lifetime ends at local issuance, independently of the transport future’s eventual Result.

Source

pub fn send_response_apdu_on_issuance<'a>( &'a self, issued: IssuedApdu<'a>, route: &'a ResponseRoute, on_issuance: impl FnOnce() + Send + 'static, ) -> impl Future<Output = Result<(), Error>> + Send + 'a

Encode and issue a response using its immutable ingress route.

Router replies use the captured port/peer and addressed source identity; direct responses require the matching original socket as well. Missing or retired authority never falls back to ordinary unicast. Router replies wait for their bounded send queue: cancellation before selection sends nothing and drops the callback. The callback is owned by that queue and runs when its transport send starts, with the local issuance contract of Self::send_apdu_on_issuance; neither issuance nor success proves receipt.

A server’s request handler carries this future for every reply, so it is kept small (#1713): not an async fn, which would copy issued and on_issuance into locals of their own, and issued is read in place.

Source§

impl<T: TransportPort + 'static> NetworkLayer<T>

Source

pub async fn send_network_message( &self, message_type: NetworkMessageType, payload: &[u8], destination_mac: Option<&[u8]>, ) -> Result<(), Error>

Send a network-layer message on the local network, with no DNET: broadcast when destination_mac is None, else to that peer.

The NPDU names no destination network, so routers act on it themselves rather than pass it on, as Who-Is-Router-To-Network and What-Is-Network-Number need (Clauses 6.4.1 and 6.4.19). A destination_mac that is one of the link’s local broadcast destinations goes out as the local broadcast, as Self::send_apdu does. Only the types in SENDABLE_NETWORK_MESSAGES are sent; any other is refused before anything is sent.

Source§

impl<T: TransportPort + 'static> NetworkLayer<T>

Source

pub fn enable_network_control_receiver( &mut self, ) -> Result<Receiver<ReceivedNetworkControl>, Error>

Enable the one-consumer decoded network-control stream with a raw receiver.

Call before either Self::start or Self::start_with_admission, at most once across the two control-enable alternatives. Returns an encoding error if the layer has started or a control receiver is already enabled. With Self::start, controls arrive only while its ApduReceiver is polled; with Self::start_with_admission, whenever they arrive. The separate 256-item control queue has no per-source quota: Closed takes precedence over Full, with Full/Closed counted internally but no snapshot or depth/high-water tracking. Failed admission drops the control and its metadata without waiting for a consumer or generating reply bytes/wire rejections; APDU admission remains independent.

Closing retains queued controls; dropping discards them. The first later control admission attempt disables only this stream, then controls resume discard behavior. Use Self::enable_network_control_receiver_with_admission for counters, and see the receive-queue contract for the raw/tracked matrix, sequencing and stop/drain semantics.

Source

pub fn enable_network_control_receiver_with_admission( &mut self, ) -> Result<AdmissionReceiver<ReceivedNetworkControl>, Error>

Enable the control stream with queue-admission snapshots.

This has the same before-start, one-consumer contract and errors as Self::enable_network_control_receiver; the two methods are alternatives. The separate 256-item queue tracks depth/high-water and Full/Closed drops through AdmissionReceiver::counters. It has no per-source quota, so fairness drops are always zero and precedence is Closed > Full. Drop-arriving, ownership release, no-reply/no-wire-rejection and close/drop/stop behavior follow the shared receive-queue contract.

Controls discarded without opting into either stream are not admission drops. Self::network_control_ingress_sequence advances before every attempted control admission, including Full and the first Closed drop, but not for later controls once that stream is disabled.

Source

pub async fn start(&mut self) -> Result<ApduReceiver, Error>

Start the network layer. Returns the receiver for incoming APDUs.

This starts the underlying transport. The ApduReceiver decodes the transport’s NPDUs in the task that polls it, with no dispatch task or second queue between them (#1713): the transport’s queue is the only buffer and bounds memory under overload, and the transport counts what it drops. Network messages reach an opted-in control receiver only while the APDU receiver is polled. Receive is cancel safe.

Close retains the transport’s queued NPDUs for draining; drop discards them. Self::stop after start leaves them drainable too. Use Self::start_with_admission for a tracked queue with snapshots and per-source fairness, whose controls flow independently; see the receive-queue contract.

Source

pub async fn start_with_admission( &mut self, ) -> Result<AdmissionReceiver<ReceivedApdu>, Error>

Start with an APDU receiver that also exposes admission snapshots.

This is an alternative to Self::start. It runs a dispatch task that decodes into its own queue, so controls flow whether or not this receiver is polled. Self::stop closes admission but leaves queued items available to drain. Dropping the receiver discards those items and releases their reply channels without sending reply bytes.

Unlike the raw receiver, this tracks depth/high-water and drop totals via AdmissionReceiver::counters and caps each complete source MAC byte value at 16 queued, unconsumed APDUs in the 256-item queue. The single transport uses no port key and ignores routed NPDU SNET/SADR for quotas. Dequeuing releases one source slot even if the consumer retains the APDU. Closed > fairness > Full selects one drop reason, releasing the arriving payload, metadata and reply sender without sending reply bytes or a wire rejection. Control queues have separate capacity and no quota. For the router’s port-scoped key, raw/tracked matrix, and full close/drop/stop and counter-lifetime rules, see the receive-queue contract.

use bacnet_network::layer::NetworkLayer;
use bacnet_transport::loopback::LoopbackTransport;
let (transport, _peer) = LoopbackTransport::pair(vec![1], vec![2]);
let mut network = NetworkLayer::new(transport);
let mut controls = network.enable_network_control_receiver_with_admission()?;
let mut apdus = network.start_with_admission().await?;
let apdu_counters = apdus.counters();
let control_counters = controls.counters();
tokio::select! {
    _ = apdus.recv() => {},
    _ = controls.recv() => {},
}
let _snapshots = (apdu_counters.snapshot(), control_counters.snapshot());
network.stop().await?;
Source§

impl<T: TransportPort + 'static> NetworkLayer<T>

Source

pub fn new(transport: T) -> Self

Create a new network layer wrapping the given transport.

Source

pub async fn send_apdu( &self, apdu: &[u8], destination_mac: &[u8], expecting_reply: bool, priority: NetworkPriority, ) -> Result<(), Error>

Send an APDU to a specific local destination by MAC address.

With no DNET, the link’s broadcast MAC or any other group address (TransportPort::is_group_destination) makes this a local broadcast, which may carry only an Unconfirmed-Request APDU (Clause 6.3).

An Unconfirmed-Request to one of TransportPort::local_broadcast_destinations goes out as the link’s local broadcast, through send_broadcast, since B/IP refuses a unicast to a group address (#1677). On B/IP those are the groups at the link’s own UDP port. The layer takes that rule when it is made and when it starts, so no send calls back into the transport; a transport that gives no rule has every send go as addressed.

Anything else goes to the transport as given, and B/IP refuses a group address there. This layer doesn’t refuse it itself, so that is the caller’s to keep: BACnetClient, the endpoint’s egress and its requester, which take caller-chosen MACs, refuse anything but an Unconfirmed-Request to one (#1479). The server refuses one for the confirmed requests it starts itself, to event recipients, Channel and Command targets, audit recipients and bound devices (#1493). Its replies and COV notifications go to the source a request came from, and the server, the client and the endpoint ignore a confirmed request from a group source MAC (#1504), so none of those goes to a group on this link. A routed request’s SADR is another network’s to judge: a reply to a group SADR is dropped only where the final router is a BACnetRouter.

Source

pub async fn send_apdu_with_data_attributes( &self, apdu: &[u8], destination_mac: &[u8], expecting_reply: bool, priority: NetworkPriority, data_attributes: &[DataAttribute], ) -> Result<(), Error>

Send an APDU with data attributes to a specific local destination.

Source

pub async fn broadcast_apdu( &self, apdu: &[u8], expecting_reply: bool, priority: NetworkPriority, ) -> Result<(), Error>

Broadcast an APDU on the local network. Anything but an Unconfirmed-Request APDU is refused before it is sent (Clause 6.3, #1479).

Source

pub async fn broadcast_apdu_with_data_attributes( &self, apdu: &[u8], expecting_reply: bool, priority: NetworkPriority, data_attributes: &[DataAttribute], ) -> Result<(), Error>

Broadcast an APDU with data attributes on the local network.

Source

pub async fn broadcast_global_apdu( &self, apdu: &[u8], expecting_reply: bool, priority: NetworkPriority, ) -> Result<(), Error>

Broadcast an APDU globally (DNET=0xFFFF, hop_count=255).

Unlike broadcast_apdu() which only reaches the local subnet, this sets DNET=0xFFFF so routers will forward to all reachable networks. Anything but an Unconfirmed-Request APDU is refused before it is sent (Clause 6.3, #1479).

Source

pub async fn broadcast_global_apdu_with_data_attributes( &self, apdu: &[u8], expecting_reply: bool, priority: NetworkPriority, data_attributes: &[DataAttribute], ) -> Result<(), Error>

Broadcast an APDU globally with data attributes (DNET=0xFFFF, hop_count=255).

Source

pub async fn broadcast_to_network( &self, apdu: &[u8], dest_network: u16, expecting_reply: bool, priority: NetworkPriority, ) -> Result<(), Error>

Broadcast an APDU to a specific remote network via routers.

Like broadcast_global_apdu() but targets a single network number instead of all networks (DNET=0xFFFF). Refuses dest_network 0 and 0xFFFF, and anything but an Unconfirmed-Request APDU (Clause 6.3, #1479), without sending anything. This is the one send that puts a remote network’s broadcast on the link’s broadcast MAC; Self::send_apdu_routed with an empty dest_mac sends it to a known router instead.

Source

pub async fn broadcast_to_network_with_data_attributes( &self, apdu: &[u8], dest_network: u16, expecting_reply: bool, priority: NetworkPriority, data_attributes: &[DataAttribute], ) -> Result<(), Error>

Broadcast an APDU with data attributes to a specific remote network via routers.

Source

pub async fn send_apdu_routed( &self, apdu: &[u8], dest_network: u16, dest_mac: &[u8], router_mac: &[u8], expecting_reply: bool, priority: NetworkPriority, ) -> Result<(), Error>

Send an APDU to a remote device through a local router.

The NPDU is sent via unicast to router_mac (the next-hop router on the local network), but the NPDU header addresses the final destination with dest_network / dest_mac. Refuses dest_network 0 and 0xFFFF without sending anything; this form and Self::send_apdu_routed_via_local_broadcast share that check. A global broadcast unicast to one router would reach only that router’s networks, so it goes through Self::broadcast_global_apdu instead.

An empty dest_mac (DLEN 0) asks the router to broadcast on dest_network, a remote broadcast, so it carries only an Unconfirmed-Request APDU (Clause 6.3); anything else is refused without sending anything (#1479).

Source

pub async fn send_apdu_routed_with_data_attributes( &self, apdu: &[u8], target: RoutedTarget<'_>, expecting_reply: bool, priority: NetworkPriority, data_attributes: &[DataAttribute], ) -> Result<(), Error>

Send an APDU with data attributes to a remote device through a local router.

Source

pub async fn send_apdu_routed_via_local_broadcast( &self, apdu: &[u8], dest_network: u16, dest_mac: &[u8], expecting_reply: bool, priority: NetworkPriority, ) -> Result<(), Error>

Send a routed APDU with a broadcast link DA, for when the next-hop router’s MAC is unknown.

Clause 6.5.3 normally addresses the frame to the MAC of the router that serves DNET, and falls back to a link broadcast while that router has not been located yet. DNET/DADR still select a single device in the NPDU. Clause 6.3 reserves broadcast network addresses for Unconfirmed-Request PDUs, but that limits the network-layer address, not the link DA: a link broadcast carrying a one-device DNET/DADR is fine for any PDU type, so this send form stays conformant. DNET 0xFFFF would make it a global broadcast, which this form doesn’t mean, so it refuses that DNET as Self::send_apdu_routed does. An empty dest_mac would make it a broadcast on dest_network, which Self::broadcast_to_network already sends, so this form refuses it too and points there, leaving one way to send a remote broadcast with the link’s broadcast MAC (#1479).

Source

pub async fn send_apdu_routed_via_local_broadcast_with_data_attributes( &self, apdu: &[u8], dest_network: u16, dest_mac: &[u8], expecting_reply: bool, priority: NetworkPriority, data_attributes: &[DataAttribute], ) -> Result<(), Error>

Send a routed APDU with data attributes and a broadcast link DA.

Source

pub fn transport(&self) -> &T

Access the underlying transport.

Useful for transport-specific operations like BBMD registration after the network layer has been started.

Source

pub fn local_mac(&self) -> &[u8] ⓘ

Get the transport’s local MAC address.

Source

pub fn local_network_number(&self) -> &LocalNetworkNumber

The number of the network this layer’s one port is attached to, as the owner of its Number controls last published it, and unknown until then. The full server and the standalone client publish into their own layers internally; a stack built directly on this layer publishes its own, and can clone the handle to publish from another task.

Source

pub fn network_control_ingress_sequence(&self) -> u64

Sequence assigned to the most recent opted-in control admission attempt.

The value advances before admission, including Full and Closed drops, but not for controls discarded without an enabled stream or after its first Closed drop. See the receive-queue contract. At counter exhaustion it remains at u64::MAX, which makes sequence-based consumers fail closed rather than accepting an alias.

Source

pub fn address_length_drops(&self) -> u64

Inbound NPDUs dropped since this layer was created because an address in them was past MAX_MAC_LEN: their DLEN or SLEN (#1141), or the link-layer source MAC the transport reported (#1198). Saturates at u64::MAX.

Only routers answer an NPDU with Reject-Message-To-Network (Clause 6.6.3.5), so this non-router layer discards the NPDU and counts it. The NPDU reaches neither the APDU nor the control receiver, so it never shows up in their admission counters. No built-in transport reports a source MAC that long, so a nonzero count with well-formed traffic points at a custom transport.

Source

pub fn global_broadcast_dadr_drops(&self) -> u64

Inbound NPDUs dropped since this layer was created 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. Such an NPDU reaches neither the APDU nor the control receiver, so it never shows up in their admission counters. It is counted here rather than in Self::address_length_drops, whose addresses are too long to hold: this one is well formed, only contradictory, and points at the peer that sent it.

Source

pub fn broadcast_pdu_type_drops(&self) -> u64

Inbound NPDUs dropped since this layer was created 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), so this layer hands no other APDU sent that way to the application, and the NPDU never shows up in the receivers’ admission counters. A remote broadcast, which a non-router discards anyway, is counted here too when its APDU is of another type. The PDU type comes from the first APDU octet, with no decode; network messages, and an APDU sent with no DNET, are not affected.

Source

pub fn counters(&self) -> Counters

What this layer has dropped on receipt since it was created, by reason: the totals of the receive rules it shares with the router’s engine (bacnet-router-core’s node, #1795). Reading them takes no lock.

Source

pub async fn stop(&mut self) -> Result<(), Error>

Stop the network layer and underlying transport.

After start, aborts and awaits dispatch before stopping the transport; it does not wait for consumers, even with full APDU/control queues. An ApduReceiver yields what the transport had queued, then None. Queued items remain drainable, retaining their reply senders until consumed or the receiver is dropped. Stop does not count as an admission drop or clear depth/high-water/drop totals. See the receive-queue contract.

Trait Implementations§

Source§

impl<T: TransportPort> Drop for NetworkLayer<T>

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§

§

impl<T> !RefUnwindSafe for NetworkLayer<T>

§

impl<T> !UnwindSafe for NetworkLayer<T>

§

impl<T> Freeze for NetworkLayer<T>
where T: Freeze,

§

impl<T> Send for NetworkLayer<T>

§

impl<T> Sync for NetworkLayer<T>

§

impl<T> Unpin for NetworkLayer<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for NetworkLayer<T>
where T: UnsafeUnpin,

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