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>
impl<T: TransportPort> NetworkLayer<T>
Sourcepub fn encode_response_for_handoff(
&self,
issued: IssuedApdu<'_>,
route: &ResponseRoute,
) -> Result<BytesMut, Error>
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.
Sourcepub fn seal_responses(&self)
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>
impl<T: TransportPort + 'static> NetworkLayer<T>
Sourcepub 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>
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.
Sourcepub 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
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>
impl<T: TransportPort + 'static> NetworkLayer<T>
Sourcepub async fn send_network_message(
&self,
message_type: NetworkMessageType,
payload: &[u8],
destination_mac: Option<&[u8]>,
) -> Result<(), Error>
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>
impl<T: TransportPort + 'static> NetworkLayer<T>
Sourcepub fn enable_network_control_receiver(
&mut self,
) -> Result<Receiver<ReceivedNetworkControl>, Error>
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.
Sourcepub fn enable_network_control_receiver_with_admission(
&mut self,
) -> Result<AdmissionReceiver<ReceivedNetworkControl>, Error>
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.
Sourcepub async fn start(&mut self) -> Result<ApduReceiver, Error>
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.
Sourcepub async fn start_with_admission(
&mut self,
) -> Result<AdmissionReceiver<ReceivedApdu>, Error>
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>
impl<T: TransportPort + 'static> NetworkLayer<T>
Sourcepub async fn send_apdu(
&self,
apdu: &[u8],
destination_mac: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
) -> Result<(), Error>
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.
Sourcepub async fn send_apdu_with_data_attributes(
&self,
apdu: &[u8],
destination_mac: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
data_attributes: &[DataAttribute],
) -> Result<(), Error>
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.
Sourcepub async fn broadcast_apdu(
&self,
apdu: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
) -> Result<(), Error>
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).
Sourcepub async fn broadcast_apdu_with_data_attributes(
&self,
apdu: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
data_attributes: &[DataAttribute],
) -> Result<(), Error>
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.
Sourcepub async fn broadcast_global_apdu(
&self,
apdu: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
) -> Result<(), Error>
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).
Sourcepub async fn broadcast_global_apdu_with_data_attributes(
&self,
apdu: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
data_attributes: &[DataAttribute],
) -> Result<(), Error>
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).
Sourcepub async fn broadcast_to_network(
&self,
apdu: &[u8],
dest_network: u16,
expecting_reply: bool,
priority: NetworkPriority,
) -> Result<(), Error>
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.
Sourcepub async fn broadcast_to_network_with_data_attributes(
&self,
apdu: &[u8],
dest_network: u16,
expecting_reply: bool,
priority: NetworkPriority,
data_attributes: &[DataAttribute],
) -> Result<(), Error>
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.
Sourcepub async fn send_apdu_routed(
&self,
apdu: &[u8],
dest_network: u16,
dest_mac: &[u8],
router_mac: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
) -> Result<(), Error>
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).
Sourcepub async fn send_apdu_routed_with_data_attributes(
&self,
apdu: &[u8],
target: RoutedTarget<'_>,
expecting_reply: bool,
priority: NetworkPriority,
data_attributes: &[DataAttribute],
) -> Result<(), Error>
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.
Sourcepub async fn send_apdu_routed_via_local_broadcast(
&self,
apdu: &[u8],
dest_network: u16,
dest_mac: &[u8],
expecting_reply: bool,
priority: NetworkPriority,
) -> Result<(), Error>
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).
Sourcepub 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>
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.
Sourcepub fn transport(&self) -> &T
pub fn transport(&self) -> &T
Access the underlying transport.
Useful for transport-specific operations like BBMD registration after the network layer has been started.
Sourcepub fn local_network_number(&self) -> &LocalNetworkNumber
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.
Sourcepub fn network_control_ingress_sequence(&self) -> u64
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.
Sourcepub fn address_length_drops(&self) -> u64
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.
Sourcepub fn global_broadcast_dadr_drops(&self) -> u64
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.
Sourcepub fn broadcast_pdu_type_drops(&self) -> u64
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.
Sourcepub fn counters(&self) -> Counters
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.
Sourcepub async fn stop(&mut self) -> Result<(), Error>
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.